Skip to content
Open
9 changes: 7 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
- `<InlineText />`
- force children to get displayed as inline content
- `<DecoupledOverlay />`
- `<DecoupledOverlay />`
- similar to `ContextOverlay` component but not directly linked to a React element, it specifies the target in the DOM to get connected lazy
- `<StringPreviewContentBlobToggler />`
- `useOnly` property: specify if only parts of the content should be used for the shortened preview, this property replaces `firstNonEmptyLineOnly`
- `<ContextOverlay />`
- `paddingSize` property to add easily some white space
- `<CodeEditor />`
- 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:
Expand Down Expand Up @@ -49,14 +51,17 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- `<CodeMirror />`
- use the latest provided `onChange` function
- `<TextField />`, `<TextArea />`
- fix emoji false-positives in invisible character detection
- fix emoji false-positives in invisible character detection

### Changed

- `<MultiSelect />`:
- Change default filter predicate to match multi-word queries.
- `<EdgeDefault />`
- reduce stroke width to only 1px
- `<CodeMirror />`
- `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 `<WorkspaceHeader />`
- `<CardActions />` and `<CardOptions />`
Expand Down
2 changes: 1 addition & 1 deletion src/cmem/react-flow/StickyNoteModal/StickyNoteModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export const StickyNoteModal: React.FC<StickyNoteModalProps> = React.memo(
name={translate("noteLabel")}
id={"sticky-note-input"}
mode="markdown"
preventLineNumbers
useToolbar
onChange={(value) => {
refNote.current = value;
}}
Expand Down
13 changes: 9 additions & 4 deletions src/extensions/codemirror/CodeMirror.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,22 @@ const TemplateFull: StoryFn<typeof CodeEditor> = (args) => <CodeEditor {...args}

export const BasicExample = TemplateFull.bind({});
BasicExample.args = {
name: "codeinput",
name: "jsinput",
mode: "json",
defaultValue: '{ json: "true" }',
};

export const MarkdownWithToolbar = TemplateFull.bind({});
MarkdownWithToolbar.args = {
name: "mdinput",
mode: "markdown",
defaultValue: "**test me**",
useToolbar: true,
disabled: false,
readOnly: true,
};

export const LinterExample = TemplateFull.bind({});
LinterExample.args = {
name: "codeinput",
name: "lintinput",
defaultValue: "**test me**",
mode: "javascript",
useLinting: true,
Expand Down
97 changes: 71 additions & 26 deletions src/extensions/codemirror/CodeMirror.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -36,7 +37,17 @@ import {
import { MarkdownToolbar } from "./toolbars/markdown.toolbar";
import { ExtensionCreator } from "./types";

export interface CodeEditorProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "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<React.HTMLAttributes<HTMLDivElement>, "translate" | "onChange" | "onKeyDown" | "onMouseDown" | "onScroll">, TestableComponent {
// Is called with the editor instance that allows access via the CodeMirror API
setEditorView?: (editor: EditorView | undefined) => void;
/**
Expand Down Expand Up @@ -86,22 +97,15 @@ export interface CodeEditorProps extends Omit<React.HTMLAttributes<HTMLDivElemen
* Default value used first when the editor is instanciated.
*/
defaultValue?: string;
/**
* If enabled the code editor won't show numbers before each line.
*/
preventLineNumbers?: boolean;

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

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

/** Long lines are wrapped and displayed on multiple lines */
wrapLines?: boolean;

/**
* Add properties to the `div` used as warpper element.
* Add properties to the `div` used as wrapper element.
* @deprecated (v26) You can now use all properties directly on `CodeEditor`.
*/
outerDivAttributes?: Omit<React.HTMLAttributes<HTMLDivElement>, "id" | "data-test-id" | "data-testid" | "translate" | "onChange" | "onKeyDown" | "onMouseDown" | "onScroll">;
Expand Down Expand Up @@ -186,6 +190,18 @@ const ModeLinterMap: ReadonlyMap<SupportedCodeEditorModes, ReadonlyArray<Extensi

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

const defaultAppearanceForModeWithToolbar: ReadonlyMap<SupportedCodeEditorModes, EditorAppearance> = 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.
*/
Expand All @@ -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,
Expand All @@ -221,12 +237,20 @@ export const CodeEditor = ({
autoFocus = false,
disabled = false,
intent,
useToolbar,
useToolbar = false,
translate,
...otherCodeEditorProps
}: CodeEditorProps) => {
const parent = useRef<any>(undefined);
const [view, setView] = React.useState<EditorView | undefined>();
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<EditorView>()
currentView.current = view
const currentReadOnly = React.useRef(readOnly)
Expand All @@ -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<boolean>(false);
// CodeMirror Compartments in order to allow for re-configuration after initialization
const readOnlyCompartment = React.useRef<Compartment>(compartment())
Expand Down Expand Up @@ -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) {
Expand All @@ -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),
Expand All @@ -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) {
Expand Down Expand Up @@ -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)
Expand All @@ -485,6 +519,17 @@ export const CodeEditor = ({
translate={getTranslation}
disabled={disabled}
readonly={readOnly}
configMenu={(
<EditorAppearanceConfigMenu
config={{...editorAppearance}}
configLocked={{
wrapLines,
preventLineNumbers,
}}
setConfig={setEditorAppearance}
configPropertyTranslate={getTranslation}
/>
)}
/>
</div>
{showPreview && (
Expand Down
Loading
Loading