From 93920bdfb45f3a9dec6051b50ff8f0109c411e27 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 13 Apr 2026 16:31:43 +0300 Subject: [PATCH] feat(options/text): add for heading style & formatting toolbar --- .../type_widgets/options/text_notes.css | 125 ++++++++++++++++++ .../type_widgets/options/text_notes.tsx | 118 ++++++++++++++--- 2 files changed, 222 insertions(+), 21 deletions(-) create mode 100644 apps/client/src/widgets/type_widgets/options/text_notes.css diff --git a/apps/client/src/widgets/type_widgets/options/text_notes.css b/apps/client/src/widgets/type_widgets/options/text_notes.css new file mode 100644 index 0000000000..e72c2defd1 --- /dev/null +++ b/apps/client/src/widgets/type_widgets/options/text_notes.css @@ -0,0 +1,125 @@ +/* HeadingStyleSelector */ +.heading-style-preview { + display: flex; + align-items: center; + gap: 12px; +} + +.heading-preview { + display: inline-flex; + align-items: center; + font-weight: bold; + font-size: 1.1em; + min-width: 40px; + position: relative; +} + +.heading-preview-markdown .heading-prefix { + color: var(--muted-text-color); + font-weight: normal; + font-family: var(--font-family-monospace); +} + +.heading-preview-underline { + flex-direction: column; + align-items: flex-start; +} + +.heading-preview-underline .heading-underline { + width: 100%; + height: 2px; + background-color: currentColor; + margin-top: 2px; +} + +.heading-style-label { + flex: 1; +} + +/* Toolbar type illustration */ +.toolbar-illustration { + width: 140px; + height: 100px; + border: 1px solid var(--main-border-color); + border-radius: 6px; + background: var(--main-background-color); + display: flex; + flex-direction: column; + overflow: hidden; + position: relative; + + .toolbar-bar { + display: flex; + gap: 4px; + padding: 6px 8px; + background: var(--left-pane-background-color); + border-bottom: 1px solid var(--main-border-color); + flex-shrink: 0; + } + + .toolbar-icon { + width: 10px; + height: 10px; + background: var(--muted-text-color); + border-radius: 2px; + opacity: 0.5; + + &.wide { + width: 20px; + } + } + + .document-area { + flex: 1; + padding: 10px; + display: flex; + flex-direction: column; + gap: 6px; + } + + .text-line { + height: 6px; + background: var(--muted-text-color); + border-radius: 2px; + opacity: 0.25; + } + + .text-line-with-selection { + display: flex; + gap: 2px; + height: 6px; + + .text-segment { + background: var(--muted-text-color); + border-radius: 2px; + opacity: 0.25; + } + + .text-selection { + width: 30%; + background: var(--accented-background-color); + border-radius: 2px; + } + } + + .floating-toolbar { + position: absolute; + top: 32px; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 3px; + padding: 4px 6px; + background: var(--tooltip-background-color); + border: 1px solid var(--main-border-color); + border-radius: 4px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + + .toolbar-icon { + width: 8px; + height: 8px; + background: var(--main-background-color); + opacity: 0.8; + } + } +} diff --git a/apps/client/src/widgets/type_widgets/options/text_notes.tsx b/apps/client/src/widgets/type_widgets/options/text_notes.tsx index b130997d3f..5914ea8230 100644 --- a/apps/client/src/widgets/type_widgets/options/text_notes.tsx +++ b/apps/client/src/widgets/type_widgets/options/text_notes.tsx @@ -1,3 +1,5 @@ +import "./text_notes.css"; + import { normalizeMimeTypeForCKEditor } from "@triliumnext/commons"; import { Themes } from "@triliumnext/highlightjs"; import type { CSSProperties } from "preact/compat"; @@ -9,8 +11,9 @@ import { ensureMimeTypesForHighlighting, loadHighlightingTheme } from "../../../ import { formatDateTime, toggleBodyClass } from "../../../services/utils"; import FormCheckbox from "../../react/FormCheckbox"; import FormGroup from "../../react/FormGroup"; -import FormRadioGroup from "../../react/FormRadioGroup"; -import FormSelect, { FormSelectGroup, FormSelectWithGroups } from "../../react/FormSelect"; +import Dropdown from "../../react/Dropdown"; +import { FormListItem } from "../../react/FormList"; +import { FormSelectGroup, FormSelectWithGroups } from "../../react/FormSelect"; import FormText from "../../react/FormText"; import FormTextBox, { FormTextBoxWithUnit } from "../../react/FormTextBox"; import { useTriliumOption, useTriliumOptionBool, useTriliumOptionJson } from "../../react/hooks"; @@ -18,6 +21,7 @@ import { getHtml } from "../../react/RawHtml"; import CheckboxList from "./components/CheckboxList"; import OptionsRow, { OptionsRowWithToggle } from "./components/OptionsRow"; import OptionsSection from "./components/OptionsSection"; +import RadioWithIllustration from "./components/RadioWithIllustration"; const isNewLayout = isExperimentalFeatureEnabled("new-layout"); @@ -40,19 +44,19 @@ function FormattingToolbar() { return ( - }, { - value: "ckeditor-classic", - label: t("editing.editor_type.fixed.title"), - inlineDescription: t("editing.editor_type.fixed.description") + key: "ckeditor-classic", + text: t("editing.editor_type.fixed.title"), + illustration: } ]} /> @@ -61,12 +65,54 @@ function FormattingToolbar() { name="multiline-toolbar" label={t("editing.editor_type.multiline-toolbar")} currentValue={textNoteEditorMultilineToolbar} onChange={setTextNoteEditorMultilineToolbar} - containerStyle={{ marginInlineStart: "1em" }} + containerStyle={{ marginTop: "1em" }} /> ); } +function ToolbarIllustration({ type }: { type: "floating" | "fixed" }) { + return ( +
+ {type === "fixed" && ( +
+ + + + + + +
+ )} + +
+
+
+
+ + + +
+
+
+
+ + {type === "floating" && ( +
+ + + + +
+ )} +
+ ); +} + +function ToolbarIcon({ wide }: { wide?: boolean }) { + return
; +} + function EditorFeatures() { const [emojiCompletionEnabled, setEmojiCompletionEnabled] = useTriliumOptionBool("textNoteEmojiCompletionEnabled"); const [noteCompletionEnabled, setNoteCompletionEnabled] = useTriliumOptionBool("textNoteCompletionEnabled"); @@ -113,15 +159,7 @@ function Editor() { return ( - + @@ -147,6 +185,44 @@ function Editor() { ); } +const HEADING_STYLES = [ + { value: "plain", labelKey: "heading_style.plain" }, + { value: "underline", labelKey: "heading_style.underline" }, + { value: "markdown", labelKey: "heading_style.markdown" } +] as const; + +function HeadingStyleSelector({ currentValue, onChange }: { currentValue: string, onChange: (value: string) => void }) { + const currentStyle = HEADING_STYLES.find(s => s.value === currentValue) ?? HEADING_STYLES[0]; + + return ( + + {HEADING_STYLES.map(({ value, labelKey }) => ( + onChange(value)} + selected={currentValue === value} + > +
+ + {t(labelKey)} +
+
+ ))} +
+ ); +} + +function HeadingPreview({ style }: { style: string }) { + const previewClass = `heading-preview heading-preview-${style}`; + return ( + + {style === "markdown" && ## } + Aa + {style === "underline" && } + + ); +} + function CodeBlockStyle() { const themes = useMemo(() => { const darkThemes: ThemeData[] = [];