From 4a1d379ab48d07cbe6858bc585baf1c340d6cf1d Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 18 Aug 2025 14:58:29 +0300 Subject: [PATCH] feat(react/settings): port code editor appearance --- .../widgets/type_widgets/content_widget.tsx | 4 - .../type_widgets/options/code_notes.tsx | 100 +++++++++- .../options/code_notes/code_theme.ts | 173 ------------------ .../options/samples/code_note.txt | 66 +++++++ 4 files changed, 161 insertions(+), 182 deletions(-) delete mode 100644 apps/client/src/widgets/type_widgets/options/code_notes/code_theme.ts create mode 100644 apps/client/src/widgets/type_widgets/options/samples/code_note.txt diff --git a/apps/client/src/widgets/type_widgets/content_widget.tsx b/apps/client/src/widgets/type_widgets/content_widget.tsx index 1a65f87f6..62dd7b2aa 100644 --- a/apps/client/src/widgets/type_widgets/content_widget.tsx +++ b/apps/client/src/widgets/type_widgets/content_widget.tsx @@ -1,7 +1,4 @@ import TypeWidget from "./type_widget.js"; -import CodeEditorOptions from "./options/code_notes/code_editor.js"; -import CodeAutoReadOnlySizeOptions from "./options/code_notes/code_auto_read_only_size.js"; -import CodeMimeTypesOptions from "./options/code_notes/code_mime_types.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"; @@ -18,7 +15,6 @@ import type FNote from "../../entities/fnote.js"; import type NoteContextAwareWidget from "../note_context_aware_widget.js"; import { t } from "../../services/i18n.js"; import type BasicWidget from "../basic_widget.js"; -import CodeTheme from "./options/code_notes/code_theme.js"; import type { JSX } from "preact/jsx-runtime"; import AppearanceSettings from "./options/appearance.jsx"; import { renderReactWidget } from "../react/ReactBasicWidget.jsx"; diff --git a/apps/client/src/widgets/type_widgets/options/code_notes.tsx b/apps/client/src/widgets/type_widgets/options/code_notes.tsx index 7cac387a5..4acc20c7c 100644 --- a/apps/client/src/widgets/type_widgets/options/code_notes.tsx +++ b/apps/client/src/widgets/type_widgets/options/code_notes.tsx @@ -1,13 +1,22 @@ -import { t } from "../../../services/i18n" -import FormCheckbox from "../../react/FormCheckbox" -import FormGroup from "../../react/FormGroup" -import { useTriliumOptionBool } from "../../react/hooks" -import OptionsSection from "./components/OptionsSection" +import CodeMirror, { ColorThemes, getThemeById } from "@triliumnext/codemirror"; +import { t } from "../../../services/i18n"; +import Column from "../../react/Column"; +import FormCheckbox from "../../react/FormCheckbox"; +import FormGroup from "../../react/FormGroup"; +import FormSelect from "../../react/FormSelect"; +import { useTriliumOption, useTriliumOptionBool } from "../../react/hooks"; +import OptionsSection from "./components/OptionsSection"; +import { useEffect, useMemo, useRef } from "preact/hooks"; +import codeNoteSample from "./samples/code_note.txt?raw"; +import { DEFAULT_PREFIX } from "../abstract_code_type_widget"; + +const SAMPLE_MIME = "application/typescript"; export default function CodeNoteSettings() { return ( <> + ) } @@ -26,4 +35,85 @@ function Editor() { ) +} + +function Appearance() { + const [ codeNoteTheme, setCodeNoteTheme ] = useTriliumOption("codeNoteTheme"); + const [ codeLineWrapEnabled, setCodeLineWrapEnabled ] = useTriliumOptionBool("codeLineWrapEnabled"); + + const themes = useMemo(() => { + return ColorThemes.map(({ id, name }) => ({ + id: "default:" + id, + name + })); + }, []); + + return ( + + + + + + + + + + + + + + + ); +} + +function CodeNotePreview({ themeName, wordWrapping }: { themeName: string, wordWrapping: boolean }) { + const editorRef = useRef(null); + const containerRef = useRef(null); + + useEffect(() => { + if (!containerRef.current) { + return; + } + + // Clean up previous instance. + editorRef.current?.destroy(); + containerRef.current.innerHTML = ""; + + // Set up a new instance. + const editor = new CodeMirror({ + parent: containerRef.current + }); + editor.setText(codeNoteSample); + editor.setMimeType(SAMPLE_MIME); + editorRef.current = editor; + }, []); + + useEffect(() => { + editorRef.current?.setLineWrapping(wordWrapping); + }, [ wordWrapping ]); + + useEffect(() => { + if (themeName?.startsWith(DEFAULT_PREFIX)) { + const theme = getThemeById(themeName.substring(DEFAULT_PREFIX.length)); + if (theme) { + editorRef.current?.setTheme(theme); + } + } + }, [ themeName ]); + + return ( +
+ ); } \ No newline at end of file diff --git a/apps/client/src/widgets/type_widgets/options/code_notes/code_theme.ts b/apps/client/src/widgets/type_widgets/options/code_notes/code_theme.ts deleted file mode 100644 index 52818e11b..000000000 --- a/apps/client/src/widgets/type_widgets/options/code_notes/code_theme.ts +++ /dev/null @@ -1,173 +0,0 @@ -import type { OptionMap } from "@triliumnext/commons"; -import OptionsWidget from "../options_widget"; -import server from "../../../../services/server"; -import CodeMirror, { getThemeById } from "@triliumnext/codemirror"; -import { DEFAULT_PREFIX } from "../../abstract_code_type_widget"; -import { t } from "../../../../services/i18n"; -import { ColorThemes } from "@triliumnext/codemirror"; - -// TODO: Deduplicate -interface Theme { - title: string; - val: string; -} - -type Response = Theme[]; - -const SAMPLE_MIME = "application/typescript"; -const SAMPLE_CODE = `\ -import { defaultKeymap, history, historyKeymap } from "@codemirror/commands"; -import { EditorView, highlightActiveLine, keymap, lineNumbers, placeholder, ViewUpdate, type EditorViewConfig } from "@codemirror/view"; -import { defaultHighlightStyle, StreamLanguage, syntaxHighlighting, indentUnit, bracketMatching, foldGutter } from "@codemirror/language"; -import { Compartment, EditorState, type Extension } from "@codemirror/state"; -import { highlightSelectionMatches } from "@codemirror/search"; -import { vim } from "@replit/codemirror-vim"; -import byMimeType from "./syntax_highlighting.js"; -import smartIndentWithTab from "./extensions/custom_tab.js"; -import type { ThemeDefinition } from "./color_themes.js"; - -export { default as ColorThemes, type ThemeDefinition, getThemeById } from "./color_themes.js"; - -type ContentChangedListener = () => void; - -export interface EditorConfig { - parent: HTMLElement; - placeholder?: string; - lineWrapping?: boolean; - vimKeybindings?: boolean; - readOnly?: boolean; - onContentChanged?: ContentChangedListener; -} - -export default class CodeMirror extends EditorView { - - private config: EditorConfig; - private languageCompartment: Compartment; - private historyCompartment: Compartment; - private themeCompartment: Compartment; - - constructor(config: EditorConfig) { - const languageCompartment = new Compartment(); - const historyCompartment = new Compartment(); - const themeCompartment = new Compartment(); - - let extensions: Extension[] = []; - - if (config.vimKeybindings) { - extensions.push(vim()); - } - - extensions = [ - ...extensions, - languageCompartment.of([]), - themeCompartment.of([ - syntaxHighlighting(defaultHighlightStyle, { fallback: true }) - ]), - highlightActiveLine(), - highlightSelectionMatches(), - bracketMatching(), - lineNumbers(), - foldGutter(), - indentUnit.of(" ".repeat(4)), - keymap.of([ - ...defaultKeymap, - ...historyKeymap, - ...smartIndentWithTab - ]) - ] - - super({ - parent: config.parent, - extensions - }); - } -}`; - -const TPL = /*html*/`\ -
-

${t("code_theme.title")}

- -
-
- - -
- -
- -
-
- -
-
- - -
-`; - -export default class CodeTheme extends OptionsWidget { - - private $themeSelect!: JQuery; - private $sampleEl!: JQuery; - private $lineWrapEnabled!: JQuery; - private editor?: CodeMirror; - - doRender() { - this.$widget = $(TPL); - this.$themeSelect = this.$widget.find(".theme-select"); - this.$themeSelect.on("change", async () => { - const newTheme = String(this.$themeSelect.val()); - await server.put(`options/codeNoteTheme/${newTheme}`); - }); - - // Populate the list of themes. - for (const theme of ColorThemes) { - const option = $("