refactor(react/settings): add names to all form groups

This commit is contained in:
Elian Doran 2025-08-19 23:34:25 +03:00
parent 51291a61e6
commit 1d7799f981
No known key found for this signature in database
29 changed files with 144 additions and 154 deletions

View File

@ -107,7 +107,7 @@ function AddLinkDialogComponent() {
}} }}
show={shown} show={shown}
> >
<FormGroup label={t("add_link.note")}> <FormGroup label={t("add_link.note")} name="note">
<NoteAutocomplete <NoteAutocomplete
inputRef={autocompleteRef} inputRef={autocompleteRef}
onChange={setSuggestion} onChange={setSuggestion}

View File

@ -64,7 +64,7 @@ function BranchPrefixDialogComponent() {
footer={<Button text={t("branch_prefix.save")} />} footer={<Button text={t("branch_prefix.save")} />}
show={shown} show={shown}
> >
<FormGroup label={t("branch_prefix.prefix")}> <FormGroup label={t("branch_prefix.prefix")} name="prefix">
<div class="input-group"> <div class="input-group">
<input class="branch-prefix-input form-control" value={prefix} ref={branchInput} <input class="branch-prefix-input form-control" value={prefix} ref={branchInput}
onChange={(e) => setPrefix((e.target as HTMLInputElement).value)} /> onChange={(e) => setPrefix((e.target as HTMLInputElement).value)} />

View File

@ -69,15 +69,15 @@ function CloneToDialogComponent() {
> >
<h5>{t("clone_to.notes_to_clone")}</h5> <h5>{t("clone_to.notes_to_clone")}</h5>
<NoteList style={{ maxHeight: "200px", overflow: "auto" }} noteIds={clonedNoteIds} /> <NoteList style={{ maxHeight: "200px", overflow: "auto" }} noteIds={clonedNoteIds} />
<FormGroup label={t("clone_to.target_parent_note")}> <FormGroup name="target-parent-note" label={t("clone_to.target_parent_note")}>
<NoteAutocomplete <NoteAutocomplete
placeholder={t("clone_to.search_for_note_by_its_name")} placeholder={t("clone_to.search_for_note_by_its_name")}
onChange={setSuggestion} onChange={setSuggestion}
inputRef={autoCompleteRef} inputRef={autoCompleteRef}
/> />
</FormGroup> </FormGroup>
<FormGroup label={t("clone_to.prefix_optional")} title={t("clone_to.cloned_note_prefix_title")}> <FormGroup name="clone-prefix" label={t("clone_to.prefix_optional")} title={t("clone_to.cloned_note_prefix_title")}>
<FormTextBox name="clone-prefix" onChange={setPrefix} /> <FormTextBox onChange={setPrefix} />
</FormGroup> </FormGroup>
</Modal> </Modal>
) )

View File

@ -4,7 +4,7 @@ import tree from "../../services/tree";
import Button from "../react/Button"; import Button from "../react/Button";
import FormCheckbox from "../react/FormCheckbox"; import FormCheckbox from "../react/FormCheckbox";
import FormFileUpload from "../react/FormFileUpload"; import FormFileUpload from "../react/FormFileUpload";
import FormGroup from "../react/FormGroup"; import FormGroup, { FormMultiGroup } from "../react/FormGroup";
import Modal from "../react/Modal"; import Modal from "../react/Modal";
import RawHtml from "../react/RawHtml"; import RawHtml from "../react/RawHtml";
import ReactBasicWidget from "../react/ReactBasicWidget"; import ReactBasicWidget from "../react/ReactBasicWidget";
@ -55,11 +55,11 @@ function ImportDialogComponent() {
footer={<Button text={t("import.import")} primary disabled={!files} />} footer={<Button text={t("import.import")} primary disabled={!files} />}
show={shown} show={shown}
> >
<FormGroup label={t("import.chooseImportFile")} description={<>{t("import.importDescription")} <strong>{ noteTitle }</strong></>}> <FormGroup name="files" label={t("import.chooseImportFile")} description={<>{t("import.importDescription")} <strong>{ noteTitle }</strong></>}>
<FormFileUpload multiple onChange={setFiles} /> <FormFileUpload multiple onChange={setFiles} />
</FormGroup> </FormGroup>
<FormGroup label={t("import.options")}> <FormMultiGroup label={t("import.options")}>
<FormCheckbox <FormCheckbox
name="safe-import" hint={t("import.safeImportTooltip")} label={t("import.safeImport")} name="safe-import" hint={t("import.safeImportTooltip")} label={t("import.safeImport")}
currentValue={safeImport} onChange={setSafeImport} currentValue={safeImport} onChange={setSafeImport}
@ -84,7 +84,7 @@ function ImportDialogComponent() {
name="replace-underscores-with-spaces" label={t("import.replaceUnderscoresWithSpaces")} name="replace-underscores-with-spaces" label={t("import.replaceUnderscoresWithSpaces")}
currentValue={replaceUnderscoresWithSpaces} onChange={setReplaceUnderscoresWithSpaces} currentValue={replaceUnderscoresWithSpaces} onChange={setReplaceUnderscoresWithSpaces}
/> />
</FormGroup> </FormMultiGroup>
</Modal> </Modal>
); );
} }

View File

@ -43,7 +43,7 @@ function IncludeNoteDialogComponent() {
footer={<Button text={t("include_note.button_include")} keyboardShortcut="Enter" />} footer={<Button text={t("include_note.button_include")} keyboardShortcut="Enter" />}
show={shown} show={shown}
> >
<FormGroup label={t("include_note.label_note")}> <FormGroup name="note" label={t("include_note.label_note")}>
<NoteAutocomplete <NoteAutocomplete
placeholder={t("include_note.placeholder_search")} placeholder={t("include_note.placeholder_search")}
onChange={setSuggestion} onChange={setSuggestion}
@ -55,8 +55,9 @@ function IncludeNoteDialogComponent() {
/> />
</FormGroup> </FormGroup>
<FormGroup label={t("include_note.box_size_prompt")}> <FormGroup name="include-note-box-size" label={t("include_note.box_size_prompt")}>
<FormRadioGroup name="include-note-box-size" <FormRadioGroup
name="include-note-box-size"
currentValue={boxSize} onChange={setBoxSize} currentValue={boxSize} onChange={setBoxSize}
values={[ values={[
{ label: t("include_note.box_size_small"), value: "small" }, { label: t("include_note.box_size_small"), value: "small" },

View File

@ -57,7 +57,7 @@ function MoveToDialogComponent() {
<h5>{t("move_to.notes_to_move")}</h5> <h5>{t("move_to.notes_to_move")}</h5>
<NoteList branchIds={movedBranchIds} /> <NoteList branchIds={movedBranchIds} />
<FormGroup label={t("move_to.target_parent_note")}> <FormGroup name="parent-note" label={t("move_to.target_parent_note")}>
<NoteAutocomplete <NoteAutocomplete
onChange={setSuggestion} onChange={setSuggestion}
inputRef={autoCompleteRef} inputRef={autoCompleteRef}

View File

@ -83,7 +83,7 @@ function NoteTypeChooserDialogComponent() {
show={shown} show={shown}
stackable stackable
> >
<FormGroup label={t("note_type_chooser.change_path_prompt")}> <FormGroup name="parent-note" label={t("note_type_chooser.change_path_prompt")}>
<NoteAutocomplete <NoteAutocomplete
onChange={setParentNote} onChange={setParentNote}
placeholder={t("note_type_chooser.search_placeholder")} placeholder={t("note_type_chooser.search_placeholder")}
@ -95,7 +95,7 @@ function NoteTypeChooserDialogComponent() {
/> />
</FormGroup> </FormGroup>
<FormGroup label={t("note_type_chooser.modal_body")}> <FormGroup name="note-type" label={t("note_type_chooser.modal_body")}>
<FormList onSelect={onNoteTypeSelected}> <FormList onSelect={onNoteTypeSelected}>
{noteTypes.map((_item) => { {noteTypes.map((_item) => {
if (_item.title === "----") { if (_item.title === "----") {

View File

@ -74,9 +74,8 @@ function PromptDialogComponent() {
show={shown} show={shown}
stackable stackable
> >
<FormGroup label={opts.current?.message} labelRef={labelRef}> <FormGroup name="prompt-dialog-answer" label={opts.current?.message} labelRef={labelRef}>
<FormTextBox <FormTextBox
name="prompt-dialog-answer"
inputRef={answerRef} inputRef={answerRef}
currentValue={value} onChange={setValue} currentValue={value} onChange={setValue}
readOnly={opts.current?.readOnly} readOnly={opts.current?.readOnly}

View File

@ -83,11 +83,8 @@ function SortChildNotesDialogComponent() {
label={t("sort_child_notes.sort_with_respect_to_different_character_sorting")} label={t("sort_child_notes.sort_with_respect_to_different_character_sorting")}
currentValue={sortNatural} onChange={setSortNatural} currentValue={sortNatural} onChange={setSortNatural}
/> />
<FormGroup className="form-check" label={t("sort_child_notes.natural_sort_language")} description={t("sort_child_notes.the_language_code_for_natural_sort")}> <FormGroup name="sort-locale" className="form-check" label={t("sort_child_notes.natural_sort_language")} description={t("sort_child_notes.the_language_code_for_natural_sort")}>
<FormTextBox <FormTextBox currentValue={sortLocale} onChange={setSortLocale} />
name="sort-locale"
currentValue={sortLocale} onChange={setSortLocale}
/>
</FormGroup> </FormGroup>
</Modal> </Modal>
) )

View File

@ -51,13 +51,12 @@ function UploadAttachmentsDialogComponent() {
onHidden={() => setShown(false)} onHidden={() => setShown(false)}
show={shown} show={shown}
> >
<FormGroup label={t("upload_attachments.choose_files")} description={description}> <FormGroup name="files" label={t("upload_attachments.choose_files")} description={description}>
<FormFileUpload onChange={setFiles} multiple /> <FormFileUpload onChange={setFiles} multiple />
</FormGroup> </FormGroup>
<FormGroup label={t("upload_attachments.options")}> <FormGroup name="shrink-images" label={t("upload_attachments.options")}>
<FormCheckbox <FormCheckbox
name="shrink-images"
hint={t("upload_attachments.tooltip")} label={t("upload_attachments.shrink_images")} hint={t("upload_attachments.tooltip")} label={t("upload_attachments.shrink_images")}
currentValue={shrinkImages} onChange={setShrinkImages} currentValue={shrinkImages} onChange={setShrinkImages}
/> />

View File

@ -3,9 +3,11 @@ import { useEffect, useRef, useMemo, useCallback } from "preact/hooks";
import { escapeQuotes } from "../../services/utils"; import { escapeQuotes } from "../../services/utils";
import { ComponentChildren } from "preact"; import { ComponentChildren } from "preact";
import { CSSProperties, memo } from "preact/compat"; import { CSSProperties, memo } from "preact/compat";
import { useUniqueName } from "./hooks";
interface FormCheckboxProps { interface FormCheckboxProps {
id?: string; id?: string;
name?: string;
label: string | ComponentChildren; label: string | ComponentChildren;
/** /**
* If set, the checkbox label will be underlined and dotted, indicating a hint. When hovered, it will show the hint text. * If set, the checkbox label will be underlined and dotted, indicating a hint. When hovered, it will show the hint text.
@ -17,7 +19,8 @@ interface FormCheckboxProps {
containerStyle?: CSSProperties; containerStyle?: CSSProperties;
} }
const FormCheckbox = memo(({ id, disabled, label, currentValue, onChange, hint, containerStyle }: FormCheckboxProps) => { const FormCheckbox = memo(({ name, id: _id, disabled, label, currentValue, onChange, hint, containerStyle }: FormCheckboxProps) => {
const id = _id ?? useUniqueName(name);
const labelRef = useRef<HTMLLabelElement>(null); const labelRef = useRef<HTMLLabelElement>(null);
// Fix: Move useEffect outside conditional // Fix: Move useEffect outside conditional

View File

@ -28,4 +28,16 @@ export default function FormGroup({ name, label, title, className, children, des
{description && <small className="form-text">{description}</small>} {description && <small className="form-text">{description}</small>}
</div> </div>
); );
}
/**
* Similar to {@link FormGroup} but allows more than one child. Due to this behaviour, there is no automatic ID assignment.
*/
export function FormMultiGroup({ label, children }: { label: string, children: ComponentChildren }) {
return (
<div className={`form-group`}>
{label && <label>{label}</label>}
{children}
</div>
);
} }

View File

@ -31,9 +31,9 @@ export default function FormRadioGroup({ values, ...restProps }: FormRadioProps)
export function FormInlineRadioGroup({ values, ...restProps }: FormRadioProps) { export function FormInlineRadioGroup({ values, ...restProps }: FormRadioProps) {
return ( return (
<> <div role="group">
{values.map(({ value, label }) => (<FormRadio value={value} label={label} {...restProps} />))} {values.map(({ value, label }) => (<FormRadio value={value} label={label} {...restProps} />))}
</> </div>
) )
} }

View File

@ -6,6 +6,7 @@ import type { RefObject } from "preact";
import type { CSSProperties } from "preact/compat"; import type { CSSProperties } from "preact/compat";
interface NoteAutocompleteProps { interface NoteAutocompleteProps {
id?: string;
inputRef?: RefObject<HTMLInputElement>; inputRef?: RefObject<HTMLInputElement>;
text?: string; text?: string;
placeholder?: string; placeholder?: string;
@ -18,7 +19,7 @@ interface NoteAutocompleteProps {
noteId?: string; noteId?: string;
} }
export default function NoteAutocomplete({ inputRef: _ref, text, placeholder, onChange, onTextChange, container, containerStyle, opts, noteId, noteIdChanged }: NoteAutocompleteProps) { export default function NoteAutocomplete({ id, inputRef: _ref, text, placeholder, onChange, onTextChange, container, containerStyle, opts, noteId, noteIdChanged }: NoteAutocompleteProps) {
const ref = _ref ?? useRef<HTMLInputElement>(null); const ref = _ref ?? useRef<HTMLInputElement>(null);
useEffect(() => { useEffect(() => {
@ -74,6 +75,7 @@ export default function NoteAutocomplete({ inputRef: _ref, text, placeholder, on
return ( return (
<div className="input-group" style={containerStyle}> <div className="input-group" style={containerStyle}>
<input <input
id={id}
ref={ref} ref={ref}
className="note-autocomplete form-control" className="note-autocomplete form-control"
placeholder={placeholder ?? t("add_link.search_note")} /> placeholder={placeholder ?? t("add_link.search_note")} />

View File

@ -190,6 +190,6 @@ export function useTriliumOptions<T extends OptionNames>(...names: T[]) {
* @param prefix a prefix to add to the unique name. * @param prefix a prefix to add to the unique name.
* @returns a name with the given prefix and a random alpanumeric string appended to it. * @returns a name with the given prefix and a random alpanumeric string appended to it.
*/ */
export function useUniqueName(prefix: string) { export function useUniqueName(prefix?: string) {
return useMemo(() => prefix + "-" + utils.randomString(10), [ prefix]); return useMemo(() => (prefix ? prefix + "-" : "") + utils.randomString(10), [ prefix ]);
} }

View File

@ -28,9 +28,8 @@ function EnableAiSettings() {
return ( return (
<> <>
<OptionsSection title={t("ai_llm.title")}> <OptionsSection title={t("ai_llm.title")}>
<FormGroup description={t("ai_llm.enable_ai_description")}> <FormGroup name="ai-enabled" description={t("ai_llm.enable_ai_description")}>
<FormCheckbox <FormCheckbox
name="ai-enabled"
label={t("ai_llm.enable_ai_features")} label={t("ai_llm.enable_ai_features")}
currentValue={aiEnabled} onChange={(isEnabled) => { currentValue={aiEnabled} onChange={(isEnabled) => {
if (isEnabled) { if (isEnabled) {
@ -56,7 +55,7 @@ function ProviderSettings() {
return ( return (
<OptionsSection title={t("ai_llm.provider_configuration")}> <OptionsSection title={t("ai_llm.provider_configuration")}>
<FormGroup label={t("ai_llm.selected_provider")} description={t("ai_llm.selected_provider_description")}> <FormGroup name="selected-provider" label={t("ai_llm.selected_provider")} description={t("ai_llm.selected_provider_description")}>
<FormSelect <FormSelect
values={[ values={[
{ value: "", text: t("ai_llm.select_provider") }, { value: "", text: t("ai_llm.select_provider") },
@ -103,15 +102,14 @@ function ProviderSettings() {
<></> <></>
} }
<FormGroup label={t("ai_llm.temperature")} description={t("ai_llm.temperature_description")}> <FormGroup name="ai-temperature" label={t("ai_llm.temperature")} description={t("ai_llm.temperature_description")}>
<FormTextBox <FormTextBox
name="ai-temperature"
type="number" min="0" max="2" step="0.1" type="number" min="0" max="2" step="0.1"
currentValue={aiTemperature} onChange={setAiTemperature} currentValue={aiTemperature} onChange={setAiTemperature}
/> />
</FormGroup> </FormGroup>
<FormGroup label={t("ai_llm.system_prompt")} description={t("ai_llm.system_prompt_description")}> <FormGroup name="system-prompt" label={t("ai_llm.system_prompt")} description={t("ai_llm.system_prompt_description")}>
<FormTextArea <FormTextArea
rows={3} rows={3}
currentValue={aiSystemPrompt} onBlur={setAiSystemPrompt} currentValue={aiSystemPrompt} onBlur={setAiSystemPrompt}
@ -149,7 +147,7 @@ function SingleProviderSettings({ provider, title, apiKeyDescription, baseUrlDes
{!isValid && <Admonition type="caution">{validationErrorMessage}</Admonition> } {!isValid && <Admonition type="caution">{validationErrorMessage}</Admonition> }
{apiKeyOption && ( {apiKeyOption && (
<FormGroup label={t("ai_llm.api_key")} description={apiKeyDescription}> <FormGroup name="api-key" label={t("ai_llm.api_key")} description={apiKeyDescription}>
<FormTextBox <FormTextBox
type="password" autoComplete="off" type="password" autoComplete="off"
currentValue={apiKey} onChange={setApiKey} currentValue={apiKey} onChange={setApiKey}
@ -157,14 +155,14 @@ function SingleProviderSettings({ provider, title, apiKeyDescription, baseUrlDes
</FormGroup> </FormGroup>
)} )}
<FormGroup label={t("ai_llm.url")} description={baseUrlDescription}> <FormGroup name="base-url" label={t("ai_llm.url")} description={baseUrlDescription}>
<FormTextBox <FormTextBox
currentValue={baseUrl ?? "https://api.openai.com/v1"} onChange={setBaseUrl} currentValue={baseUrl ?? "https://api.openai.com/v1"} onChange={setBaseUrl}
/> />
</FormGroup> </FormGroup>
{isValid && {isValid &&
<FormGroup label={t("ai_llm.model")} description={modelDescription}> <FormGroup name="model" label={t("ai_llm.model")} description={modelDescription}>
<ModelSelector provider={provider} baseUrl={baseUrl} modelOption={modelOption} /> <ModelSelector provider={provider} baseUrl={baseUrl} modelOption={modelOption} />
</FormGroup> </FormGroup>
} }

View File

@ -189,7 +189,7 @@ function Font({ title, fontFamilyOption, fontSizeOption }: { title: string, font
<> <>
<h5>{title}</h5> <h5>{title}</h5>
<div className="row"> <div className="row">
<FormGroup className="col-md-4" label={t("fonts.font_family")}> <FormGroup name="font-family" className="col-md-4" label={t("fonts.font_family")}>
<FormSelectWithGroups <FormSelectWithGroups
values={FONT_FAMILIES} values={FONT_FAMILIES}
currentValue={fontFamily} onChange={setFontFamily} currentValue={fontFamily} onChange={setFontFamily}
@ -197,7 +197,7 @@ function Font({ title, fontFamilyOption, fontSizeOption }: { title: string, font
/> />
</FormGroup> </FormGroup>
<FormGroup className="col-md-6" label={t("fonts.size")}> <FormGroup name="font-size" className="col-md-6" label={t("fonts.size")}>
<FormTextBoxWithUnit <FormTextBoxWithUnit
name="tree-font-size" name="tree-font-size"
type="number" min={50} max={200} step={10} type="number" min={50} max={200} step={10}
@ -217,7 +217,7 @@ function ElectronIntegration() {
return ( return (
<OptionsSection title={t("electron_integration.desktop-application")}> <OptionsSection title={t("electron_integration.desktop-application")}>
<FormGroup label={t("electron_integration.zoom-factor")} description={t("zoom_factor.description")}> <FormGroup name="zoom-factor" label={t("electron_integration.zoom-factor")} description={t("zoom_factor.description")}>
<FormTextBox <FormTextBox
type="number" type="number"
min="0.3" max="2.0" step="0.1" min="0.3" max="2.0" step="0.1"
@ -226,16 +226,16 @@ function ElectronIntegration() {
</FormGroup> </FormGroup>
<hr/> <hr/>
<FormGroup description={t("electron_integration.native-title-bar-description")}> <FormGroup name="native-title-bar" description={t("electron_integration.native-title-bar-description")}>
<FormCheckbox <FormCheckbox
name="native-title-bar" label={t("electron_integration.native-title-bar")} label={t("electron_integration.native-title-bar")}
currentValue={nativeTitleBarVisible} onChange={setNativeTitleBarVisible} currentValue={nativeTitleBarVisible} onChange={setNativeTitleBarVisible}
/> />
</FormGroup> </FormGroup>
<FormGroup description={t("electron_integration.background-effects-description")}> <FormGroup name="background-effects" description={t("electron_integration.background-effects-description")}>
<FormCheckbox <FormCheckbox
name="background-effects" label={t("electron_integration.background-effects")} label={t("electron_integration.background-effects")}
currentValue={backgroundEffects} onChange={setBackgroundEffects} currentValue={backgroundEffects} onChange={setBackgroundEffects}
/> />
</FormGroup> </FormGroup>
@ -253,9 +253,8 @@ function MaxContentWidth() {
<FormText>{t("max_content_width.default_description")}</FormText> <FormText>{t("max_content_width.default_description")}</FormText>
<Column md={6}> <Column md={6}>
<FormGroup label={t("max_content_width.max_width_label")}> <FormGroup name="max-content-width" label={t("max_content_width.max_width_label")}>
<FormTextBoxWithUnit <FormTextBoxWithUnit
name="max-content-width"
type="number" min={MIN_CONTENT_WIDTH} step="10" type="number" min={MIN_CONTENT_WIDTH} step="10"
currentValue={maxContentWidth} onChange={setMaxContentWidth} currentValue={maxContentWidth} onChange={setMaxContentWidth}
unit={t("max_content_width.max_width_unit")} unit={t("max_content_width.max_width_unit")}

View File

@ -4,7 +4,7 @@ import server from "../../../services/server";
import toast from "../../../services/toast"; import toast from "../../../services/toast";
import Button from "../../react/Button"; import Button from "../../react/Button";
import FormCheckbox from "../../react/FormCheckbox"; import FormCheckbox from "../../react/FormCheckbox";
import FormGroup from "../../react/FormGroup"; import FormGroup, { FormMultiGroup } from "../../react/FormGroup";
import FormText from "../../react/FormText"; import FormText from "../../react/FormText";
import { useTriliumOptionBool } from "../../react/hooks"; import { useTriliumOptionBool } from "../../react/hooks";
import OptionsSection from "./components/OptionsSection"; import OptionsSection from "./components/OptionsSection";
@ -45,7 +45,7 @@ export function AutomaticBackup() {
return ( return (
<OptionsSection title={t("backup.automatic_backup")}> <OptionsSection title={t("backup.automatic_backup")}>
<FormGroup label={t("backup.automatic_backup_description")}> <FormMultiGroup label={t("backup.automatic_backup_description")}>
<FormCheckbox <FormCheckbox
name="daily-backup-enabled" name="daily-backup-enabled"
label={t("backup.enable_daily_backup")} label={t("backup.enable_daily_backup")}
@ -63,7 +63,7 @@ export function AutomaticBackup() {
label={t("backup.enable_monthly_backup")} label={t("backup.enable_monthly_backup")}
currentValue={monthlyBackupEnabled} onChange={setMonthlyBackupEnabled} currentValue={monthlyBackupEnabled} onChange={setMonthlyBackupEnabled}
/> />
</FormGroup> </FormMultiGroup>
<FormText>{t("backup.backup_recommendation")}</FormText> <FormText>{t("backup.backup_recommendation")}</FormText>
</OptionsSection> </OptionsSection>

View File

@ -32,9 +32,8 @@ function Editor() {
return ( return (
<OptionsSection title={t("code-editor-options.title")}> <OptionsSection title={t("code-editor-options.title")}>
<FormGroup description={t("vim_key_bindings.enable_vim_keybindings")}> <FormGroup name="vim-keymap-enabled" description={t("vim_key_bindings.enable_vim_keybindings")}>
<FormCheckbox <FormCheckbox
name="vim-keymap-enabled"
label={t("vim_key_bindings.use_vim_keybindings_in_code_notes")} label={t("vim_key_bindings.use_vim_keybindings_in_code_notes")}
currentValue={vimKeymapEnabled} onChange={setVimKeymapEnabled} currentValue={vimKeymapEnabled} onChange={setVimKeymapEnabled}
/> />
@ -57,7 +56,7 @@ function Appearance() {
return ( return (
<OptionsSection title={t("code_theme.title")}> <OptionsSection title={t("code_theme.title")}>
<div className="row" style={{ marginBottom: "15px" }}> <div className="row" style={{ marginBottom: "15px" }}>
<FormGroup label={t("code_theme.color-scheme")} className="col-md-6" style={{ marginBottom: 0 }}> <FormGroup name="color-scheme" label={t("code_theme.color-scheme")} className="col-md-6" style={{ marginBottom: 0 }}>
<FormSelect <FormSelect
values={themes} values={themes}
keyProperty="id" titleProperty="name" keyProperty="id" titleProperty="name"

View File

@ -18,9 +18,8 @@ export default function AutoReadOnlySize({ label, option }: AutoReadOnlySizeProp
<OptionsSection title={t("text_auto_read_only_size.title")}> <OptionsSection title={t("text_auto_read_only_size.title")}>
<FormText>{t("text_auto_read_only_size.description")}</FormText> <FormText>{t("text_auto_read_only_size.description")}</FormText>
<FormGroup label={label}> <FormGroup name="auto-readonly-size-text" label={label}>
<FormTextBoxWithUnit <FormTextBoxWithUnit
name="auto-readonly-size-text"
type="number" min={0} type="number" min={0}
unit={t("text_auto_read_only_size.unit")} unit={t("text_auto_read_only_size.unit")}
currentValue={autoReadonlyOpt} onChange={setAutoReadonlyOpt} currentValue={autoReadonlyOpt} onChange={setAutoReadonlyOpt}

View File

@ -36,11 +36,11 @@ function LocalizationOptions() {
return ( return (
<OptionsSection title={t("i18n.title")}> <OptionsSection title={t("i18n.title")}>
<OptionsRow label={t("i18n.language")}> <OptionsRow name="language" label={t("i18n.language")}>
<LocaleSelector locales={uiLocales} currentValue={locale} onChange={setLocale} /> <LocaleSelector locales={uiLocales} currentValue={locale} onChange={setLocale} />
</OptionsRow> </OptionsRow>
{isElectron() && <OptionsRow label={t("i18n.formatting-locale")}> {isElectron() && <OptionsRow name="formatting-locale" label={t("i18n.formatting-locale")}>
<LocaleSelector locales={contentLocales} currentValue={formattingLocale} onChange={setFormattingLocale} /> <LocaleSelector locales={contentLocales} currentValue={formattingLocale} onChange={setFormattingLocale} />
</OptionsRow>} </OptionsRow>}
@ -65,34 +65,30 @@ function DateSettings() {
return ( return (
<> <>
<OptionsRow label={t("i18n.first-day-of-the-week")}> <OptionsRow name="first-day-of-week" label={t("i18n.first-day-of-the-week")}>
<div role="group"> <FormInlineRadioGroup
<FormInlineRadioGroup name="first-day-of-week"
name="first-day-of-week" values={[
values={[ { value: "0", label: t("i18n.sunday") },
{ value: "0", label: t("i18n.sunday") }, { value: "1", label: t("i18n.monday") }
{ value: "1", label: t("i18n.monday") } ]}
]} currentValue={firstDayOfWeek} onChange={setFirstDayOfWeek}
currentValue={firstDayOfWeek} onChange={setFirstDayOfWeek} />
/>
</div>
</OptionsRow> </OptionsRow>
<OptionsRow label={t("i18n.first-week-of-the-year")}> <OptionsRow name="first-week-of-year" label={t("i18n.first-week-of-the-year")}>
<div role="group"> <FormRadioGroup
<FormRadioGroup name="first-week-of-year"
name="first-week-of-year" currentValue={firstWeekOfYear} onChange={setFirstWeekOfYear}
currentValue={firstWeekOfYear} onChange={setFirstWeekOfYear} values={[
values={[ { value: "0", label: t("i18n.first-week-contains-first-day") },
{ value: "0", label: t("i18n.first-week-contains-first-day") }, { value: "1", label: t("i18n.first-week-contains-first-thursday") },
{ value: "1", label: t("i18n.first-week-contains-first-thursday") }, { value: "2", label: t("i18n.first-week-has-minimum-days") }
{ value: "2", label: t("i18n.first-week-has-minimum-days") } ]}
]} />
/>
</div>
</OptionsRow> </OptionsRow>
{firstWeekOfYear === "2" && <OptionsRow label={t("i18n.min-days-in-first-week")}> {firstWeekOfYear === "2" && <OptionsRow name="min-days-in-first-week" label={t("i18n.min-days-in-first-week")}>
<FormSelect <FormSelect
keyProperty="days" keyProperty="days"
currentValue={minDaysInFirstWeek} onChange={setMinDaysInFirstWeek} currentValue={minDaysInFirstWeek} onChange={setMinDaysInFirstWeek}
@ -109,7 +105,7 @@ function DateSettings() {
{t("i18n.first-week-warning")} {t("i18n.first-week-warning")}
</Admonition> </Admonition>
<OptionsRow centered> <OptionsRow name="restart" centered>
<Button <Button
text={t("electron_integration.restart-app-button")} text={t("electron_integration.restart-app-button")}
size="micro" size="micro"

View File

@ -13,9 +13,8 @@ export default function ImageSettings() {
return ( return (
<OptionsSection title={t("images.images_section_title")}> <OptionsSection title={t("images.images_section_title")}>
<FormGroup description={t("images.download_images_description")}> <FormGroup name="download-images-automatically" description={t("images.download_images_description")}>
<FormCheckbox <FormCheckbox
name="download-images-automatically"
label={t("images.download_images_automatically")} label={t("images.download_images_automatically")}
currentValue={downloadImagesAutomatically} onChange={setDownloadImagesAutomatically} currentValue={downloadImagesAutomatically} onChange={setDownloadImagesAutomatically}
/> />
@ -29,18 +28,16 @@ export default function ImageSettings() {
currentValue={compressImages} onChange={setCompressImages} currentValue={compressImages} onChange={setCompressImages}
/> />
<FormGroup label={t("images.max_image_dimensions")} disabled={!compressImages}> <FormGroup name="image-max-width-height" label={t("images.max_image_dimensions")} disabled={!compressImages}>
<FormTextBoxWithUnit <FormTextBoxWithUnit
name="image-max-width-height"
type="number" min="1" type="number" min="1"
unit={t("images.max_image_dimensions_unit")} unit={t("images.max_image_dimensions_unit")}
currentValue={imageMaxWidthHeight} onChange={setImageMaxWidthHeight} currentValue={imageMaxWidthHeight} onChange={setImageMaxWidthHeight}
/> />
</FormGroup> </FormGroup>
<FormGroup label={t("images.jpeg_quality_description")} disabled={!compressImages}> <FormGroup name="image-jpeg-quality" label={t("images.jpeg_quality_description")} disabled={!compressImages}>
<FormTextBoxWithUnit <FormTextBoxWithUnit
name="image-jpeg-quality"
min="10" max="100" type="number" min="10" max="100" type="number"
unit={t("units.percentage")} unit={t("units.percentage")}
currentValue={imageJpegQuality} onChange={setImageJpegQuality} currentValue={imageJpegQuality} onChange={setImageJpegQuality}

View File

@ -4,7 +4,6 @@ import FormText from "../../react/FormText"
import OptionsSection from "./components/OptionsSection" import OptionsSection from "./components/OptionsSection"
import FormCheckbox from "../../react/FormCheckbox" import FormCheckbox from "../../react/FormCheckbox"
import { useTriliumOption, useTriliumOptionBool } from "../../react/hooks" import { useTriliumOption, useTriliumOptionBool } from "../../react/hooks"
import FormGroup from "../../react/FormGroup"
import { FormInlineRadioGroup } from "../../react/FormRadioGroup" import { FormInlineRadioGroup } from "../../react/FormRadioGroup"
import Admonition from "../../react/Admonition" import Admonition from "../../react/Admonition"
import { useCallback, useEffect, useMemo, useState } from "preact/hooks" import { useCallback, useEffect, useMemo, useState } from "preact/hooks"

View File

@ -7,7 +7,7 @@ import FormText from "../../react/FormText";
import OptionsSection from "./components/OptionsSection"; import OptionsSection from "./components/OptionsSection";
import TimeSelector from "./components/TimeSelector"; import TimeSelector from "./components/TimeSelector";
import { useMemo } from "preact/hooks"; import { useMemo } from "preact/hooks";
import { useTriliumOption, useTriliumOptionBool, useTriliumOptionInt, useTriliumOptionJson } from "../../react/hooks"; import { useTriliumOption, useTriliumOptionBool, useTriliumOptionJson } from "../../react/hooks";
import { SANITIZER_DEFAULT_ALLOWED_TAGS } from "@triliumnext/commons"; import { SANITIZER_DEFAULT_ALLOWED_TAGS } from "@triliumnext/commons";
import FormCheckbox from "../../react/FormCheckbox"; import FormCheckbox from "../../react/FormCheckbox";
import FormGroup from "../../react/FormGroup"; import FormGroup from "../../react/FormGroup";
@ -51,7 +51,7 @@ function SearchEngineSettings() {
<OptionsSection title={t("search_engine.title")}> <OptionsSection title={t("search_engine.title")}>
<FormText>{t("search_engine.custom_search_engine_info")}</FormText> <FormText>{t("search_engine.custom_search_engine_info")}</FormText>
<FormGroup label={t("search_engine.predefined_templates_label")}> <FormGroup name="predefined-search-engine" label={t("search_engine.predefined_templates_label")}>
<FormSelect <FormSelect
values={searchEngines} values={searchEngines}
currentValue={customSearchEngineUrl} currentValue={customSearchEngineUrl}
@ -68,14 +68,14 @@ function SearchEngineSettings() {
/> />
</FormGroup> </FormGroup>
<FormGroup label={t("search_engine.custom_name_label")}> <FormGroup name="custom-name" label={t("search_engine.custom_name_label")}>
<FormTextBox <FormTextBox
currentValue={customSearchEngineName} onChange={setCustomSearchEngineName} currentValue={customSearchEngineName} onChange={setCustomSearchEngineName}
placeholder={t("search_engine.custom_name_placeholder")} placeholder={t("search_engine.custom_name_placeholder")}
/> />
</FormGroup> </FormGroup>
<FormGroup label={t("search_engine.custom_url_label")}> <FormGroup name="custom-url" label={t("search_engine.custom_url_label")}>
<FormTextBox <FormTextBox
currentValue={customSearchEngineUrl} onChange={setCustomSearchEngineUrl} currentValue={customSearchEngineUrl} onChange={setCustomSearchEngineUrl}
placeholder={t("search_engine.custom_url_placeholder")} placeholder={t("search_engine.custom_url_placeholder")}
@ -104,9 +104,9 @@ function NoteErasureTimeout() {
return ( return (
<OptionsSection title={t("note_erasure_timeout.note_erasure_timeout_title")}> <OptionsSection title={t("note_erasure_timeout.note_erasure_timeout_title")}>
<FormText>{t("note_erasure_timeout.note_erasure_description")}</FormText> <FormText>{t("note_erasure_timeout.note_erasure_description")}</FormText>
<FormGroup label={t("note_erasure_timeout.erase_notes_after")}> <FormGroup name="erase-entities-after" label={t("note_erasure_timeout.erase_notes_after")}>
<TimeSelector <TimeSelector
name="erase-entities-after" name="erase-entities-after"
optionValueId="eraseEntitiesAfterTimeInSeconds" optionTimeScaleId="eraseEntitiesAfterTimeScale" optionValueId="eraseEntitiesAfterTimeInSeconds" optionTimeScaleId="eraseEntitiesAfterTimeScale"
/> />
</FormGroup> </FormGroup>
@ -128,9 +128,9 @@ function AttachmentErasureTimeout() {
return ( return (
<OptionsSection title={t("attachment_erasure_timeout.attachment_erasure_timeout")}> <OptionsSection title={t("attachment_erasure_timeout.attachment_erasure_timeout")}>
<FormText>{t("attachment_erasure_timeout.attachment_auto_deletion_description")}</FormText> <FormText>{t("attachment_erasure_timeout.attachment_auto_deletion_description")}</FormText>
<FormGroup label={t("attachment_erasure_timeout.erase_attachments_after")}> <FormGroup name="erase-unused-attachments-after" label={t("attachment_erasure_timeout.erase_attachments_after")}>
<TimeSelector <TimeSelector
name="erase-unused-attachments-after" name="erase-unused-attachments-after"
optionValueId="eraseUnusedAttachmentsAfterSeconds" optionTimeScaleId="eraseUnusedAttachmentsAfterTimeScale" optionValueId="eraseUnusedAttachmentsAfterSeconds" optionTimeScaleId="eraseUnusedAttachmentsAfterTimeScale"
/> />
</FormGroup> </FormGroup>
@ -157,7 +157,7 @@ function RevisionSnapshotInterval() {
components={{ doc: <a href="https://triliumnext.github.io/Docs/Wiki/note-revisions.html" class="external" />}} components={{ doc: <a href="https://triliumnext.github.io/Docs/Wiki/note-revisions.html" class="external" />}}
/> />
</FormText> </FormText>
<FormGroup label={t("revisions_snapshot_interval.snapshot_time_interval_label")}> <FormGroup name="revision-snapshot-time-interval" label={t("revisions_snapshot_interval.snapshot_time_interval_label")}>
<TimeSelector <TimeSelector
name="revision-snapshot-time-interval" name="revision-snapshot-time-interval"
optionValueId="revisionSnapshotTimeInterval" optionTimeScaleId="revisionSnapshotTimeIntervalTimeScale" optionValueId="revisionSnapshotTimeInterval" optionTimeScaleId="revisionSnapshotTimeIntervalTimeScale"
@ -175,9 +175,8 @@ function RevisionSnapshotLimit() {
<OptionsSection title={t("revisions_snapshot_limit.note_revisions_snapshot_limit_title")}> <OptionsSection title={t("revisions_snapshot_limit.note_revisions_snapshot_limit_title")}>
<FormText>{t("revisions_snapshot_limit.note_revisions_snapshot_limit_description")}</FormText> <FormText>{t("revisions_snapshot_limit.note_revisions_snapshot_limit_description")}</FormText>
<FormGroup> <FormGroup name="revision-snapshot-number-limit">
<FormTextBoxWithUnit <FormTextBoxWithUnit
name="revision-snapshot-number-limit"
type="number" min={-1} type="number" min={-1}
currentValue={revisionSnapshotNumberLimit} currentValue={revisionSnapshotNumberLimit}
unit={t("revisions_snapshot_limit.snapshot_number_limit_unit")} unit={t("revisions_snapshot_limit.snapshot_number_limit_unit")}
@ -246,9 +245,8 @@ function ShareSettings() {
return ( return (
<OptionsSection title={t("share.title")}> <OptionsSection title={t("share.title")}>
<FormGroup description={t("share.redirect_bare_domain_description")}> <FormGroup name="redirectBareDomain" description={t("share.redirect_bare_domain_description")}>
<FormCheckbox <FormCheckbox
name="redirectBareDomain"
label={t(t("share.redirect_bare_domain"))} label={t(t("share.redirect_bare_domain"))}
currentValue={redirectBareDomain} currentValue={redirectBareDomain}
onChange={async value => { onChange={async value => {
@ -269,9 +267,8 @@ function ShareSettings() {
/> />
</FormGroup> </FormGroup>
<FormGroup description={t("share.show_login_link_description")}> <FormGroup name="showLoginInShareTheme" description={t("share.show_login_link_description")}>
<FormCheckbox <FormCheckbox
name="showLoginInShareTheme"
label={t("share.show_login_link")} label={t("share.show_login_link")}
currentValue={showLogInShareTheme} onChange={setShowLogInShareTheme} currentValue={showLogInShareTheme} onChange={setShowLogInShareTheme}
/> />

View File

@ -72,25 +72,22 @@ function ChangePassword() {
toast.showError(result.message); toast.showError(result.message);
} }
}}> }}>
<FormGroup label={t("password.old_password")}> <FormGroup name="old-password" label={t("password.old_password")}>
<FormTextBox <FormTextBox
name="old-password"
type="password" type="password"
currentValue={oldPassword} onChange={setOldPassword} currentValue={oldPassword} onChange={setOldPassword}
/> />
</FormGroup> </FormGroup>
<FormGroup label={t("password.new_password")}> <FormGroup name="new-password1" label={t("password.new_password")}>
<FormTextBox <FormTextBox
name="new-password1"
type="password" type="password"
currentValue={newPassword1} onChange={setNewPassword1} currentValue={newPassword1} onChange={setNewPassword1}
/> />
</FormGroup> </FormGroup>
<FormGroup label={t("password.new_password_confirmation")}> <FormGroup name="new-password2" label={t("password.new_password_confirmation")}>
<FormTextBox <FormTextBox
name="new-password2"
type="password" type="password"
currentValue={newPassword2} onChange={setNewPassword2} currentValue={newPassword2} onChange={setNewPassword2}
/> />
@ -114,9 +111,9 @@ function ProtectedSessionTimeout() {
<a class="tn-link" href="https://triliumnext.github.io/Docs/Wiki/protected-notes.html" className="external">{t("password.wiki")}</a> {t("password.for_more_info")} <a class="tn-link" href="https://triliumnext.github.io/Docs/Wiki/protected-notes.html" className="external">{t("password.wiki")}</a> {t("password.for_more_info")}
</FormText> </FormText>
<FormGroup label={t("password.protected_session_timeout_label")}> <FormGroup name="protected-session-timeout" label={t("password.protected_session_timeout_label")}>
<TimeSelector <TimeSelector
name="protected-session-timeout" name="protected-session-timeout"
optionValueId="protectedSessionTimeout" optionTimeScaleId="protectedSessionTimeoutTimeScale" optionValueId="protectedSessionTimeout" optionTimeScaleId="protectedSessionTimeoutTimeScale"
minimumSeconds={60} minimumSeconds={60}
/> />

View File

@ -81,9 +81,8 @@ export default function ShortcutSettings() {
<RawHtml html={t("shortcuts.electron_documentation")} /> <RawHtml html={t("shortcuts.electron_documentation")} />
</FormText> </FormText>
<FormGroup> <FormGroup name="keyboard-shortcut-filter">
<FormTextBox <FormTextBox
name="keyboard-shortcut-filter"
placeholder={t("shortcuts.type_text_to_filter")} placeholder={t("shortcuts.type_text_to_filter")}
currentValue={filter} onChange={(value) => setFilter(value.toLowerCase())} currentValue={filter} onChange={(value) => setFilter(value.toLowerCase())}
/> />

View File

@ -39,9 +39,8 @@ function ElectronSpellcheckSettings() {
currentValue={spellCheckEnabled} onChange={setSpellCheckEnabled} currentValue={spellCheckEnabled} onChange={setSpellCheckEnabled}
/> />
<FormGroup label={t("spellcheck.language_code_label")} description={t("spellcheck.multiple_languages_info")}> <FormGroup name="spell-check-languages" label={t("spellcheck.language_code_label")} description={t("spellcheck.multiple_languages_info")}>
<FormTextBox <FormTextBox
name="spell-check-languages"
placeholder={t("spellcheck.language_code_placeholder")} placeholder={t("spellcheck.language_code_placeholder")}
currentValue={spellCheckLanguageCode} onChange={setSpellCheckLanguageCode} currentValue={spellCheckLanguageCode} onChange={setSpellCheckLanguageCode}
/> />

View File

@ -37,28 +37,27 @@ export function SyncConfiguration() {
}); });
e.preventDefault(); e.preventDefault();
}}> }}>
<FormGroup label={t("sync_2.server_address")}> <FormGroup name="sync-server-host" label={t("sync_2.server_address")}>
<FormTextBox <FormTextBox
name="sync-server-host"
placeholder="https://<host>:<port>" placeholder="https://<host>:<port>"
currentValue={syncServerHost.current} onChange={(newValue) => syncServerHost.current = newValue} currentValue={syncServerHost.current} onChange={(newValue) => syncServerHost.current = newValue}
/> />
</FormGroup> </FormGroup>
<FormGroup label={t("sync_2.proxy_label")} description={<> <FormGroup name="sync-proxy" label={t("sync_2.proxy_label")}
<strong>{t("sync_2.note")}:</strong> {t("sync_2.note_description")}<br/> description={<>
<RawHtml html={t("sync_2.special_value_description")} /></>} <strong>{t("sync_2.note")}:</strong> {t("sync_2.note_description")}<br/>
<RawHtml html={t("sync_2.special_value_description")} />
</>}
> >
<FormTextBox <FormTextBox
name="sync-proxy"
placeholder="https://<host>:<port>" placeholder="https://<host>:<port>"
currentValue={syncProxy.current} onChange={(newValue) => syncProxy.current = newValue} currentValue={syncProxy.current} onChange={(newValue) => syncProxy.current = newValue}
/> />
</FormGroup> </FormGroup>
<FormGroup label={t("sync_2.timeout")}> <FormGroup name="sync-server-timeout" label={t("sync_2.timeout")}>
<FormTextBoxWithUnit <FormTextBoxWithUnit
name="sync-server-timeout"
min={1} max={10000000} type="number" min={1} max={10000000} type="number"
unit={t("sync_2.timeout_unit")} unit={t("sync_2.timeout_unit")}
currentValue={syncServerTimeout.current} onChange={(newValue) => syncServerTimeout.current = newValue} currentValue={syncServerTimeout.current} onChange={(newValue) => syncServerTimeout.current = newValue}

View File

@ -154,7 +154,7 @@ function CodeBlockStyle() {
return ( return (
<OptionsSection title={t("highlighting.title")}> <OptionsSection title={t("highlighting.title")}>
<div className="row" style={{ marginBottom: "15px" }}> <div className="row" style={{ marginBottom: "15px" }}>
<FormGroup className="col-md-6" label={t("highlighting.color-scheme")} style={{ marginBottom: 0 }}> <FormGroup name="theme" className="col-md-6" label={t("highlighting.color-scheme")} style={{ marginBottom: 0 }}>
<FormSelectWithGroups <FormSelectWithGroups
values={themes} values={themes}
keyProperty="val" titleProperty="title" keyProperty="val" titleProperty="title"
@ -244,7 +244,7 @@ function TableOfContent() {
<OptionsSection title={t("table_of_contents.title")}> <OptionsSection title={t("table_of_contents.title")}>
<FormText>{t("table_of_contents.description")}</FormText> <FormText>{t("table_of_contents.description")}</FormText>
<FormGroup> <FormGroup name="min-toc-headings">
<FormTextBoxWithUnit <FormTextBoxWithUnit
type="number" type="number"
min={0} max={999999999999999} step={1} min={0} max={999999999999999} step={1}
@ -300,21 +300,20 @@ function DateTimeFormatOptions() {
/> />
</FormText> </FormText>
<FormGroup className="row align-items-center"> <div className="row align-items-center">
<FormGroup className="col-md-6" label={t("custom_date_time_format.format_string")}> <FormGroup name="custom-date-time-format" className="col-md-6" label={t("custom_date_time_format.format_string")}>
<FormTextBox <FormTextBox
name="custom-date-time-format"
placeholder="YYYY-MM-DD HH:mm" placeholder="YYYY-MM-DD HH:mm"
currentValue={customDateTimeFormat || "YYYY-MM-DD HH:mm"} onChange={setCustomDateTimeFormat} currentValue={customDateTimeFormat || "YYYY-MM-DD HH:mm"} onChange={setCustomDateTimeFormat}
/> />
</FormGroup> </FormGroup>
<FormGroup className="col-md-6" label={t("custom_date_time_format.formatted_time")}> <FormGroup name="formatted-date" className="col-md-6" label={t("custom_date_time_format.formatted_time")}>
<div className="formatted-date"> <div>
{formatDateTime(new Date(), customDateTimeFormat)} {formatDateTime(new Date(), customDateTimeFormat)}
</div> </div>
</FormGroup> </FormGroup>
</FormGroup> </div>
</OptionsSection> </OptionsSection>
) )
} }