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 ( + + + + {t("code_theme.color-scheme")} + + + + + + + + + + + ); +} + +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")} - - - - ${t("code_theme.color-scheme")} - - - - - - - ${t("code_theme.word_wrapping")} - - - - - - - - - -`; - -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 = $("") - .attr("value", `default:${theme.id}`) - .text(theme.name); - this.$themeSelect.append(option); - } - - this.$sampleEl = this.$widget.find(".note-detail-readonly-code-content"); - this.$lineWrapEnabled = this.$widget.find(".word-wrap"); - this.$lineWrapEnabled.on("change", () => this.updateCheckboxOption("codeLineWrapEnabled", this.$lineWrapEnabled)); - } - - async #setupPreview(options: OptionMap) { - if (!this.editor) { - this.editor = new CodeMirror({ - parent: this.$sampleEl[0], - }); - } - this.editor.setText(SAMPLE_CODE); - this.editor.setMimeType(SAMPLE_MIME); - this.editor.setLineWrapping(options.codeLineWrapEnabled === "true"); - - // Load the theme. - const themeId = options.codeNoteTheme; - if (themeId?.startsWith(DEFAULT_PREFIX)) { - const theme = getThemeById(themeId.substring(DEFAULT_PREFIX.length)); - if (theme) { - await this.editor.setTheme(theme); - } - } - } - - async optionsLoaded(options: OptionMap) { - this.$themeSelect.val(options.codeNoteTheme); - this.#setupPreview(options); - this.setCheckboxState(this.$lineWrapEnabled, options.codeLineWrapEnabled); - } - -} diff --git a/apps/client/src/widgets/type_widgets/options/samples/code_note.txt b/apps/client/src/widgets/type_widgets/options/samples/code_note.txt new file mode 100644 index 000000000..58946d9d9 --- /dev/null +++ b/apps/client/src/widgets/type_widgets/options/samples/code_note.txt @@ -0,0 +1,66 @@ +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 + }); + } +} \ No newline at end of file