mirror of
https://github.com/zadam/trilium.git
synced 2025-10-20 15:19:01 +02:00
feat(react/settings): port text formatting toolbar
This commit is contained in:
parent
5614891d92
commit
71b627fbc7
@ -2,7 +2,7 @@ import { Tooltip } from "bootstrap";
|
||||
import { useEffect, useRef, useMemo, useCallback } from "preact/hooks";
|
||||
import { escapeQuotes } from "../../services/utils";
|
||||
import { ComponentChildren } from "preact";
|
||||
import { memo } from "preact/compat";
|
||||
import { CSSProperties, memo } from "preact/compat";
|
||||
|
||||
interface FormCheckboxProps {
|
||||
name: string;
|
||||
@ -14,9 +14,10 @@ interface FormCheckboxProps {
|
||||
currentValue: boolean;
|
||||
disabled?: boolean;
|
||||
onChange(newValue: boolean): void;
|
||||
containerStyle?: CSSProperties;
|
||||
}
|
||||
|
||||
const FormCheckbox = memo(({ name, disabled, label, currentValue, onChange, hint }: FormCheckboxProps) => {
|
||||
const FormCheckbox = memo(({ name, disabled, label, currentValue, onChange, hint, containerStyle }: FormCheckboxProps) => {
|
||||
const labelRef = useRef<HTMLLabelElement>(null);
|
||||
|
||||
// Fix: Move useEffect outside conditional
|
||||
@ -46,7 +47,7 @@ const FormCheckbox = memo(({ name, disabled, label, currentValue, onChange, hint
|
||||
const titleText = useMemo(() => hint ? escapeQuotes(hint) : undefined, [hint]);
|
||||
|
||||
return (
|
||||
<div className="form-checkbox">
|
||||
<div className="form-checkbox" style={containerStyle}>
|
||||
<label
|
||||
className="form-check-label tn-checkbox"
|
||||
style={labelStyle}
|
||||
|
@ -7,6 +7,7 @@ interface FormRadioProps {
|
||||
values: {
|
||||
value: string;
|
||||
label: string | ComponentChildren;
|
||||
inlineDescription?: string | ComponentChildren;
|
||||
}[];
|
||||
onChange(newValue: string): void;
|
||||
}
|
||||
@ -14,9 +15,14 @@ interface FormRadioProps {
|
||||
export default function FormRadioGroup({ values, ...restProps }: FormRadioProps) {
|
||||
return (
|
||||
<>
|
||||
{(values || []).map(({ value, label }) => (
|
||||
{(values || []).map(({ value, label, inlineDescription }) => (
|
||||
<div className="form-checkbox">
|
||||
<FormRadio value={value} label={label} {...restProps} labelClassName="form-check-label" />
|
||||
<FormRadio
|
||||
value={value}
|
||||
label={label} inlineDescription={inlineDescription}
|
||||
labelClassName="form-check-label"
|
||||
{...restProps}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
@ -31,7 +37,7 @@ export function FormInlineRadioGroup({ values, ...restProps }: FormRadioProps) {
|
||||
)
|
||||
}
|
||||
|
||||
function FormRadio({ name, value, label, currentValue, onChange, labelClassName }: Omit<FormRadioProps, "values"> & { value: string, label: ComponentChildren, labelClassName?: string }) {
|
||||
function FormRadio({ name, value, label, currentValue, onChange, labelClassName, inlineDescription }: Omit<FormRadioProps, "values"> & { value: string, label: ComponentChildren, inlineDescription?: ComponentChildren, labelClassName?: string }) {
|
||||
return (
|
||||
<label className={`tn-radio ${labelClassName ?? ""}`}>
|
||||
<input
|
||||
@ -42,7 +48,9 @@ function FormRadio({ name, value, label, currentValue, onChange, labelClassName
|
||||
checked={value === currentValue}
|
||||
onChange={e => onChange((e.target as HTMLInputElement).value)}
|
||||
/>
|
||||
{label}
|
||||
{inlineDescription ?
|
||||
<><strong>{label}</strong> - {inlineDescription}</>
|
||||
: label}
|
||||
</label>
|
||||
)
|
||||
}
|
@ -100,6 +100,16 @@ export function useSpacedUpdate(callback: () => Promise<void>, interval = 1000)
|
||||
return spacedUpdateRef.current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows a React component to read and write a Trilium option, while also watching for external changes.
|
||||
*
|
||||
* Conceptually, `useTriliumOption` works just like `useState`, but the value is also automatically updated if
|
||||
* the option is changed somewhere else in the client.
|
||||
*
|
||||
* @param name the name of the option to listen for.
|
||||
* @param needsRefresh whether to reload the frontend whenever the value is changed.
|
||||
* @returns an array where the first value is the current option value and the second value is the setter.
|
||||
*/
|
||||
export function useTriliumOption(name: OptionNames, needsRefresh?: boolean): [string, (newValue: OptionValue) => Promise<void>] {
|
||||
const initialValue = options.get(name);
|
||||
const [ value, setValue ] = useState(initialValue);
|
||||
@ -127,8 +137,8 @@ export function useTriliumOption(name: OptionNames, needsRefresh?: boolean): [st
|
||||
]
|
||||
}
|
||||
|
||||
export function useTriliumOptionBool(name: OptionNames): [boolean, (newValue: boolean) => Promise<void>] {
|
||||
const [ value, setValue ] = useTriliumOption(name);
|
||||
export function useTriliumOptionBool(name: OptionNames, needsRefresh?: boolean): [boolean, (newValue: boolean) => Promise<void>] {
|
||||
const [ value, setValue ] = useTriliumOption(name, needsRefresh);
|
||||
return [
|
||||
(value === "true"),
|
||||
(newValue) => setValue(newValue ? "true" : "false")
|
||||
|
@ -40,6 +40,7 @@ import BackupSettings from "./options/backup.js";
|
||||
import SpellcheckSettings from "./options/spellcheck.js";
|
||||
import PasswordSettings from "./options/password.jsx";
|
||||
import ShortcutSettings from "./options/shortcuts.js";
|
||||
import TextNoteSettings from "./options/text_notes.jsx";
|
||||
|
||||
const TPL = /*html*/`<div class="note-detail-content-widget note-detail-printable">
|
||||
<style>
|
||||
@ -69,16 +70,7 @@ export type OptionPages = "_optionsAppearance" | "_optionsShortcuts" | "_options
|
||||
const CONTENT_WIDGETS: Record<OptionPages | "_backendLog", ((typeof NoteContextAwareWidget)[] | JSX.Element)> = {
|
||||
_optionsAppearance: <AppearanceSettings />,
|
||||
_optionsShortcuts: <ShortcutSettings />,
|
||||
_optionsTextNotes: [
|
||||
EditorOptions,
|
||||
EditorFeaturesOptions,
|
||||
HeadingStyleOptions,
|
||||
CodeBlockOptions,
|
||||
TableOfContentsOptions,
|
||||
HighlightsListOptions,
|
||||
TextAutoReadOnlySizeOptions,
|
||||
DateTimeFormatOptions
|
||||
],
|
||||
_optionsTextNotes: <TextNoteSettings />,
|
||||
_optionsCodeNotes: [
|
||||
CodeEditorOptions,
|
||||
CodeTheme,
|
||||
|
@ -112,11 +112,13 @@ function LayoutOrientation() {
|
||||
name="layout-orientation"
|
||||
values={[
|
||||
{
|
||||
label: <><strong>{t("theme.layout-vertical-title")}</strong> - {t("theme.layout-vertical-description")}</>,
|
||||
label: t("theme.layout-vertical-title"),
|
||||
inlineDescription: t("theme.layout-vertical-description"),
|
||||
value: "vertical"
|
||||
},
|
||||
{
|
||||
label: <><strong>{t("theme.layout-horizontal-title")}</strong> - {t("theme.layout-horizontal-description")}</>,
|
||||
label: t("theme.layout-horizontal-title"),
|
||||
inlineDescription: t("theme.layout-horizontal-description"),
|
||||
value: "horizontal"
|
||||
}
|
||||
]}
|
||||
|
46
apps/client/src/widgets/type_widgets/options/text_notes.tsx
Normal file
46
apps/client/src/widgets/type_widgets/options/text_notes.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import { t } from "../../../services/i18n";
|
||||
import FormCheckbox from "../../react/FormCheckbox";
|
||||
import FormRadioGroup from "../../react/FormRadioGroup";
|
||||
import { useTriliumOption, useTriliumOptionBool } from "../../react/hooks";
|
||||
import OptionsSection from "./components/OptionsSection";
|
||||
|
||||
export default function TextNoteSettings() {
|
||||
return (
|
||||
<>
|
||||
<FormattingToolbar />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function FormattingToolbar() {
|
||||
const [ textNoteEditorType, setTextNoteEditorType ] = useTriliumOption("textNoteEditorType", true);
|
||||
const [ textNoteEditorMultilineToolbar, setTextNoteEditorMultilineToolbar ] = useTriliumOptionBool("textNoteEditorMultilineToolbar", true);
|
||||
|
||||
return (
|
||||
<OptionsSection title={t("editing.editor_type.label")}>
|
||||
<FormRadioGroup
|
||||
name="editor-type"
|
||||
currentValue={textNoteEditorType} onChange={setTextNoteEditorType}
|
||||
values={[
|
||||
{
|
||||
value: "ckeditor-balloon",
|
||||
label: t("editing.editor_type.floating.title"),
|
||||
inlineDescription: t("editing.editor_type.floating.description")
|
||||
},
|
||||
{
|
||||
value: "ckeditor-classic",
|
||||
label: t("editing.editor_type.fixed.title"),
|
||||
inlineDescription: t("editing.editor_type.fixed.description")
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
<FormCheckbox
|
||||
name="multiline-toolbar"
|
||||
label={t("editing.editor_type.multiline-toolbar")}
|
||||
currentValue={textNoteEditorMultilineToolbar} onChange={setTextNoteEditorMultilineToolbar}
|
||||
containerStyle={{ marginLeft: "1em" }}
|
||||
/>
|
||||
</OptionsSection>
|
||||
)
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
import type { OptionMap } from "@triliumnext/commons";
|
||||
import { t } from "../../../../services/i18n.js";
|
||||
import utils from "../../../../services/utils.js";
|
||||
import OptionsWidget from "../options_widget.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="options-section formatting-toolbar">
|
||||
<h4>${t("editing.editor_type.label")}</h4>
|
||||
|
||||
<div>
|
||||
<label class="tn-radio">
|
||||
<input type="radio" name="editor-type" value="ckeditor-balloon" />
|
||||
<strong>${t("editing.editor_type.floating.title")}</strong>
|
||||
- ${t("editing.editor_type.floating.description")}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="tn-radio">
|
||||
<input type="radio" name="editor-type" value="ckeditor-classic" />
|
||||
<strong>${t("editing.editor_type.fixed.title")}</strong>
|
||||
- ${t("editing.editor_type.fixed.description")}
|
||||
</label>
|
||||
|
||||
<div>
|
||||
<label class="tn-checkbox">
|
||||
<input type="checkbox" name="multiline-toolbar" />
|
||||
${t("editing.editor_type.multiline-toolbar")}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.formatting-toolbar div > div {
|
||||
margin-left: 1em;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
|
||||
export default class EditorOptions extends OptionsWidget {
|
||||
|
||||
private $body!: JQuery<HTMLElement>;
|
||||
private $multilineToolbarCheckbox!: JQuery<HTMLElement>;
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$body = $("body");
|
||||
this.$widget.find(`input[name="editor-type"]`).on("change", async () => {
|
||||
const newEditorType = this.$widget.find(`input[name="editor-type"]:checked`).val();
|
||||
await this.updateOption("textNoteEditorType", newEditorType);
|
||||
utils.reloadFrontendApp("editor type change");
|
||||
});
|
||||
|
||||
this.$multilineToolbarCheckbox = this.$widget.find('input[name="multiline-toolbar"]');
|
||||
this.$multilineToolbarCheckbox.on("change", () => this.updateCheckboxOption("textNoteEditorMultilineToolbar", this.$multilineToolbarCheckbox));
|
||||
}
|
||||
|
||||
async optionsLoaded(options: OptionMap) {
|
||||
this.$widget.find(`input[name="editor-type"][value="${options.textNoteEditorType}"]`).prop("checked", "true");
|
||||
this.setCheckboxState(this.$multilineToolbarCheckbox, options.textNoteEditorMultilineToolbar);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user