From 234d3997b1ea5af66494b392367461fbb276a382 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 18 Aug 2025 11:21:09 +0300 Subject: [PATCH] feat(react/settings): port code block settings --- apps/client/src/widgets/react/FormGroup.tsx | 4 +- apps/client/src/widgets/react/FormSelect.tsx | 2 +- apps/client/src/widgets/react/RawHtml.tsx | 2 +- .../type_widgets/options/text_notes.tsx | 142 ++++++++++++++- .../options/text_notes/code_block.ts | 165 ------------------ 5 files changed, 146 insertions(+), 169 deletions(-) delete mode 100644 apps/client/src/widgets/type_widgets/options/text_notes/code_block.ts diff --git a/apps/client/src/widgets/react/FormGroup.tsx b/apps/client/src/widgets/react/FormGroup.tsx index 513594dfa..8b2acd72e 100644 --- a/apps/client/src/widgets/react/FormGroup.tsx +++ b/apps/client/src/widgets/react/FormGroup.tsx @@ -14,10 +14,12 @@ export default function FormGroup({ label, title, className, children, descripti return (
-
diff --git a/apps/client/src/widgets/react/FormSelect.tsx b/apps/client/src/widgets/react/FormSelect.tsx index ce42c4815..cd003c6ae 100644 --- a/apps/client/src/widgets/react/FormSelect.tsx +++ b/apps/client/src/widgets/react/FormSelect.tsx @@ -2,7 +2,7 @@ import type { ComponentChildren } from "preact"; type OnChangeListener = (newValue: string) => void; -interface FormSelectGroup { +export interface FormSelectGroup { title: string; items: T[]; } diff --git a/apps/client/src/widgets/react/RawHtml.tsx b/apps/client/src/widgets/react/RawHtml.tsx index fc4632881..1e5aef648 100644 --- a/apps/client/src/widgets/react/RawHtml.tsx +++ b/apps/client/src/widgets/react/RawHtml.tsx @@ -24,7 +24,7 @@ function getProps({ className, html, style }: RawHtmlProps) { } } -function getHtml(html: string | HTMLElement | JQuery) { +export function getHtml(html: string | HTMLElement | JQuery) { if (typeof html === "object" && "length" in html) { html = html[0]; } 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 d1d362128..fd9c8d933 100644 --- a/apps/client/src/widgets/type_widgets/options/text_notes.tsx +++ b/apps/client/src/widgets/type_widgets/options/text_notes.tsx @@ -1,10 +1,20 @@ -import { useEffect } from "preact/hooks"; +import { useContext, useEffect, useMemo, useState } from "preact/hooks"; 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"; import { toggleBodyClass } from "../../../services/utils"; +import FormGroup from "../../react/FormGroup"; +import Column from "../../react/Column"; +import { FormSelectGroup, FormSelectWithGroups } from "../../react/FormSelect"; +import { Themes, type Theme } from "@triliumnext/highlightjs"; +import { ensureMimeTypesForHighlighting, loadHighlightingTheme } from "../../../services/syntax_highlight"; +import { normalizeMimeTypeForCKEditor } from "@triliumnext/commons"; +import { ComponentChildren } from "preact"; +import RawHtml, { getHtml } from "../../react/RawHtml"; +import { ParentComponent } from "../../react/ReactBasicWidget"; +import { CSSProperties } from "preact/compat"; export default function TextNoteSettings() { return ( @@ -12,6 +22,7 @@ export default function TextNoteSettings() { + ) } @@ -90,4 +101,133 @@ function HeadingStyle() { /> ); +} + +function CodeBlockStyle() { + const themes = useMemo(() => groupThemesByLightOrDark(), []); + const [ codeBlockTheme, setCodeBlockTheme ] = useTriliumOption("codeBlockTheme"); + const [ codeBlockWordWrap, setCodeBlockWordWrap ] = useTriliumOptionBool("codeBlockWordWrap"); + + return ( + + + + + { + loadHighlightingTheme(newTheme); + setCodeBlockTheme(newTheme); + }} + /> + + + + + + + + + + ) +} + +const SAMPLE_LANGUAGE = normalizeMimeTypeForCKEditor("application/javascript;env=frontend"); +const SAMPLE_CODE = `\ +const n = 10; +greet(n); // Print "Hello World" for n times + +/** + * Displays a "Hello World!" message for a given amount of times, on the standard console. The "Hello World!" text will be displayed once per line. + * + * @param {number} times The number of times to print the \`Hello World!\` message. + */ +function greet(times) { + for (let i = 0; i++; i < times) { + console.log("Hello World!"); + } +} +`; + +function CodeBlockPreview({ theme, wordWrap }: { theme: string, wordWrap: boolean }) { + const [ code, setCode ] = useState(SAMPLE_CODE); + + useEffect(() => { + if (theme !== "none") { + import("@triliumnext/highlightjs").then(async (hljs) => { + await ensureMimeTypesForHighlighting(); + const highlightedText = hljs.highlight(SAMPLE_CODE, { + language: SAMPLE_LANGUAGE + }); + if (highlightedText) { + setCode(highlightedText.value); + } + }); + } else { + setCode(SAMPLE_CODE); + } + }, [theme]); + + const codeStyle = useMemo(() => { + if (wordWrap) { + return { whiteSpace: "pre-wrap" }; + } else { + return { whiteSpace: "pre"}; + } + }, [ wordWrap ]); + + return ( +
+
+                
+            
+
+ ) +} + +interface ThemeData { + val: string; + title: string; +} + +function groupThemesByLightOrDark() { + const darkThemes: ThemeData[] = []; + const lightThemes: ThemeData[] = []; + + for (const [ id, theme ] of Object.entries(Themes)) { + const data: ThemeData = { + val: "default:" + id, + title: theme.name + }; + + if (theme.name.includes("Dark")) { + darkThemes.push(data); + } else { + lightThemes.push(data); + } + } + + const output: FormSelectGroup[] = [ + { + title: "", + items: [{ + val: "none", + title: t("code_block.theme_none") + }] + }, + { + title: t("code_block.theme_group_light"), + items: lightThemes + }, + { + title: t("code_block.theme_group_dark"), + items: darkThemes + } + ]; + return output; } \ No newline at end of file diff --git a/apps/client/src/widgets/type_widgets/options/text_notes/code_block.ts b/apps/client/src/widgets/type_widgets/options/text_notes/code_block.ts deleted file mode 100644 index 0a97d37d3..000000000 --- a/apps/client/src/widgets/type_widgets/options/text_notes/code_block.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { normalizeMimeTypeForCKEditor, type OptionMap } from "@triliumnext/commons"; -import { t } from "../../../../services/i18n.js"; -import server from "../../../../services/server.js"; -import OptionsWidget from "../options_widget.js"; -import { ensureMimeTypesForHighlighting, loadHighlightingTheme } from "../../../../services/syntax_highlight.js"; -import { Themes, type Theme } from "@triliumnext/highlightjs"; - -const SAMPLE_LANGUAGE = normalizeMimeTypeForCKEditor("application/javascript;env=frontend"); -const SAMPLE_CODE = `\ -const n = 10; -greet(n); // Print "Hello World" for n times - -/** - * Displays a "Hello World!" message for a given amount of times, on the standard console. The "Hello World!" text will be displayed once per line. - * - * @param {number} times The number of times to print the \`Hello World!\` message. - */ -function greet(times) { - for (let i = 0; i++; i < times) { - console.log("Hello World!"); - } -} -`; - -const TPL = /*html*/` -
-

${t("highlighting.title")}

- -
-
- - -
- -
- -
-
- -
-
${SAMPLE_CODE}
-
- - -
-`; - -/** - * Contains appearance settings for code blocks within text notes, such as the theme for the syntax highlighter. - */ -export default class CodeBlockOptions extends OptionsWidget { - - private $themeSelect!: JQuery; - private $wordWrap!: JQuery; - private $sampleEl!: JQuery; - - doRender() { - this.$widget = $(TPL); - this.$themeSelect = this.$widget.find(".theme-select"); - - // Populate the list of themes. - const themeGroups = groupThemesByLightOrDark(); - for (const [key, themes] of Object.entries(themeGroups)) { - const $group = key ? $("").attr("label", key) : null; - - for (const theme of themes) { - const option = $("