Skip to content

Commit af98f40

Browse files
authored
Merge pull request #257 from eccenca/feature/provideMarkdownEditorForNonExpertUsers-CMEM-3747
Provide markdown editor for non expert users (CMEM-3747)
2 parents 6afcca3 + 70a71cd commit af98f40

7 files changed

Lines changed: 575 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,19 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
3333
- `<NodeContent />`
3434
- `resizeDirections` to specifiy the axis that can be used to resize the node
3535
- `resizeMaxDimensions` to add maximum values for resizing height/width
36+
- `<CodeEditor />`
37+
- `useToolbar` property to display toolbar if the `mode` is supported
38+
- currently `markdown` mode is integrated, including support for headlines `<h1-6>`, `<blockquote>`, `<code>` block and inline, `<b>` bold, `<i>`, italic, `<del>` strike through, `<ul>`, `<ol>` and checkbox lists, `<a>` links and `<img>` images
3639
- New icons:
37-
- "item-magic-edit": icon for "magic" edit suggestions
38-
- "artefact-task-concatenatetofile": icon for "Concatenate to file" operator
40+
- `item-magic-edit`: icon for "magic" edit suggestions
41+
- `artefact-task-concatenatetofile`: icon for "Concatenate to file" operator
42+
- `operation-format-text-code`
43+
- `operation-format-text-bold`
44+
- `operation-format-text-italic`
45+
- `operation-format-text-strikethrough`
46+
- `operation-format-list-bullet`
47+
- `operation-format-list-checked`
48+
- `operation-format-list-numbered`
3949

4050
### Fixed
4151

src/components/Icon/canonicalIconNames.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ const canonicalIcons = {
8686
"item-vertmenu": icons.OverflowMenuHorizontal,
8787
"item-viewdetails": icons.View,
8888
"item-hidedetails": icons.ViewOff,
89+
"item-image": icons.Image,
8990

9091
"list-sortasc": icons.ArrowDown,
9192
"list-sortdesc": icons.ArrowUp,
@@ -127,6 +128,13 @@ const canonicalIcons = {
127128
"operation-filterremove": icons.FilterRemove,
128129
"operation-filter": icons.Filter,
129130
"operation-format-codeblock": icons.CodeBlock,
131+
"operation-format-text-code": icons.Code,
132+
"operation-format-text-bold": icons.TextBold,
133+
"operation-format-text-italic": icons.TextItalic,
134+
"operation-format-text-strikethrough": icons.TextStrikethrough,
135+
"operation-format-list-bullet": icons.ListBulleted,
136+
"operation-format-list-checked": icons.ListChecked,
137+
"operation-format-list-numbered": icons.ListNumbered,
130138
"operation-fix": icons.Tools,
131139
"operation-link": icons.Link,
132140
"operation-logout": icons.Logout,

src/extensions/codemirror/CodeMirror.stories.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,10 @@ IntentExample.args = {
5252
mode: "javascript",
5353
intent: "warning",
5454
};
55+
export const MarkdownWithToolbarExample = TemplateFull.bind({});
56+
MarkdownWithToolbarExample.args = {
57+
name: "codeinput",
58+
defaultValue: "**test me**",
59+
mode: "markdown",
60+
useToolbar: true,
61+
};

src/extensions/codemirror/CodeMirror.tsx

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import { minimalSetup } from "codemirror";
88
import { IntentTypes } from "../../common/Intent";
99
import { markField } from "../../components/AutoSuggestion/extensions/markText";
1010
import { TestableComponent } from "../../components/interfaces";
11+
import { MarkdownToolbar } from "./toolbars/markdown.toolbar";
12+
import { Markdown } from "../../cmem/markdown/Markdown";
1113
import { CLASSPREFIX as eccgui } from "../../configuration/constants";
1214

1315
//hooks
@@ -77,7 +79,6 @@ export interface CodeEditorProps extends TestableComponent {
7779
/**
7880
* Syntax mode of the code editor.
7981
*/
80-
8182
mode?: SupportedCodeEditorModes;
8283
/**
8384
* Default value used first when the editor is instanciated.
@@ -156,6 +157,15 @@ export interface CodeEditorProps extends TestableComponent {
156157
* Disables the editor.
157158
*/
158159
disabled?: boolean;
160+
/**
161+
* Add toolbar for mode.
162+
* Currently only `markdown` is supported.
163+
*/
164+
useToolbar?: boolean;
165+
/**
166+
* Get the translation for a specific key
167+
*/
168+
translate?: (key: string) => string | false;
159169
}
160170

161171
const addExtensionsFor = (flag: boolean, ...extensions: Extension[]) => (flag ? [...extensions] : []);
@@ -168,6 +178,8 @@ const ModeLinterMap: ReadonlyMap<SupportedCodeEditorModes, ReadonlyArray<Extensi
168178
["javascript", [jsLinter]],
169179
]);
170180

181+
const ModeToolbarSupport: ReadonlyArray<SupportedCodeEditorModes> = ["markdown"];
182+
171183
/**
172184
* Includes a code editor, currently we use CodeMirror library as base.
173185
*/
@@ -203,9 +215,13 @@ export const CodeEditor = ({
203215
autoFocus = false,
204216
disabled = false,
205217
intent,
218+
useToolbar,
219+
translate,
206220
...otherCodeEditorProps
207221
}: CodeEditorProps) => {
208222
const parent = useRef<any>(undefined);
223+
const [view, setView] = React.useState<EditorView | undefined>();
224+
const [showPreview, setShowPreview] = React.useState<boolean>(false);
209225

210226
const linters = useMemo(() => {
211227
if (!mode) {
@@ -241,6 +257,14 @@ export const CodeEditor = ({
241257
}
242258
};
243259

260+
const getTranslation = (key: string): string | false => {
261+
if (translate && typeof translate === "function") {
262+
return translate(key);
263+
}
264+
265+
return false;
266+
};
267+
244268
React.useEffect(() => {
245269
const tabIndent =
246270
!!(tabIntentStyle === "tab" && mode && !(tabForceSpaceForModes ?? []).includes(mode)) || enableTab;
@@ -280,8 +304,8 @@ export const CodeEditor = ({
280304
if (onSelection)
281305
onSelection(v.state.selection.ranges.filter((r) => !r.empty).map(({ from, to }) => ({ from, to })));
282306

283-
if (onFocusChange) {
284-
v.view.dom.className += ` ${eccgui}-intent--${intent}`;
307+
if (onFocusChange && intent && !v.view.dom.classList?.contains(`${eccgui}-intent--${intent}`)) {
308+
v.view.dom.classList.add(`${eccgui}-intent--${intent}`);
285309
}
286310

287311
if (onCursorChange) {
@@ -319,6 +343,7 @@ export const CodeEditor = ({
319343
}),
320344
parent: parent.current,
321345
});
346+
setView(view);
322347

323348
if (view?.dom) {
324349
if (height) {
@@ -346,10 +371,38 @@ export const CodeEditor = ({
346371
view.destroy();
347372
if (setEditorView) {
348373
setEditorView(undefined);
374+
setView(undefined);
349375
}
350376
};
351377
}, [parent.current, mode, preventLineNumbers]);
352378

379+
const hasToolbarSupport = mode && ModeToolbarSupport.indexOf(mode) > -1 && useToolbar;
380+
381+
const editorToolbar = (mode?: SupportedCodeEditorModes): JSX.Element => {
382+
switch (mode) {
383+
case "markdown":
384+
return (
385+
<div>
386+
<div className={`${eccgui}-codeeditor__toolbar`}>
387+
<MarkdownToolbar
388+
view={view}
389+
togglePreviewStatus={() => setShowPreview((p) => !p)}
390+
showPreview={showPreview}
391+
translate={getTranslation}
392+
/>
393+
</div>
394+
{showPreview && (
395+
<div className={`${eccgui}-codeeditor__preview`}>
396+
<Markdown>{view?.state.doc.toString() ?? ""}</Markdown>
397+
</div>
398+
)}
399+
</div>
400+
);
401+
default:
402+
return <></>;
403+
}
404+
};
405+
353406
return (
354407
<div
355408
{...outerDivAttributes}
@@ -360,10 +413,13 @@ export const CodeEditor = ({
360413
data-test-id={dataTestId ? dataTestId : "codemirror-wrapper"}
361414
className={
362415
`${eccgui}-codeeditor ${eccgui}-codeeditor--mode-${mode}` +
363-
(outerDivAttributes?.className ? ` ${outerDivAttributes?.className}` : "")
416+
(outerDivAttributes?.className ? ` ${outerDivAttributes?.className}` : "") +
417+
(hasToolbarSupport ? ` ${eccgui}-codeeditor--has-toolbar` : "")
364418
}
365419
{...otherCodeEditorProps}
366-
/>
420+
>
421+
{hasToolbarSupport && editorToolbar(mode)}
422+
</div>
367423
);
368424
};
369425

src/extensions/codemirror/_codemirror.scss

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
// own vars
44
$eccgui-color-codeeditor-background: $eccgui-color-textfield-background !default;
5+
$eccgui-color-codeeditor-separation: $eccgui-color-separation-divider !default;
6+
$eccgui-size-codeeditor-height: 20rem !default;
7+
$eccgui-size-codeeditor-toolbar-height: $button-height !default;
58

69
// adjustments
710
// stylelint-disable selector-class-pattern
@@ -14,9 +17,39 @@ $eccgui-color-codeeditor-background: $eccgui-color-textfield-background !default
1417
width: 100%;
1518
}
1619

20+
&__toolbar {
21+
position: absolute;
22+
z-index: 3;
23+
left: 1px;
24+
right: 1px;
25+
top: 1px;
26+
border-radius: $pt-border-radius $pt-border-radius 0 0;
27+
border-bottom: solid 1px $eccgui-color-codeeditor-separation;
28+
background-color: $eccgui-color-codeeditor-background;
29+
}
30+
31+
&--has-toolbar {
32+
.cm-scroller {
33+
margin-top: $eccgui-size-codeeditor-toolbar-height !important;
34+
}
35+
}
36+
37+
&__preview {
38+
position: absolute;
39+
top: calc(#{$eccgui-size-codeeditor-toolbar-height} + 1px) !important;
40+
left: 1px;
41+
right: 1px;
42+
bottom: 1px;
43+
z-index: 2;
44+
padding: $button-padding;
45+
overflow-y: auto;
46+
background-color: $eccgui-color-codeeditor-background;
47+
border-radius: 0 0 $pt-border-radius $pt-border-radius;
48+
}
49+
1750
.cm-editor {
1851
width: 100%;
19-
height: 290px;
52+
height: $eccgui-size-codeeditor-height;
2053
clip-path: unset !important; // we may check later why they set inset(0) now
2154
background-color: $eccgui-color-codeeditor-background;
2255
border-radius: $pt-border-radius;
@@ -27,7 +60,7 @@ $eccgui-color-codeeditor-background: $eccgui-color-textfield-background !default
2760
&.#{eccgui}-disabled {
2861
@extend .#{$ns}-input, .#{$ns}-disabled;
2962

30-
height: 290px;
63+
height: $eccgui-size-codeeditor-height;
3164
padding: 0;
3265
}
3366

0 commit comments

Comments
 (0)