From 76fd657010719d91ff69eb6f8610c8232ffaea3a Mon Sep 17 00:00:00 2001 From: Michael Haschke Date: Wed, 11 Mar 2026 14:30:12 +0100 Subject: [PATCH 1/8] add editor config menu to markdown toolbar --- .../toolbars/EditorAppearanceConfigMenu.tsx | 59 +++++++++++++++++++ .../codemirror/toolbars/markdown.toolbar.tsx | 20 ++++++- 2 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 src/extensions/codemirror/toolbars/EditorAppearanceConfigMenu.tsx diff --git a/src/extensions/codemirror/toolbars/EditorAppearanceConfigMenu.tsx b/src/extensions/codemirror/toolbars/EditorAppearanceConfigMenu.tsx new file mode 100644 index 000000000..413242a33 --- /dev/null +++ b/src/extensions/codemirror/toolbars/EditorAppearanceConfigMenu.tsx @@ -0,0 +1,59 @@ +import React from "react"; + +import { ContextMenu, ContextMenuProps, MenuItem } from "../../../components"; + +export interface EditorAppearanceConfigMenuProps { + /** Object containing a `true`/`false` value for each property */ + config: { [s: string]: boolean }; + /** Object containing `true` for each property that cannot be changed by user */ + configLocked?: { [s: string]: boolean | undefined }; + /** Handler that returns a translation for each config property key */ + configPropertyTranslate?: (key: string) => string | false; + /** Handler to update config after user changes */ + setConfig: React.Dispatch>; + /** Additional properties used for the included `ContextMenu` */ + contextMenuProps?: ContextMenuProps; +} + +/** + * Returns a simple context menu that provides switches to control the editor appearance. + */ +export function EditorAppearanceConfigMenu({ + config, + configLocked = {}, + configPropertyTranslate = (s) => s, + setConfig, + contextMenuProps, +}: EditorAppearanceConfigMenuProps) { + return ( + { + return typeof value !== "undefined"; + }).length + } + > + {Object.entries(config).map(([key, value]) => { + return ( + { + setConfig({ + ...config, + [key]: !value, + }); + }} + /> + ); + })} + + ); +} diff --git a/src/extensions/codemirror/toolbars/markdown.toolbar.tsx b/src/extensions/codemirror/toolbars/markdown.toolbar.tsx index 13a1444b6..b82534584 100644 --- a/src/extensions/codemirror/toolbars/markdown.toolbar.tsx +++ b/src/extensions/codemirror/toolbars/markdown.toolbar.tsx @@ -9,6 +9,7 @@ import { Spacing } from "../../../components/Separation/Spacing"; import { Toolbar, ToolbarSection } from "../../../components/Toolbar"; import MarkdownCommand from "./commands/markdown.command"; +import { EditorAppearanceConfigMenu } from "./EditorAppearanceConfigMenu"; interface MarkdownToolbarProps { view?: EditorView; @@ -17,6 +18,7 @@ interface MarkdownToolbarProps { translate: (key: string) => string | false; disabled?: boolean; readonly?: boolean; + configMenu?: React.ReactElement; } export const MarkdownToolbar: React.FC = ({ @@ -25,7 +27,8 @@ export const MarkdownToolbar: React.FC = ({ showPreview, disabled, readonly, - translate + translate, + configMenu, }) => { const commandRef = React.useRef(null); @@ -35,10 +38,10 @@ export const MarkdownToolbar: React.FC = ({ } }, [view]); - const getTranslation = (fallback: string) : string => { + const getTranslation = (fallback: string): string => { const key = fallback.toLowerCase().replace(" ", "-"); return translate(key) || fallback; - } + }; const { basic, lists, attachments } = MarkdownCommand.commands; return ( @@ -112,6 +115,17 @@ export const MarkdownToolbar: React.FC = ({ disabled={disabled} /> + {configMenu && ( + + + {React.cloneElement(configMenu, { + ...{ + ...configMenu.props, + contextMenuProps: { disabled: showPreview || disabled ? true : undefined }, + }, + })} + + )} ); }; From 4b7c29c2e09f7a6a0c100719bfd0e6df0f5c1beb Mon Sep 17 00:00:00 2001 From: Michael Haschke Date: Wed, 11 Mar 2026 14:53:09 +0100 Subject: [PATCH 2/8] allow user to configure editor appearance (in markdown mode) as long as properties are not set on CodeEditor element directly --- src/extensions/codemirror/CodeMirror.tsx | 97 +++++++++++++++++------- 1 file changed, 71 insertions(+), 26 deletions(-) diff --git a/src/extensions/codemirror/CodeMirror.tsx b/src/extensions/codemirror/CodeMirror.tsx index bcaec554b..0e58642f9 100644 --- a/src/extensions/codemirror/CodeMirror.tsx +++ b/src/extensions/codemirror/CodeMirror.tsx @@ -6,6 +6,7 @@ import { DOMEventHandlers, EditorView, KeyBinding, keymap, Rect, ViewUpdate } fr import { minimalSetup } from "codemirror"; import { Markdown } from "../../cmem/markdown/Markdown"; +import { EditorAppearanceConfigMenu } from "./toolbars/EditorAppearanceConfigMenu"; import { IntentTypes } from "../../common/Intent"; import { markField } from "../../components/AutoSuggestion/extensions/markText"; import { TestableComponent } from "../../components/interfaces"; @@ -36,7 +37,17 @@ import { import { MarkdownToolbar } from "./toolbars/markdown.toolbar"; import { ExtensionCreator } from "./types"; -export interface CodeEditorProps extends Omit, "translate" | "onChange" | "onKeyDown" | "onMouseDown" | "onScroll">, TestableComponent { +interface EditorAppearance { + /** + * If enabled the code editor won't show numbers before each line. + */ + preventLineNumbers?: boolean; + + /** Long lines are wrapped and displayed on multiple lines */ + wrapLines?: boolean; +} + +export interface CodeEditorProps extends EditorAppearance, Omit, "translate" | "onChange" | "onKeyDown" | "onMouseDown" | "onScroll">, TestableComponent { // Is called with the editor instance that allows access via the CodeMirror API setEditorView?: (editor: EditorView | undefined) => void; /** @@ -86,10 +97,6 @@ export interface CodeEditorProps extends Omit, "id" | "data-test-id" | "data-testid" | "translate" | "onChange" | "onKeyDown" | "onMouseDown" | "onScroll">; @@ -186,6 +190,18 @@ const ModeLinterMap: ReadonlyMap = ["markdown"]; +const defaultAppearanceForModeWithToolbar: ReadonlyMap = new Map([ + ["markdown", { wrapLines: true, preventLineNumbers: true }] +]); + +const getDefaultAppearanceForModeWithToolbar = (hasToolbar: boolean, mode?: SupportedCodeEditorModes): EditorAppearance | undefined => { + if (hasToolbar && mode) { + return defaultAppearanceForModeWithToolbar.get(mode); + } + + return undefined; +} + /** * Includes a code editor, currently we use CodeMirror library as base. */ @@ -200,11 +216,11 @@ export const CodeEditor = ({ name, id, mode, - preventLineNumbers = false, + preventLineNumbers, + wrapLines, defaultValue = "", readOnly = false, shouldHaveMinimalSetup = true, - wrapLines = false, onScroll, setEditorView, supportCodeFolding = false, @@ -221,12 +237,20 @@ export const CodeEditor = ({ autoFocus = false, disabled = false, intent, - useToolbar, + useToolbar = false, translate, ...otherCodeEditorProps }: CodeEditorProps) => { const parent = useRef(undefined); const [view, setView] = React.useState(); + const defaultAppearanceForModeWithToolbar = getDefaultAppearanceForModeWithToolbar(useToolbar, mode); + const [editorAppearance, setEditorAppearance] = React.useState<{[s: string]: boolean;}>( + { + // we also set the fallback default here + wrapLines: wrapLines ?? defaultAppearanceForModeWithToolbar?.wrapLines ?? false, + preventLineNumbers: preventLineNumbers ?? defaultAppearanceForModeWithToolbar?.preventLineNumbers ?? false, + } + ) const currentView = React.useRef() currentView.current = view const currentReadOnly = React.useRef(readOnly) @@ -235,6 +259,8 @@ export const CodeEditor = ({ currentOnChange.current = onChange const currentDisabled = React.useRef(disabled) currentDisabled.current = disabled + const currentIntent = React.useRef(intent) + currentIntent.current = intent const [showPreview, setShowPreview] = React.useState(false); // CodeMirror Compartments in order to allow for re-configuration after initialization const readOnlyCompartment = React.useRef(compartment()) @@ -333,8 +359,8 @@ export const CodeEditor = ({ if (onSelection) onSelection(v.state.selection.ranges.filter((r) => !r.empty).map(({ from, to }) => ({ from, to }))); - if (onFocusChange && intent && !v.view.dom.classList?.contains(`${eccgui}-intent--${intent}`)) { - v.view.dom.classList.add(`${eccgui}-intent--${intent}`); + if (onFocusChange && currentIntent.current && !v.view.dom.classList?.contains(`${eccgui}-intent--${currentIntent.current}`)) { + v.view.dom.classList.add(`${eccgui}-intent--${currentIntent.current}`); } if (onCursorChange) { @@ -357,9 +383,9 @@ export const CodeEditor = ({ } }), shouldHaveMinimalSetupCompartment.current.of(addExtensionsFor(shouldHaveMinimalSetup, minimalSetup)), - preventLineNumbersCompartment.current.of(addExtensionsFor(!preventLineNumbers, adaptedLineNumbers())), + preventLineNumbersCompartment.current.of(addExtensionsFor(!editorAppearance.preventLineNumbers, adaptedLineNumbers())), shouldHighlightActiveLineCompartment.current.of(addExtensionsFor(shouldHighlightActiveLine, adaptedHighlightActiveLine())), - wrapLinesCompartment.current.of(addExtensionsFor(wrapLines, EditorView?.lineWrapping)), + wrapLinesCompartment.current.of(addExtensionsFor((editorAppearance.wrapLines!), EditorView?.lineWrapping)), supportCodeFoldingCompartment.current.of(addExtensionsFor(supportCodeFolding, adaptedFoldGutter(), adaptedCodeFolding())), useLintingCompartment.current.of(addExtensionsFor(useLinting, ...linters)), adaptedSyntaxHighlighting(defaultHighlightStyle), @@ -384,8 +410,8 @@ export const CodeEditor = ({ view.dom.classList.add(`${eccgui}-disabled`); } - if (intent) { - view.dom.className += ` ${eccgui}-intent--${intent}`; + if (currentIntent.current) { + view.dom.className += ` ${eccgui}-intent--${currentIntent.current}`; } if (autoFocus) { @@ -447,20 +473,28 @@ export const CodeEditor = ({ }, [disabled]) React.useEffect(() => { - updateExtension(addExtensionsFor(shouldHaveMinimalSetup ?? true, minimalSetup), shouldHaveMinimalSetupCompartment.current) - }, [shouldHaveMinimalSetup]) + setEditorAppearance({ + ...editorAppearance, + preventLineNumbers: preventLineNumbers ?? editorAppearance?.preventLineNumbers ?? false, + }); + updateExtension(addExtensionsFor(!editorAppearance.preventLineNumbers, adaptedLineNumbers()), preventLineNumbersCompartment.current) + }, [preventLineNumbers, editorAppearance.preventLineNumbers]) React.useEffect(() => { - updateExtension(addExtensionsFor(!preventLineNumbers, adaptedLineNumbers()), preventLineNumbersCompartment.current) - }, [preventLineNumbers]) + setEditorAppearance({ + ...editorAppearance, + wrapLines: wrapLines ?? editorAppearance?.wrapLines ?? false, + }); + updateExtension(addExtensionsFor(editorAppearance.wrapLines!, EditorView?.lineWrapping), wrapLinesCompartment.current) + }, [wrapLines, editorAppearance.wrapLines]) React.useEffect(() => { - updateExtension(addExtensionsFor(shouldHighlightActiveLine ?? false, adaptedHighlightActiveLine()), shouldHighlightActiveLineCompartment.current) - }, [shouldHighlightActiveLine]) + updateExtension(addExtensionsFor(shouldHaveMinimalSetup ?? true, minimalSetup), shouldHaveMinimalSetupCompartment.current) + }, [shouldHaveMinimalSetup]) React.useEffect(() => { - updateExtension(addExtensionsFor(wrapLines ?? false, EditorView?.lineWrapping), wrapLinesCompartment.current) - }, [wrapLines]) + updateExtension(addExtensionsFor(shouldHighlightActiveLine ?? false, adaptedHighlightActiveLine()), shouldHighlightActiveLineCompartment.current) + }, [shouldHighlightActiveLine]) React.useEffect(() => { updateExtension(addExtensionsFor(supportCodeFolding ?? false, adaptedFoldGutter(), adaptedCodeFolding()), supportCodeFoldingCompartment.current) @@ -485,6 +519,17 @@ export const CodeEditor = ({ translate={getTranslation} disabled={disabled} readonly={readOnly} + configMenu={( + + )} /> {showPreview && ( From df3760f2c72999f3645cdbd38be3ae020c9771f9 Mon Sep 17 00:00:00 2001 From: Michael Haschke Date: Wed, 11 Mar 2026 15:51:01 +0100 Subject: [PATCH 3/8] add tests for CodeEditor markdown toolbar using the appearance config --- .../codemirror/tests/CodeEditor.test.tsx | 138 ++++++++++++++++++ .../tests/EditorAppearanceConfigMenu.test.tsx | 131 +++++++++++++++++ 2 files changed, 269 insertions(+) create mode 100644 src/extensions/codemirror/tests/CodeEditor.test.tsx create mode 100644 src/extensions/codemirror/tests/EditorAppearanceConfigMenu.test.tsx diff --git a/src/extensions/codemirror/tests/CodeEditor.test.tsx b/src/extensions/codemirror/tests/CodeEditor.test.tsx new file mode 100644 index 000000000..20e4507be --- /dev/null +++ b/src/extensions/codemirror/tests/CodeEditor.test.tsx @@ -0,0 +1,138 @@ +import React from "react"; +import { fireEvent, render, screen } from "@testing-library/react"; + +import "@testing-library/jest-dom"; + +import { CLASSPREFIX as eccgui } from "../../../configuration/constants"; +import { CodeEditor } from "../CodeMirror"; + +const contextOverlayClass = `${eccgui}-contextoverlay`; + +const setupDocumentRange = () => { + document.createRange = () => { + const range = new Range(); + range.getBoundingClientRect = jest.fn(); + range.getClientRects = () => ({ + item: () => null, + length: 0, + [Symbol.iterator]: jest.fn(), + }); + return range; + }; +}; + +describe("CodeEditor - markdown mode with toolbar", () => { + beforeAll(() => { + setupDocumentRange(); + }); + + // The toolbar contains a Paragraphs ContextMenu first, then the EditorAppearanceConfigMenu last. + const getConfigMenuOverlay = (container: HTMLElement) => { + const overlays = container.getElementsByClassName(contextOverlayClass); + return overlays[overlays.length - 1] as HTMLElement; + }; + + it("renders toolbar when mode is markdown and useToolbar is true", () => { + const { container } = render(); + expect(container.querySelector(`.${eccgui}-codeeditor__toolbar`)).not.toBeNull(); + }); + + it("does not render toolbar when useToolbar is false", () => { + const { container } = render(); + expect(container.querySelector(`.${eccgui}-codeeditor__toolbar`)).toBeNull(); + }); + + it("does not render toolbar for non-markdown modes even when useToolbar is true", () => { + const { container } = render(); + expect(container.querySelector(`.${eccgui}-codeeditor__toolbar`)).toBeNull(); + }); + + it("includes the EditorAppearanceConfigMenu in the markdown toolbar", () => { + const { container } = render(); + const toolbar = container.querySelector(`.${eccgui}-codeeditor__toolbar`); + // Toolbar contains at least the Paragraphs menu and the EditorAppearanceConfigMenu + expect(toolbar?.getElementsByClassName(contextOverlayClass).length).toBeGreaterThanOrEqual(2); + }); + + it("defaults wrapLines to true in markdown mode with toolbar", async () => { + const { container } = render(); + + fireEvent.click(getConfigMenuOverlay(container)); + + const wrapLinesItem = await screen.findByText("wrapLines"); + expect(wrapLinesItem.closest("[aria-selected='true']")).not.toBeNull(); + }); + + it("defaults preventLineNumbers to true in markdown mode with toolbar", async () => { + const { container } = render(); + + fireEvent.click(getConfigMenuOverlay(container)); + + const preventLineNumbersItem = await screen.findByText("preventLineNumbers"); + expect(preventLineNumbersItem.closest("[aria-selected='true']")).not.toBeNull(); + }); + + it("locks wrapLines in config menu when wrapLines prop is explicitly provided", async () => { + const { container } = render( + + ); + + fireEvent.click(getConfigMenuOverlay(container)); + + const wrapLinesItem = await screen.findByText("wrapLines"); + expect(wrapLinesItem.closest("[aria-disabled='true']")).not.toBeNull(); + }); + + it("locks preventLineNumbers in config menu when preventLineNumbers prop is explicitly provided", async () => { + const { container } = render( + + ); + + fireEvent.click(getConfigMenuOverlay(container)); + + const preventLineNumbersItem = await screen.findByText("preventLineNumbers"); + expect(preventLineNumbersItem.closest("[aria-disabled='true']")).not.toBeNull(); + }); + + it("does not lock wrapLines in config menu when wrapLines prop is not provided", async () => { + const { container } = render(); + + fireEvent.click(getConfigMenuOverlay(container)); + + const wrapLinesItem = await screen.findByText("wrapLines"); + expect(wrapLinesItem.closest("[aria-disabled='true']")).toBeNull(); + }); + + it("does not lock preventLineNumbers in config menu when preventLineNumbers prop is not provided", async () => { + const { container } = render(); + + fireEvent.click(getConfigMenuOverlay(container)); + + const preventLineNumbersItem = await screen.findByText("preventLineNumbers"); + expect(preventLineNumbersItem.closest("[aria-disabled='true']")).toBeNull(); + }); + + it("disables config menu trigger when both wrapLines and preventLineNumbers props are provided", () => { + const { container } = render( + + ); + + const configMenuTrigger = getConfigMenuOverlay(container).querySelector("button"); + expect(configMenuTrigger).toBeDisabled(); + }); + + it("disables config menu trigger when editor is disabled", () => { + const { container } = render( + + ); + + const configMenuTrigger = getConfigMenuOverlay(container).querySelector("button"); + expect(configMenuTrigger).toBeDisabled(); + }); +}); diff --git a/src/extensions/codemirror/tests/EditorAppearanceConfigMenu.test.tsx b/src/extensions/codemirror/tests/EditorAppearanceConfigMenu.test.tsx new file mode 100644 index 000000000..deb1b6370 --- /dev/null +++ b/src/extensions/codemirror/tests/EditorAppearanceConfigMenu.test.tsx @@ -0,0 +1,131 @@ +import React from "react"; +import { fireEvent, render, screen } from "@testing-library/react"; + +import "@testing-library/jest-dom"; + +import { CLASSPREFIX as eccgui } from "../../../configuration/constants"; +import { EditorAppearanceConfigMenu } from "../toolbars/EditorAppearanceConfigMenu"; + +const contextOverlayClass = `${eccgui}-contextoverlay`; + +describe("EditorAppearanceConfigMenu", () => { + it("renders menu items for each config property, using key as fallback label", async () => { + const config = { wrapLines: true, preventLineNumbers: false }; + const setConfig = jest.fn(); + + const { container } = render(); + + fireEvent.click(container.getElementsByClassName(contextOverlayClass)[0]); + + expect(await screen.findByText("wrapLines")).toBeVisible(); + expect(await screen.findByText("preventLineNumbers")).toBeVisible(); + }); + + it("uses configPropertyTranslate for menu item labels", async () => { + const config = { wrapLines: true, preventLineNumbers: false }; + const setConfig = jest.fn(); + const translate = (key: string) => `Label_${key}` as string | false; + + const { container } = render( + + ); + + fireEvent.click(container.getElementsByClassName(contextOverlayClass)[0]); + + expect(await screen.findByText("Label_wrapLines")).toBeVisible(); + expect(await screen.findByText("Label_preventLineNumbers")).toBeVisible(); + }); + + it("calls setConfig with the toggled value when a menu item is clicked", async () => { + const config = { wrapLines: true, preventLineNumbers: false }; + const setConfig = jest.fn(); + + const { container } = render(); + + fireEvent.click(container.getElementsByClassName(contextOverlayClass)[0]); + const wrapLinesItem = await screen.findByText("wrapLines"); + fireEvent.click(wrapLinesItem); + + expect(setConfig).toHaveBeenCalledWith({ wrapLines: false, preventLineNumbers: false }); + }); + + it("menu trigger is disabled when all config properties are locked", () => { + const config = { wrapLines: true, preventLineNumbers: false }; + const configLocked = { wrapLines: true, preventLineNumbers: true }; + const setConfig = jest.fn(); + + const { container } = render( + + ); + + const trigger = container.getElementsByClassName(contextOverlayClass)[0].querySelector("button"); + expect(trigger).toBeDisabled(); + }); + + it("menu trigger is enabled when not all config properties are locked", () => { + const config = { wrapLines: true, preventLineNumbers: false }; + const configLocked = { wrapLines: true }; // only one locked + const setConfig = jest.fn(); + + const { container } = render( + + ); + + const trigger = container.getElementsByClassName(contextOverlayClass)[0].querySelector("button"); + expect(trigger).not.toBeDisabled(); + }); + + it("locked config property has a disabled menu item", async () => { + const config = { wrapLines: true, preventLineNumbers: false }; + const configLocked = { wrapLines: true }; + const setConfig = jest.fn(); + + const { container } = render( + + ); + + fireEvent.click(container.getElementsByClassName(contextOverlayClass)[0]); + + const wrapLinesItem = await screen.findByText("wrapLines"); + expect(wrapLinesItem.closest("[aria-disabled='true']")).not.toBeNull(); + }); + + it("unlocked config property has an enabled menu item", async () => { + const config = { wrapLines: true, preventLineNumbers: false }; + const configLocked = { wrapLines: true }; // only wrapLines is locked + const setConfig = jest.fn(); + + const { container } = render( + + ); + + fireEvent.click(container.getElementsByClassName(contextOverlayClass)[0]); + + const preventLineNumbersItem = await screen.findByText("preventLineNumbers"); + expect(preventLineNumbersItem.closest("[aria-disabled='true']")).toBeNull(); + }); + + it("selected config property is marked as selected in the menu", async () => { + const config = { wrapLines: true, preventLineNumbers: false }; + const setConfig = jest.fn(); + + const { container } = render(); + + fireEvent.click(container.getElementsByClassName(contextOverlayClass)[0]); + + const wrapLinesItem = await screen.findByText("wrapLines"); + expect(wrapLinesItem.closest("[aria-selected='true']")).not.toBeNull(); + }); + + it("unselected config property is not marked as selected in the menu", async () => { + const config = { wrapLines: true, preventLineNumbers: false }; + const setConfig = jest.fn(); + + const { container } = render(); + + fireEvent.click(container.getElementsByClassName(contextOverlayClass)[0]); + + const preventLineNumbersItem = await screen.findByText("preventLineNumbers"); + expect(preventLineNumbersItem.closest("[aria-selected='true']")).toBeNull(); + }); +}); From dafb4bde8dfa86b2daee1a4b7cb596f40f5cddff Mon Sep 17 00:00:00 2001 From: Michael Haschke Date: Wed, 11 Mar 2026 15:56:12 +0100 Subject: [PATCH 4/8] update changelog --- CHANGELOG.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f0b30302..bbded7cdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,12 +15,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - component for hiding elements in specific media - `` - force children to get displayed as inline content - - `` - - similar to `ContextOverlay` component but not directly linked to a React element, it specifies the target in the DOM to get connected lazy +- `` +- similar to `ContextOverlay` component but not directly linked to a React element, it specifies the target in the DOM to get connected lazy - `` - `useOnly` property: specify if only parts of the content should be used for the shortened preview, this property replaces `firstNonEmptyLineOnly` - `` - `paddingSize` property to add easily some white space +- `` + - toolbar in `markdown` mode provides a user config menu for the editor appearance - CSS custom properties - beside the color palette we now mirror the most important layout configuration variables as CSS custom properties - new icons: @@ -55,6 +57,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - Change default filter predicate to match multi-word queries. - `` - reduce stroke width to only 1px +- `` + - `wrapLines` and `preventLineNumber` do use `false` default value but if not set then it will be interpreted as `false` + - in this way it can be overwritten by new user config for the markdown mode - automatically hide user interaction elements in print view - all application header components except `` - `` and `` From 5a22631e4c95e737c77df911742341d17d231c33 Mon Sep 17 00:00:00 2001 From: Michael Haschke Date: Wed, 11 Mar 2026 16:10:45 +0100 Subject: [PATCH 5/8] update CodeEditor storybook examples --- src/extensions/codemirror/CodeMirror.stories.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/extensions/codemirror/CodeMirror.stories.tsx b/src/extensions/codemirror/CodeMirror.stories.tsx index b3d8ce75a..e0c2d72cd 100644 --- a/src/extensions/codemirror/CodeMirror.stories.tsx +++ b/src/extensions/codemirror/CodeMirror.stories.tsx @@ -24,17 +24,22 @@ const TemplateFull: StoryFn = (args) => Date: Wed, 11 Mar 2026 16:39:03 +0100 Subject: [PATCH 6/8] fix changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ddea90da..78466ca43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - `` - force children to get displayed as inline content - `` -- similar to `ContextOverlay` component but not directly linked to a React element, it specifies the target in the DOM to get connected lazy + - similar to `ContextOverlay` component but not directly linked to a React element, it specifies the target in the DOM to get connected lazy - `` - `useOnly` property: specify if only parts of the content should be used for the shortened preview, this property replaces `firstNonEmptyLineOnly` - `` @@ -51,7 +51,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - `` - use the latest provided `onChange` function - ``, `