Skip to content

Commit 4b7c29c

Browse files
committed
allow user to configure editor appearance (in markdown mode) as long as properties are not set on CodeEditor element directly
1 parent 76fd657 commit 4b7c29c

1 file changed

Lines changed: 71 additions & 26 deletions

File tree

src/extensions/codemirror/CodeMirror.tsx

Lines changed: 71 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { DOMEventHandlers, EditorView, KeyBinding, keymap, Rect, ViewUpdate } fr
66
import { minimalSetup } from "codemirror";
77

88
import { Markdown } from "../../cmem/markdown/Markdown";
9+
import { EditorAppearanceConfigMenu } from "./toolbars/EditorAppearanceConfigMenu";
910
import { IntentTypes } from "../../common/Intent";
1011
import { markField } from "../../components/AutoSuggestion/extensions/markText";
1112
import { TestableComponent } from "../../components/interfaces";
@@ -36,7 +37,17 @@ import {
3637
import { MarkdownToolbar } from "./toolbars/markdown.toolbar";
3738
import { ExtensionCreator } from "./types";
3839

39-
export interface CodeEditorProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "translate" | "onChange" | "onKeyDown" | "onMouseDown" | "onScroll">, TestableComponent {
40+
interface EditorAppearance {
41+
/**
42+
* If enabled the code editor won't show numbers before each line.
43+
*/
44+
preventLineNumbers?: boolean;
45+
46+
/** Long lines are wrapped and displayed on multiple lines */
47+
wrapLines?: boolean;
48+
}
49+
50+
export interface CodeEditorProps extends EditorAppearance, Omit<React.HTMLAttributes<HTMLDivElement>, "translate" | "onChange" | "onKeyDown" | "onMouseDown" | "onScroll">, TestableComponent {
4051
// Is called with the editor instance that allows access via the CodeMirror API
4152
setEditorView?: (editor: EditorView | undefined) => void;
4253
/**
@@ -86,22 +97,15 @@ export interface CodeEditorProps extends Omit<React.HTMLAttributes<HTMLDivElemen
8697
* Default value used first when the editor is instanciated.
8798
*/
8899
defaultValue?: string;
89-
/**
90-
* If enabled the code editor won't show numbers before each line.
91-
*/
92-
preventLineNumbers?: boolean;
93100

94101
/** Set read-only mode. Default: false */
95102
readOnly?: boolean;
96103

97104
/** Optional height of the component */
98105
height?: number | string;
99106

100-
/** Long lines are wrapped and displayed on multiple lines */
101-
wrapLines?: boolean;
102-
103107
/**
104-
* Add properties to the `div` used as warpper element.
108+
* Add properties to the `div` used as wrapper element.
105109
* @deprecated (v26) You can now use all properties directly on `CodeEditor`.
106110
*/
107111
outerDivAttributes?: Omit<React.HTMLAttributes<HTMLDivElement>, "id" | "data-test-id" | "data-testid" | "translate" | "onChange" | "onKeyDown" | "onMouseDown" | "onScroll">;
@@ -186,6 +190,18 @@ const ModeLinterMap: ReadonlyMap<SupportedCodeEditorModes, ReadonlyArray<Extensi
186190

187191
const ModeToolbarSupport: ReadonlyArray<SupportedCodeEditorModes> = ["markdown"];
188192

193+
const defaultAppearanceForModeWithToolbar: ReadonlyMap<SupportedCodeEditorModes, EditorAppearance> = new Map([
194+
["markdown", { wrapLines: true, preventLineNumbers: true }]
195+
]);
196+
197+
const getDefaultAppearanceForModeWithToolbar = (hasToolbar: boolean, mode?: SupportedCodeEditorModes): EditorAppearance | undefined => {
198+
if (hasToolbar && mode) {
199+
return defaultAppearanceForModeWithToolbar.get(mode);
200+
}
201+
202+
return undefined;
203+
}
204+
189205
/**
190206
* Includes a code editor, currently we use CodeMirror library as base.
191207
*/
@@ -200,11 +216,11 @@ export const CodeEditor = ({
200216
name,
201217
id,
202218
mode,
203-
preventLineNumbers = false,
219+
preventLineNumbers,
220+
wrapLines,
204221
defaultValue = "",
205222
readOnly = false,
206223
shouldHaveMinimalSetup = true,
207-
wrapLines = false,
208224
onScroll,
209225
setEditorView,
210226
supportCodeFolding = false,
@@ -221,12 +237,20 @@ export const CodeEditor = ({
221237
autoFocus = false,
222238
disabled = false,
223239
intent,
224-
useToolbar,
240+
useToolbar = false,
225241
translate,
226242
...otherCodeEditorProps
227243
}: CodeEditorProps) => {
228244
const parent = useRef<any>(undefined);
229245
const [view, setView] = React.useState<EditorView | undefined>();
246+
const defaultAppearanceForModeWithToolbar = getDefaultAppearanceForModeWithToolbar(useToolbar, mode);
247+
const [editorAppearance, setEditorAppearance] = React.useState<{[s: string]: boolean;}>(
248+
{
249+
// we also set the fallback default here
250+
wrapLines: wrapLines ?? defaultAppearanceForModeWithToolbar?.wrapLines ?? false,
251+
preventLineNumbers: preventLineNumbers ?? defaultAppearanceForModeWithToolbar?.preventLineNumbers ?? false,
252+
}
253+
)
230254
const currentView = React.useRef<EditorView>()
231255
currentView.current = view
232256
const currentReadOnly = React.useRef(readOnly)
@@ -235,6 +259,8 @@ export const CodeEditor = ({
235259
currentOnChange.current = onChange
236260
const currentDisabled = React.useRef(disabled)
237261
currentDisabled.current = disabled
262+
const currentIntent = React.useRef(intent)
263+
currentIntent.current = intent
238264
const [showPreview, setShowPreview] = React.useState<boolean>(false);
239265
// CodeMirror Compartments in order to allow for re-configuration after initialization
240266
const readOnlyCompartment = React.useRef<Compartment>(compartment())
@@ -333,8 +359,8 @@ export const CodeEditor = ({
333359
if (onSelection)
334360
onSelection(v.state.selection.ranges.filter((r) => !r.empty).map(({ from, to }) => ({ from, to })));
335361

336-
if (onFocusChange && intent && !v.view.dom.classList?.contains(`${eccgui}-intent--${intent}`)) {
337-
v.view.dom.classList.add(`${eccgui}-intent--${intent}`);
362+
if (onFocusChange && currentIntent.current && !v.view.dom.classList?.contains(`${eccgui}-intent--${currentIntent.current}`)) {
363+
v.view.dom.classList.add(`${eccgui}-intent--${currentIntent.current}`);
338364
}
339365

340366
if (onCursorChange) {
@@ -357,9 +383,9 @@ export const CodeEditor = ({
357383
}
358384
}),
359385
shouldHaveMinimalSetupCompartment.current.of(addExtensionsFor(shouldHaveMinimalSetup, minimalSetup)),
360-
preventLineNumbersCompartment.current.of(addExtensionsFor(!preventLineNumbers, adaptedLineNumbers())),
386+
preventLineNumbersCompartment.current.of(addExtensionsFor(!editorAppearance.preventLineNumbers, adaptedLineNumbers())),
361387
shouldHighlightActiveLineCompartment.current.of(addExtensionsFor(shouldHighlightActiveLine, adaptedHighlightActiveLine())),
362-
wrapLinesCompartment.current.of(addExtensionsFor(wrapLines, EditorView?.lineWrapping)),
388+
wrapLinesCompartment.current.of(addExtensionsFor((editorAppearance.wrapLines!), EditorView?.lineWrapping)),
363389
supportCodeFoldingCompartment.current.of(addExtensionsFor(supportCodeFolding, adaptedFoldGutter(), adaptedCodeFolding())),
364390
useLintingCompartment.current.of(addExtensionsFor(useLinting, ...linters)),
365391
adaptedSyntaxHighlighting(defaultHighlightStyle),
@@ -384,8 +410,8 @@ export const CodeEditor = ({
384410
view.dom.classList.add(`${eccgui}-disabled`);
385411
}
386412

387-
if (intent) {
388-
view.dom.className += ` ${eccgui}-intent--${intent}`;
413+
if (currentIntent.current) {
414+
view.dom.className += ` ${eccgui}-intent--${currentIntent.current}`;
389415
}
390416

391417
if (autoFocus) {
@@ -447,20 +473,28 @@ export const CodeEditor = ({
447473
}, [disabled])
448474

449475
React.useEffect(() => {
450-
updateExtension(addExtensionsFor(shouldHaveMinimalSetup ?? true, minimalSetup), shouldHaveMinimalSetupCompartment.current)
451-
}, [shouldHaveMinimalSetup])
476+
setEditorAppearance({
477+
...editorAppearance,
478+
preventLineNumbers: preventLineNumbers ?? editorAppearance?.preventLineNumbers ?? false,
479+
});
480+
updateExtension(addExtensionsFor(!editorAppearance.preventLineNumbers, adaptedLineNumbers()), preventLineNumbersCompartment.current)
481+
}, [preventLineNumbers, editorAppearance.preventLineNumbers])
452482

453483
React.useEffect(() => {
454-
updateExtension(addExtensionsFor(!preventLineNumbers, adaptedLineNumbers()), preventLineNumbersCompartment.current)
455-
}, [preventLineNumbers])
484+
setEditorAppearance({
485+
...editorAppearance,
486+
wrapLines: wrapLines ?? editorAppearance?.wrapLines ?? false,
487+
});
488+
updateExtension(addExtensionsFor(editorAppearance.wrapLines!, EditorView?.lineWrapping), wrapLinesCompartment.current)
489+
}, [wrapLines, editorAppearance.wrapLines])
456490

457491
React.useEffect(() => {
458-
updateExtension(addExtensionsFor(shouldHighlightActiveLine ?? false, adaptedHighlightActiveLine()), shouldHighlightActiveLineCompartment.current)
459-
}, [shouldHighlightActiveLine])
492+
updateExtension(addExtensionsFor(shouldHaveMinimalSetup ?? true, minimalSetup), shouldHaveMinimalSetupCompartment.current)
493+
}, [shouldHaveMinimalSetup])
460494

461495
React.useEffect(() => {
462-
updateExtension(addExtensionsFor(wrapLines ?? false, EditorView?.lineWrapping), wrapLinesCompartment.current)
463-
}, [wrapLines])
496+
updateExtension(addExtensionsFor(shouldHighlightActiveLine ?? false, adaptedHighlightActiveLine()), shouldHighlightActiveLineCompartment.current)
497+
}, [shouldHighlightActiveLine])
464498

465499
React.useEffect(() => {
466500
updateExtension(addExtensionsFor(supportCodeFolding ?? false, adaptedFoldGutter(), adaptedCodeFolding()), supportCodeFoldingCompartment.current)
@@ -485,6 +519,17 @@ export const CodeEditor = ({
485519
translate={getTranslation}
486520
disabled={disabled}
487521
readonly={readOnly}
522+
configMenu={(
523+
<EditorAppearanceConfigMenu
524+
config={{...editorAppearance}}
525+
configLocked={{
526+
wrapLines,
527+
preventLineNumbers,
528+
}}
529+
setConfig={setEditorAppearance}
530+
configPropertyTranslate={getTranslation}
531+
/>
532+
)}
488533
/>
489534
</div>
490535
{showPreview && (

0 commit comments

Comments
 (0)