@eccenca/gui-elements 24.1.0-rc.3 → 24.1.0-rc.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +57 -2
- package/dist/cjs/cmem/ActivityControl/ActivityControlWidget.js +7 -2
- package/dist/cjs/cmem/ActivityControl/ActivityControlWidget.js.map +1 -1
- package/dist/cjs/components/AutoSuggestion/AutoSuggestion.js +5 -3
- package/dist/cjs/components/AutoSuggestion/AutoSuggestion.js.map +1 -1
- package/dist/cjs/components/AutocompleteField/AutoCompleteField.js +4 -2
- package/dist/cjs/components/AutocompleteField/AutoCompleteField.js.map +1 -1
- package/dist/cjs/components/Card/CardActions.js +2 -1
- package/dist/cjs/components/Card/CardActions.js.map +1 -1
- package/dist/cjs/components/Card/CardContent.js +4 -6
- package/dist/cjs/components/Card/CardContent.js.map +1 -1
- package/dist/cjs/components/Dialog/SimpleDialog.js +3 -3
- package/dist/cjs/components/Dialog/SimpleDialog.js.map +1 -1
- package/dist/cjs/components/Icon/canonicalIconNames.js +12 -0
- package/dist/cjs/components/Icon/canonicalIconNames.js.map +1 -1
- package/dist/cjs/components/Label/Label.js +2 -1
- package/dist/cjs/components/Label/Label.js.map +1 -1
- package/dist/cjs/components/Menu/MenuItem.js +3 -2
- package/dist/cjs/components/Menu/MenuItem.js.map +1 -1
- package/dist/cjs/components/Switch/Switch.js +6 -4
- package/dist/cjs/components/Switch/Switch.js.map +1 -1
- package/dist/cjs/components/Tag/TagList.js +1 -1
- package/dist/cjs/components/Tag/TagList.js.map +1 -1
- package/dist/cjs/components/Typography/OverflowText.js +1 -1
- package/dist/cjs/components/Typography/OverflowText.js.map +1 -1
- package/dist/cjs/extensions/codemirror/CodeMirror.js +52 -23
- package/dist/cjs/extensions/codemirror/CodeMirror.js.map +1 -1
- package/dist/cjs/extensions/codemirror/tests/codemirrorTestHelper.js +4 -1
- package/dist/cjs/extensions/codemirror/tests/codemirrorTestHelper.js.map +1 -1
- package/dist/cjs/extensions/codemirror/toolbars/commands/markdown.command.js +278 -0
- package/dist/cjs/extensions/codemirror/toolbars/commands/markdown.command.js.map +1 -0
- package/dist/cjs/extensions/codemirror/toolbars/markdown.toolbar.js +47 -0
- package/dist/cjs/extensions/codemirror/toolbars/markdown.toolbar.js.map +1 -0
- package/dist/cjs/extensions/react-flow/nodes/NodeContent.js +136 -41
- package/dist/cjs/extensions/react-flow/nodes/NodeContent.js.map +1 -1
- package/dist/cjs/extensions/react-flow/nodes/nodeUtils.js +5 -6
- package/dist/cjs/extensions/react-flow/nodes/nodeUtils.js.map +1 -1
- package/dist/esm/cmem/ActivityControl/ActivityControlWidget.js +7 -2
- package/dist/esm/cmem/ActivityControl/ActivityControlWidget.js.map +1 -1
- package/dist/esm/components/AutoSuggestion/AutoSuggestion.js +5 -3
- package/dist/esm/components/AutoSuggestion/AutoSuggestion.js.map +1 -1
- package/dist/esm/components/AutocompleteField/AutoCompleteField.js +4 -3
- package/dist/esm/components/AutocompleteField/AutoCompleteField.js.map +1 -1
- package/dist/esm/components/Card/CardActions.js +2 -1
- package/dist/esm/components/Card/CardActions.js.map +1 -1
- package/dist/esm/components/Card/CardContent.js +4 -5
- package/dist/esm/components/Card/CardContent.js.map +1 -1
- package/dist/esm/components/Dialog/SimpleDialog.js +4 -4
- package/dist/esm/components/Dialog/SimpleDialog.js.map +1 -1
- package/dist/esm/components/Icon/canonicalIconNames.js +12 -0
- package/dist/esm/components/Icon/canonicalIconNames.js.map +1 -1
- package/dist/esm/components/Label/Label.js +2 -1
- package/dist/esm/components/Label/Label.js.map +1 -1
- package/dist/esm/components/Menu/MenuItem.js +3 -2
- package/dist/esm/components/Menu/MenuItem.js.map +1 -1
- package/dist/esm/components/Switch/Switch.js +7 -5
- package/dist/esm/components/Switch/Switch.js.map +1 -1
- package/dist/esm/components/Tag/TagList.js +1 -1
- package/dist/esm/components/Tag/TagList.js.map +1 -1
- package/dist/esm/components/Typography/OverflowText.js +1 -1
- package/dist/esm/components/Typography/OverflowText.js.map +1 -1
- package/dist/esm/extensions/codemirror/CodeMirror.js +53 -24
- package/dist/esm/extensions/codemirror/CodeMirror.js.map +1 -1
- package/dist/esm/extensions/codemirror/tests/codemirrorTestHelper.js +4 -0
- package/dist/esm/extensions/codemirror/tests/codemirrorTestHelper.js.map +1 -1
- package/dist/esm/extensions/codemirror/toolbars/commands/markdown.command.js +283 -0
- package/dist/esm/extensions/codemirror/toolbars/commands/markdown.command.js.map +1 -0
- package/dist/esm/extensions/codemirror/toolbars/markdown.toolbar.js +41 -0
- package/dist/esm/extensions/codemirror/toolbars/markdown.toolbar.js.map +1 -0
- package/dist/esm/extensions/react-flow/nodes/NodeContent.js +145 -48
- package/dist/esm/extensions/react-flow/nodes/NodeContent.js.map +1 -1
- package/dist/esm/extensions/react-flow/nodes/nodeUtils.js +5 -6
- package/dist/esm/extensions/react-flow/nodes/nodeUtils.js.map +1 -1
- package/dist/types/cmem/ActivityControl/ActivityControlWidget.d.ts +1 -1
- package/dist/types/components/Card/CardActions.d.ts +5 -1
- package/dist/types/components/Card/CardContent.d.ts +1 -2
- package/dist/types/components/Dialog/SimpleDialog.d.ts +4 -1
- package/dist/types/components/Icon/canonicalIconNames.d.ts +12 -0
- package/dist/types/components/Label/Label.d.ts +3 -1
- package/dist/types/components/Menu/MenuItem.d.ts +8 -1
- package/dist/types/components/Switch/Switch.d.ts +3 -3
- package/dist/types/components/Typography/OverflowText.d.ts +23 -2
- package/dist/types/extensions/codemirror/CodeMirror.d.ts +10 -1
- package/dist/types/extensions/codemirror/tests/codemirrorTestHelper.d.ts +1 -0
- package/dist/types/extensions/codemirror/toolbars/commands/markdown.command.d.ts +55 -0
- package/dist/types/extensions/codemirror/toolbars/markdown.toolbar.d.ts +12 -0
- package/dist/types/extensions/react-flow/nodes/NodeContent.d.ts +18 -4
- package/dist/types/extensions/react-flow/nodes/nodeUtils.d.ts +7 -6
- package/package.json +22 -20
- package/src/cmem/ActivityControl/ActivityControlWidget.tsx +5 -2
- package/src/cmem/react-flow/configuration/_colors-graph.scss +4 -1
- package/src/cmem/react-flow/configuration/_colors-workflow.scss +3 -0
- package/src/components/AutoSuggestion/AutoSuggestion.tsx +5 -3
- package/src/components/AutocompleteField/AutoCompleteField.tsx +5 -3
- package/src/components/Card/CardActions.tsx +6 -0
- package/src/components/Card/CardContent.tsx +8 -4
- package/src/components/Card/card.scss +15 -0
- package/src/components/CodeAutocompleteField/CodeAutocompleteField.stories.tsx +3 -2
- package/src/components/ContextOverlay/ContextOverlay.stories.tsx +15 -4
- package/src/components/Dialog/SimpleDialog.tsx +9 -2
- package/src/components/Dialog/stories/AlertDialog.stories.tsx +5 -1
- package/src/components/Dialog/stories/Modal.stories.tsx +4 -2
- package/src/components/Dialog/stories/SimpleDialog.stories.tsx +5 -2
- package/src/components/Icon/canonicalIconNames.tsx +12 -0
- package/src/components/Label/Label.tsx +4 -0
- package/src/components/Label/label.scss +5 -1
- package/src/components/Menu/MenuItem.tsx +27 -1
- package/src/components/Menu/menu.scss +1 -0
- package/src/components/OverviewItem/overviewitem.scss +4 -1
- package/src/components/Select/Select.stories.tsx +4 -1
- package/src/components/Switch/Switch.tsx +27 -8
- package/src/components/Tag/TagList.tsx +2 -2
- package/src/components/Typography/OverflowText.tsx +24 -3
- package/src/components/Typography/stories/OverflowText.stories.tsx +33 -0
- package/src/extensions/codemirror/CodeMirror.stories.tsx +5 -17
- package/src/extensions/codemirror/CodeMirror.tsx +82 -22
- package/src/extensions/codemirror/_codemirror.scss +35 -2
- package/src/extensions/codemirror/tests/codemirrorTestHelper.ts +4 -0
- package/src/extensions/codemirror/toolbars/commands/markdown.command.ts +340 -0
- package/src/extensions/codemirror/toolbars/markdown.toolbar.tsx +117 -0
- package/src/extensions/react-flow/_config.scss +1 -0
- package/src/extensions/react-flow/nodes/NodeContent.tsx +166 -52
- package/src/extensions/react-flow/nodes/_nodes.scss +71 -35
- package/src/extensions/react-flow/nodes/nodeUtils.tsx +16 -14
- package/src/extensions/react-flow/nodes/stories/NodeContent.stories.tsx +6 -3
|
@@ -19,13 +19,17 @@ export default {
|
|
|
19
19
|
},
|
|
20
20
|
} as Meta<typeof CodeEditor>;
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
let forcedUpdateKey = 0; // @see https://github.com/storybookjs/storybook/issues/13375#issuecomment-1291011856
|
|
23
|
+
const TemplateFull: StoryFn<typeof CodeEditor> = (args) => <CodeEditor {...args} key={++forcedUpdateKey} />;
|
|
23
24
|
|
|
24
25
|
export const BasicExample = TemplateFull.bind({});
|
|
25
26
|
BasicExample.args = {
|
|
26
27
|
name: "codeinput",
|
|
27
28
|
mode: "markdown",
|
|
28
29
|
defaultValue: "**test me**",
|
|
30
|
+
useToolbar: true,
|
|
31
|
+
disabled: false,
|
|
32
|
+
readOnly: true,
|
|
29
33
|
};
|
|
30
34
|
|
|
31
35
|
export const LinterExample = TemplateFull.bind({});
|
|
@@ -36,19 +40,3 @@ LinterExample.args = {
|
|
|
36
40
|
useLinting: true,
|
|
37
41
|
autoFocus: true,
|
|
38
42
|
};
|
|
39
|
-
|
|
40
|
-
export const DisabledExample = TemplateFull.bind({});
|
|
41
|
-
DisabledExample.args = {
|
|
42
|
-
name: "codeinput",
|
|
43
|
-
defaultValue: "**test me**",
|
|
44
|
-
mode: "javascript",
|
|
45
|
-
disabled: true,
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
export const IntentExample = TemplateFull.bind({});
|
|
49
|
-
IntentExample.args = {
|
|
50
|
-
name: "codeinput",
|
|
51
|
-
defaultValue: "**test me**",
|
|
52
|
-
mode: "javascript",
|
|
53
|
-
intent: "warning",
|
|
54
|
-
};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React, { useMemo, useRef } from "react";
|
|
2
2
|
import { defaultKeymap, indentWithTab } from "@codemirror/commands";
|
|
3
3
|
import { foldKeymap } from "@codemirror/language";
|
|
4
|
-
import { lintGutter } from "@codemirror/lint";
|
|
5
4
|
import { EditorState, Extension } from "@codemirror/state";
|
|
6
5
|
import { DOMEventHandlers, EditorView, KeyBinding, keymap, Rect, ViewUpdate } from "@codemirror/view";
|
|
7
6
|
import { minimalSetup } from "codemirror";
|
|
@@ -9,6 +8,8 @@ import { minimalSetup } from "codemirror";
|
|
|
9
8
|
import { IntentTypes } from "../../common/Intent";
|
|
10
9
|
import { markField } from "../../components/AutoSuggestion/extensions/markText";
|
|
11
10
|
import { TestableComponent } from "../../components/interfaces";
|
|
11
|
+
import { MarkdownToolbar } from "./toolbars/markdown.toolbar";
|
|
12
|
+
import { Markdown } from "../../cmem/markdown/Markdown";
|
|
12
13
|
import { CLASSPREFIX as eccgui } from "../../configuration/constants";
|
|
13
14
|
|
|
14
15
|
//hooks
|
|
@@ -28,6 +29,7 @@ import {
|
|
|
28
29
|
adaptedHighlightActiveLine,
|
|
29
30
|
adaptedHighlightSpecialChars,
|
|
30
31
|
adaptedLineNumbers,
|
|
32
|
+
adaptedLintGutter,
|
|
31
33
|
adaptedPlaceholder,
|
|
32
34
|
} from "./tests/codemirrorTestHelper";
|
|
33
35
|
import { ExtensionCreator } from "./types";
|
|
@@ -77,7 +79,6 @@ export interface CodeEditorProps extends TestableComponent {
|
|
|
77
79
|
/**
|
|
78
80
|
* Syntax mode of the code editor.
|
|
79
81
|
*/
|
|
80
|
-
|
|
81
82
|
mode?: SupportedCodeEditorModes;
|
|
82
83
|
/**
|
|
83
84
|
* Default value used first when the editor is instanciated.
|
|
@@ -156,6 +157,15 @@ export interface CodeEditorProps extends TestableComponent {
|
|
|
156
157
|
* Disables the editor.
|
|
157
158
|
*/
|
|
158
159
|
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;
|
|
159
169
|
}
|
|
160
170
|
|
|
161
171
|
const addExtensionsFor = (flag: boolean, ...extensions: Extension[]) => (flag ? [...extensions] : []);
|
|
@@ -168,6 +178,8 @@ const ModeLinterMap: ReadonlyMap<SupportedCodeEditorModes, ReadonlyArray<Extensi
|
|
|
168
178
|
["javascript", [jsLinter]],
|
|
169
179
|
]);
|
|
170
180
|
|
|
181
|
+
const ModeToolbarSupport: ReadonlyArray<SupportedCodeEditorModes> = ["markdown"];
|
|
182
|
+
|
|
171
183
|
/**
|
|
172
184
|
* Includes a code editor, currently we use CodeMirror library as base.
|
|
173
185
|
*/
|
|
@@ -203,16 +215,20 @@ export const CodeEditor = ({
|
|
|
203
215
|
autoFocus = false,
|
|
204
216
|
disabled = false,
|
|
205
217
|
intent,
|
|
218
|
+
useToolbar,
|
|
219
|
+
translate,
|
|
206
220
|
...otherCodeEditorProps
|
|
207
221
|
}: CodeEditorProps) => {
|
|
208
222
|
const parent = useRef<any>(undefined);
|
|
223
|
+
const [view, setView] = React.useState<EditorView | undefined>();
|
|
224
|
+
const [showPreview, setShowPreview] = React.useState<boolean>(false);
|
|
209
225
|
|
|
210
226
|
const linters = useMemo(() => {
|
|
211
227
|
if (!mode) {
|
|
212
228
|
return [];
|
|
213
229
|
}
|
|
214
230
|
|
|
215
|
-
const values = [
|
|
231
|
+
const values = [adaptedLintGutter()];
|
|
216
232
|
|
|
217
233
|
const linters = ModeLinterMap.get(mode);
|
|
218
234
|
if (linters) {
|
|
@@ -241,6 +257,14 @@ export const CodeEditor = ({
|
|
|
241
257
|
}
|
|
242
258
|
};
|
|
243
259
|
|
|
260
|
+
const getTranslation = (key: string): string | false => {
|
|
261
|
+
if (translate && typeof translate === "function") {
|
|
262
|
+
return translate(key);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return false;
|
|
266
|
+
};
|
|
267
|
+
|
|
244
268
|
React.useEffect(() => {
|
|
245
269
|
const tabIndent =
|
|
246
270
|
!!(tabIntentStyle === "tab" && mode && !(tabForceSpaceForModes ?? []).includes(mode)) || enableTab;
|
|
@@ -280,8 +304,8 @@ export const CodeEditor = ({
|
|
|
280
304
|
if (onSelection)
|
|
281
305
|
onSelection(v.state.selection.ranges.filter((r) => !r.empty).map(({ from, to }) => ({ from, to })));
|
|
282
306
|
|
|
283
|
-
if (onFocusChange) {
|
|
284
|
-
v.view.dom.
|
|
307
|
+
if (onFocusChange && intent && !v.view.dom.classList?.contains(`${eccgui}-intent--${intent}`)) {
|
|
308
|
+
v.view.dom.classList.add(`${eccgui}-intent--${intent}`);
|
|
285
309
|
}
|
|
286
310
|
|
|
287
311
|
if (onCursorChange) {
|
|
@@ -319,34 +343,67 @@ export const CodeEditor = ({
|
|
|
319
343
|
}),
|
|
320
344
|
parent: parent.current,
|
|
321
345
|
});
|
|
346
|
+
setView(view);
|
|
322
347
|
|
|
323
|
-
if (
|
|
324
|
-
|
|
325
|
-
|
|
348
|
+
if (view?.dom) {
|
|
349
|
+
if (height) {
|
|
350
|
+
view.dom.style.height = typeof height === "string" ? height : `${height}px`;
|
|
351
|
+
}
|
|
326
352
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
353
|
+
if (disabled) {
|
|
354
|
+
view.dom.className += ` ${eccgui}-disabled`;
|
|
355
|
+
}
|
|
330
356
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
357
|
+
if (intent) {
|
|
358
|
+
view.dom.className += ` ${eccgui}-intent--${intent}`;
|
|
359
|
+
}
|
|
334
360
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
361
|
+
if (autoFocus) {
|
|
362
|
+
view.focus();
|
|
363
|
+
}
|
|
338
364
|
|
|
339
|
-
|
|
340
|
-
|
|
365
|
+
if (setEditorView) {
|
|
366
|
+
setEditorView(view);
|
|
367
|
+
}
|
|
341
368
|
}
|
|
342
369
|
|
|
343
370
|
return () => {
|
|
344
371
|
view.destroy();
|
|
345
372
|
if (setEditorView) {
|
|
346
373
|
setEditorView(undefined);
|
|
374
|
+
setView(undefined);
|
|
347
375
|
}
|
|
348
376
|
};
|
|
349
|
-
}, [parent.current, mode, preventLineNumbers]);
|
|
377
|
+
}, [parent.current, mode, preventLineNumbers, wrapLines]);
|
|
378
|
+
|
|
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
|
+
disabled={disabled}
|
|
393
|
+
readonly={readOnly}
|
|
394
|
+
/>
|
|
395
|
+
</div>
|
|
396
|
+
{showPreview && (
|
|
397
|
+
<div className={`${eccgui}-codeeditor__preview`}>
|
|
398
|
+
<Markdown>{view?.state.doc.toString() ?? ""}</Markdown>
|
|
399
|
+
</div>
|
|
400
|
+
)}
|
|
401
|
+
</div>
|
|
402
|
+
);
|
|
403
|
+
default:
|
|
404
|
+
return <></>;
|
|
405
|
+
}
|
|
406
|
+
};
|
|
350
407
|
|
|
351
408
|
return (
|
|
352
409
|
<div
|
|
@@ -358,10 +415,13 @@ export const CodeEditor = ({
|
|
|
358
415
|
data-test-id={dataTestId ? dataTestId : "codemirror-wrapper"}
|
|
359
416
|
className={
|
|
360
417
|
`${eccgui}-codeeditor ${eccgui}-codeeditor--mode-${mode}` +
|
|
361
|
-
(outerDivAttributes?.className ? ` ${outerDivAttributes?.className}` : "")
|
|
418
|
+
(outerDivAttributes?.className ? ` ${outerDivAttributes?.className}` : "") +
|
|
419
|
+
(hasToolbarSupport ? ` ${eccgui}-codeeditor--has-toolbar` : "")
|
|
362
420
|
}
|
|
363
421
|
{...otherCodeEditorProps}
|
|
364
|
-
|
|
422
|
+
>
|
|
423
|
+
{hasToolbarSupport && editorToolbar(mode)}
|
|
424
|
+
</div>
|
|
365
425
|
);
|
|
366
426
|
};
|
|
367
427
|
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
// own vars
|
|
4
4
|
$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;
|
|
5
8
|
|
|
6
9
|
// adjustments
|
|
7
10
|
// stylelint-disable selector-class-pattern
|
|
@@ -14,9 +17,39 @@ $eccgui-color-codeeditor-background: $eccgui-color-textfield-background !default
|
|
|
14
17
|
width: 100%;
|
|
15
18
|
}
|
|
16
19
|
|
|
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
|
+
|
|
17
50
|
.cm-editor {
|
|
18
51
|
width: 100%;
|
|
19
|
-
height:
|
|
52
|
+
height: $eccgui-size-codeeditor-height;
|
|
20
53
|
clip-path: unset !important; // we may check later why they set inset(0) now
|
|
21
54
|
background-color: $eccgui-color-codeeditor-background;
|
|
22
55
|
border-radius: $pt-border-radius;
|
|
@@ -27,7 +60,7 @@ $eccgui-color-codeeditor-background: $eccgui-color-textfield-background !default
|
|
|
27
60
|
&.#{eccgui}-disabled {
|
|
28
61
|
@extend .#{$ns}-input, .#{$ns}-disabled;
|
|
29
62
|
|
|
30
|
-
height:
|
|
63
|
+
height: $eccgui-size-codeeditor-height;
|
|
31
64
|
padding: 0;
|
|
32
65
|
}
|
|
33
66
|
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { EditorView, placeholder, highlightSpecialChars, lineNumbers, highlightActiveLine } from "@codemirror/view";
|
|
11
11
|
import { syntaxHighlighting, foldGutter, codeFolding } from "@codemirror/language";
|
|
12
12
|
import { Extension } from "@codemirror/state";
|
|
13
|
+
import { lintGutter } from "@codemirror/lint";
|
|
13
14
|
|
|
14
15
|
/** placeholder extension, current error '_view.placeholder is not a function' */
|
|
15
16
|
export const adaptedPlaceholder = (text?: string) =>
|
|
@@ -55,3 +56,6 @@ export const adaptedFoldGutter = (props?: any) =>
|
|
|
55
56
|
|
|
56
57
|
export const adaptedCodeFolding = (props?: any) =>
|
|
57
58
|
typeof codeFolding === "function" ? codeFolding(props) : emptyExtension;
|
|
59
|
+
|
|
60
|
+
export const adaptedLintGutter = (props?: any) =>
|
|
61
|
+
typeof lintGutter === "function" ? lintGutter(props) : emptyExtension;
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import { type ChangeSpec, EditorSelection } from "@codemirror/state";
|
|
2
|
+
import { EditorView } from "codemirror";
|
|
3
|
+
|
|
4
|
+
import { ValidIconName } from "../../../../components/Icon/canonicalIconNames";
|
|
5
|
+
|
|
6
|
+
enum Commands {
|
|
7
|
+
header1 = "Heading 1",
|
|
8
|
+
header2 = "Heading 2",
|
|
9
|
+
header3 = "Heading 3",
|
|
10
|
+
header4 = "Heading 4",
|
|
11
|
+
header5 = "Heading 5",
|
|
12
|
+
header6 = "Heading 6",
|
|
13
|
+
codeBlock = "Code block",
|
|
14
|
+
quote = "Block quote",
|
|
15
|
+
bold = "Bold",
|
|
16
|
+
italic = "Italic",
|
|
17
|
+
strike = "StrikeThrough",
|
|
18
|
+
inlineCode = "Inline code",
|
|
19
|
+
unorderedList = "Unordered list",
|
|
20
|
+
orderedList = "Ordered list",
|
|
21
|
+
todoList = "Todo list",
|
|
22
|
+
link = "Link",
|
|
23
|
+
image = "Image",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type formatConfig = { start: number; startDelimiter: string; stop?: number; endDelimiter?: string };
|
|
27
|
+
type headerLevels = 1 | 2 | 3 | 4 | 5 | 6;
|
|
28
|
+
type ListType = "ul" | "ol" | "todo";
|
|
29
|
+
|
|
30
|
+
//contains all utilities for markdown toolbar
|
|
31
|
+
export default class MarkdownCommand {
|
|
32
|
+
private view: EditorView | null = null;
|
|
33
|
+
|
|
34
|
+
//list of supported commands as well as the valid icon names.
|
|
35
|
+
public static commands = {
|
|
36
|
+
paragraphs: [
|
|
37
|
+
Commands.header1,
|
|
38
|
+
Commands.header2,
|
|
39
|
+
Commands.header3,
|
|
40
|
+
Commands.header4,
|
|
41
|
+
Commands.header5,
|
|
42
|
+
Commands.header6,
|
|
43
|
+
Commands.quote,
|
|
44
|
+
Commands.codeBlock,
|
|
45
|
+
],
|
|
46
|
+
basic: [
|
|
47
|
+
{ title: Commands.bold, icon: "operation-format-text-bold" },
|
|
48
|
+
{ title: Commands.italic, icon: "operation-format-text-italic" },
|
|
49
|
+
{ title: Commands.strike, icon: "operation-format-text-strikethrough" },
|
|
50
|
+
{ title: Commands.inlineCode, icon: "operation-format-text-code" },
|
|
51
|
+
] as { title: Commands; icon: ValidIconName }[],
|
|
52
|
+
lists: [
|
|
53
|
+
{ title: Commands.unorderedList, icon: "operation-format-list-bullet", moniker: "ul" },
|
|
54
|
+
{ title: Commands.orderedList, icon: "operation-format-list-numbered", moniker: "ol" },
|
|
55
|
+
{ title: Commands.todoList, icon: "operation-format-list-checked", moniker: "todo" },
|
|
56
|
+
] as { title: Commands; icon: ValidIconName; moniker: string }[],
|
|
57
|
+
attachments: [
|
|
58
|
+
{ title: Commands.link, icon: "operation-link" },
|
|
59
|
+
{ title: Commands.image, icon: "item-image" },
|
|
60
|
+
] as { title: Commands; icon: ValidIconName }[],
|
|
61
|
+
} as const;
|
|
62
|
+
|
|
63
|
+
constructor(view: EditorView) {
|
|
64
|
+
this.view = view;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Supported list types are ol, ul, todo.
|
|
69
|
+
* utility helps to determine which at the start of the line
|
|
70
|
+
*/
|
|
71
|
+
private getListTypeOfLine = (text: string): [ListType, number?] | undefined => {
|
|
72
|
+
if (!text) return;
|
|
73
|
+
text = text?.trimStart();
|
|
74
|
+
|
|
75
|
+
if (text.startsWith("- ")) {
|
|
76
|
+
if (text.startsWith("- [ ] ") || text.startsWith("- [x] ")) return ["todo"];
|
|
77
|
+
return ["ul"];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const v = text.match(/^(\d+)\. /);
|
|
81
|
+
|
|
82
|
+
return v ? ["ol", Number.parseInt(v[1], 10)] : undefined;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
//inserts the list delimiters of "-", "- [ ]" and "{number}."
|
|
86
|
+
private createListDelimiter(text: string, type: string, orderedList: { currentIndex: number }) {
|
|
87
|
+
return text.replace(/^(( *)(-( \[[x ]])?|\d+\.) )?/, (...args) => {
|
|
88
|
+
const { space = "" } = args[args.length - 1];
|
|
89
|
+
|
|
90
|
+
let newFlag = "- ";
|
|
91
|
+
|
|
92
|
+
if (type === "ol") {
|
|
93
|
+
newFlag = `${orderedList.currentIndex}. `;
|
|
94
|
+
orderedList.currentIndex++;
|
|
95
|
+
} else if (type === "todo") {
|
|
96
|
+
newFlag = "- [ ] ";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return space + newFlag;
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
//factory for different list types.
|
|
104
|
+
private createList = (type: ListType) => {
|
|
105
|
+
if (!this.view) return;
|
|
106
|
+
const view = this.view;
|
|
107
|
+
const doc = view.state.doc;
|
|
108
|
+
|
|
109
|
+
const orderedList = { currentIndex: 1 };
|
|
110
|
+
|
|
111
|
+
view.dispatch(
|
|
112
|
+
view.state.changeByRange((range) => {
|
|
113
|
+
const text = doc.slice(range.from, range.to);
|
|
114
|
+
const changes: ChangeSpec[] = [];
|
|
115
|
+
|
|
116
|
+
let selectionStart: number = range.from;
|
|
117
|
+
let selectionLength: number = range.to - range.from;
|
|
118
|
+
|
|
119
|
+
Array.from({ length: text.lines }).forEach((_, index) => {
|
|
120
|
+
const line = doc.line(doc.lineAt(range.from).number + index);
|
|
121
|
+
|
|
122
|
+
const currentListType = this.getListTypeOfLine(line.text);
|
|
123
|
+
|
|
124
|
+
if (currentListType && currentListType[0] === type) {
|
|
125
|
+
if (currentListType[0] === "ol" && currentListType[1]) {
|
|
126
|
+
orderedList.currentIndex = currentListType[1];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const content = this.createListDelimiter(line.text, type, orderedList);
|
|
132
|
+
|
|
133
|
+
const diffLength = content.length - line.length;
|
|
134
|
+
|
|
135
|
+
changes.push({
|
|
136
|
+
from: line.from,
|
|
137
|
+
to: line.to,
|
|
138
|
+
insert: content,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
if (index === 0) {
|
|
142
|
+
selectionStart = selectionStart + diffLength;
|
|
143
|
+
} else {
|
|
144
|
+
selectionLength = selectionLength + diffLength;
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
changes,
|
|
150
|
+
range: EditorSelection.range(selectionStart, selectionStart + selectionLength),
|
|
151
|
+
};
|
|
152
|
+
})
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
view.focus();
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
private enforceCursorFocus = (cursorPosition: number) => {
|
|
159
|
+
if (!this.view) return;
|
|
160
|
+
const view = this.view;
|
|
161
|
+
setTimeout(() => {
|
|
162
|
+
view.dispatch({
|
|
163
|
+
selection: EditorSelection.cursor(cursorPosition),
|
|
164
|
+
});
|
|
165
|
+
view.focus();
|
|
166
|
+
}, 50);
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
//supported headers from h1-h6, h6 being the smallest
|
|
170
|
+
private createHeading = (level: headerLevels) => {
|
|
171
|
+
if (!this.view) return;
|
|
172
|
+
const view = this.view;
|
|
173
|
+
const state = view.state;
|
|
174
|
+
|
|
175
|
+
const flags = "#".repeat(level) + " ";
|
|
176
|
+
|
|
177
|
+
let lastCursorPosition = 0;
|
|
178
|
+
|
|
179
|
+
view.dispatch(
|
|
180
|
+
state.changeByRange((range) => {
|
|
181
|
+
const line = state.doc.lineAt(range.from);
|
|
182
|
+
|
|
183
|
+
const content = line.text.replace(/^((#+) )?/, flags);
|
|
184
|
+
|
|
185
|
+
const diffLength = content.length - line.length;
|
|
186
|
+
lastCursorPosition = line.to + diffLength;
|
|
187
|
+
return {
|
|
188
|
+
changes: {
|
|
189
|
+
from: line.from,
|
|
190
|
+
to: line.to,
|
|
191
|
+
insert: content,
|
|
192
|
+
},
|
|
193
|
+
range: EditorSelection.range(range.anchor + diffLength, range.head + diffLength),
|
|
194
|
+
};
|
|
195
|
+
})
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
this.enforceCursorFocus(lastCursorPosition);
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
private applyFormatting = ({
|
|
202
|
+
start,
|
|
203
|
+
startDelimiter,
|
|
204
|
+
endDelimiter = startDelimiter,
|
|
205
|
+
stop = start,
|
|
206
|
+
}: formatConfig) => {
|
|
207
|
+
if (!this.view) return;
|
|
208
|
+
const view = this.view;
|
|
209
|
+
const { from, to } = view.state.selection.main;
|
|
210
|
+
const text = view.state.sliceDoc(from, to);
|
|
211
|
+
view.dispatch(
|
|
212
|
+
view.state.changeByRange((range) => {
|
|
213
|
+
return {
|
|
214
|
+
changes: [{ from: range.from, to: range.to, insert: `${startDelimiter}${text}${endDelimiter}` }],
|
|
215
|
+
range: EditorSelection.range(range.from + start, range.to + stop),
|
|
216
|
+
};
|
|
217
|
+
})
|
|
218
|
+
);
|
|
219
|
+
view.focus();
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
private applyAttachment = (type: Commands.link | Commands.image) => {
|
|
223
|
+
if (!this.view) return;
|
|
224
|
+
const view = this.view;
|
|
225
|
+
const { state } = view;
|
|
226
|
+
const isImageAttachmentType = type === Commands.image;
|
|
227
|
+
|
|
228
|
+
const { doc } = state;
|
|
229
|
+
|
|
230
|
+
view.dispatch(
|
|
231
|
+
state.changeByRange((range) => {
|
|
232
|
+
const { from, to } = range;
|
|
233
|
+
|
|
234
|
+
const text = doc.sliceString(from, to);
|
|
235
|
+
|
|
236
|
+
const link = `${isImageAttachmentType ? `!` : ""}[${text}]()`;
|
|
237
|
+
|
|
238
|
+
const cursor = from + (text.length ? 3 + text.length : 1 + Number(isImageAttachmentType));
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
changes: [
|
|
242
|
+
{
|
|
243
|
+
from,
|
|
244
|
+
to,
|
|
245
|
+
insert: link,
|
|
246
|
+
},
|
|
247
|
+
],
|
|
248
|
+
range: EditorSelection.range(cursor, cursor),
|
|
249
|
+
};
|
|
250
|
+
})
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
view.focus();
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
private applyQuoteFormatting = () => {
|
|
257
|
+
if (!this.view) return;
|
|
258
|
+
const view = this.view;
|
|
259
|
+
const { state } = view;
|
|
260
|
+
const { doc } = state;
|
|
261
|
+
|
|
262
|
+
let lastCursorPosition = 0;
|
|
263
|
+
|
|
264
|
+
view.dispatch(
|
|
265
|
+
view.state.changeByRange((range) => {
|
|
266
|
+
const startLine = doc.lineAt(range.from);
|
|
267
|
+
|
|
268
|
+
const text = doc.slice(range.from, range.to);
|
|
269
|
+
|
|
270
|
+
const lineCount = text.lines;
|
|
271
|
+
|
|
272
|
+
const changes: ChangeSpec[] = [];
|
|
273
|
+
|
|
274
|
+
let selectionStart: number = range.from;
|
|
275
|
+
let selectionLength: number = range.to - range.from;
|
|
276
|
+
|
|
277
|
+
new Array(lineCount).fill(0).forEach((_, index) => {
|
|
278
|
+
const line = doc.line(startLine.number + index);
|
|
279
|
+
|
|
280
|
+
if (line.text.startsWith("> ")) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
changes.push({
|
|
284
|
+
from: line.from,
|
|
285
|
+
insert: "> ",
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
if (index === 0) {
|
|
289
|
+
selectionStart = selectionStart + 2;
|
|
290
|
+
} else {
|
|
291
|
+
selectionLength += 2;
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
lastCursorPosition = selectionStart + selectionLength;
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
changes,
|
|
299
|
+
range: EditorSelection.range(selectionStart, selectionStart + selectionLength),
|
|
300
|
+
};
|
|
301
|
+
})
|
|
302
|
+
);
|
|
303
|
+
this.enforceCursorFocus(lastCursorPosition);
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
executeCommand = (command: Commands): true | void => {
|
|
307
|
+
switch (command) {
|
|
308
|
+
case Commands.bold:
|
|
309
|
+
return this.applyFormatting({ start: 2, startDelimiter: "**" });
|
|
310
|
+
case Commands.italic:
|
|
311
|
+
return this.applyFormatting({ start: 1, startDelimiter: "*" });
|
|
312
|
+
case Commands.codeBlock:
|
|
313
|
+
return this.applyFormatting({ start: 3, startDelimiter: "```\n", endDelimiter: "\n```" });
|
|
314
|
+
case Commands.strike:
|
|
315
|
+
return this.applyFormatting({ start: 2, startDelimiter: "~~" });
|
|
316
|
+
case Commands.inlineCode:
|
|
317
|
+
return this.applyFormatting({ start: 1, startDelimiter: "`" });
|
|
318
|
+
case Commands.header1:
|
|
319
|
+
case Commands.header2:
|
|
320
|
+
case Commands.header3:
|
|
321
|
+
case Commands.header4:
|
|
322
|
+
case Commands.header5:
|
|
323
|
+
case Commands.header6:
|
|
324
|
+
return this.createHeading(Number(command.slice(-1)) as headerLevels);
|
|
325
|
+
case Commands.unorderedList:
|
|
326
|
+
case Commands.orderedList:
|
|
327
|
+
case Commands.todoList:
|
|
328
|
+
return this.createList(
|
|
329
|
+
MarkdownCommand.commands.lists.find((l) => l.title === command)?.moniker as ListType
|
|
330
|
+
);
|
|
331
|
+
case Commands.image:
|
|
332
|
+
case Commands.link:
|
|
333
|
+
return this.applyAttachment(command);
|
|
334
|
+
case Commands.quote:
|
|
335
|
+
return this.applyQuoteFormatting();
|
|
336
|
+
default:
|
|
337
|
+
return; //do nothing;
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
}
|