Skip to content

Commit c0ab32e

Browse files
committed
feat: allow uiPlugins handling for eui-markdown-editor
1 parent 5a61193 commit c0ab32e

File tree

3 files changed

+87
-6
lines changed

3 files changed

+87
-6
lines changed

packages/core/src/components/eui-markdown-editor-toolbar.gts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@ import MarkdownCheckmark from './eui-markdown-editor-toolbar/icons/markdown-chec
1515
import EuiToolTip from './eui-tool-tip.gts';
1616

1717
import type MarkdownActions from '../utils/markdown/markdown-actions';
18+
import type { EuiMarkdownEditorUiPlugin } from '../utils/markdown/markdown-types';
1819

1920
export interface EuiMarkdownEditorToolbarArgs {
2021
viewMode?: string;
2122
markdownActions: MarkdownActions;
2223
uiPlugins: any[];
23-
openPluginEditor?: (actionResult: ReturnType<MarkdownActions['do']>) => void;
24+
openPluginEditor?: (plugin: EuiMarkdownEditorUiPlugin) => void;
2425
onClickPreview: () => void;
2526
selectedNode: any;
2627
}
@@ -121,7 +122,7 @@ export default class EuiMarkdownEditorToolbarComponent extends Component<EuiMark
121122
handleMdButtonClick(mdButtonId: string) {
122123
const actionResult = this.args.markdownActions.do(mdButtonId);
123124

124-
if (actionResult !== true) this.args.openPluginEditor?.(actionResult);
125+
if (actionResult !== true) this.args.openPluginEditor?.(actionResult!);
125126
}
126127

127128
itemComponent(item: EuiMarkdownEditorToolbarItem) {

packages/core/src/components/eui-markdown-editor.gts

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,16 @@ import pick from 'ember-composable-helpers/helpers/pick';
1212
import { modifier } from 'ember-modifier';
1313
import set from 'ember-set-helper/helpers/set';
1414
import style from 'ember-style-modifier/modifiers/style';
15-
import { eq, not } from 'ember-truth-helpers';
15+
import { eq } from 'ember-truth-helpers';
1616
import unified from 'unified';
1717

1818
import { argOrDefaultDecorator } from '../helpers/arg-or-default';
1919
import classNames from '../helpers/class-names';
2020
import resizeObserver from '../modifiers/resize-observer';
2121
import validatableControl from '../modifiers/validatable-control';
22-
import MarkdownActions from '../utils/markdown/markdown-actions';
22+
import MarkdownActions, {
23+
insertText
24+
} from '../utils/markdown/markdown-actions';
2325
import { MODE_EDITING, MODE_VIEWING } from '../utils/markdown/markdown-modes';
2426
import {
2527
defaultParsingPlugins,
@@ -30,6 +32,7 @@ import EuiMarkdownEditorDropZone from './eui-markdown-editor-drop-zone.gts';
3032
import EuiMarkdownEditorTextArea from './eui-markdown-editor-text-area.gts';
3133
import EuiMarkdownEditorToolbar from './eui-markdown-editor-toolbar.gts';
3234
import EuiMarkdownFormat from './eui-markdown-format.gts';
35+
import EuiModal from './eui-modal.gts';
3336

3437
import type {
3538
EuiMarkdownAstNode,
@@ -127,7 +130,32 @@ export const getCursorNodeModifier = modifier(function getCursorNodeModifier(
127130
};
128131
});
129132

133+
function isNewLine(char: string | undefined): boolean {
134+
if (char == null) return true;
135+
136+
return !!char.match(/[\r\n]/);
137+
}
138+
139+
function padWithNewlinesIfNeeded(textarea: HTMLTextAreaElement, text: string) {
140+
const selectionStart = textarea.selectionStart;
141+
const selectionEnd = textarea.selectionEnd;
142+
143+
// block parsing requires two leading new lines and none trailing, but we add an extra trailing line for readability
144+
const isPrevNewLine = isNewLine(textarea.value[selectionStart - 1]);
145+
const isPrevPrevNewLine = isNewLine(textarea.value[selectionStart - 2]);
146+
const isNextNewLine = isNewLine(textarea.value[selectionEnd]);
147+
148+
// pad text with newlines as needed
149+
text = `${isPrevNewLine ? '' : '\n'}${isPrevPrevNewLine ? '' : '\n'}${text}${
150+
isNextNewLine ? '' : '\n'
151+
}`;
152+
153+
return text;
154+
}
155+
130156
export default class EuiMarkdownEditorComponent extends Component<EuiMarkdownEditorSignature> {
157+
@tracked pluginEditorPlugin?: EuiMarkdownEditorUiPlugin;
158+
131159
// Defaults
132160
@argOrDefaultDecorator(defaultParsingPlugins) declare parsingPluginList: typeof defaultParsingPlugins;
133161

@@ -157,7 +185,6 @@ export default class EuiMarkdownEditorComponent extends Component<EuiMarkdownEdi
157185
super(owner, args);
158186
this.markdownActions = new MarkdownActions(
159187
this.editorId,
160-
// @ts-expect-error
161188
this.toolbarPlugins
162189
);
163190
this.currentHeight = this.height;
@@ -235,6 +262,8 @@ export default class EuiMarkdownEditorComponent extends Component<EuiMarkdownEdi
235262
}
236263
}
237264

265+
getCursorNode = () => {};
266+
238267
@action
239268
setTextAreaRef(ref: HTMLTextAreaElement) {
240269
this.textareaRef = ref;
@@ -317,6 +346,45 @@ export default class EuiMarkdownEditorComponent extends Component<EuiMarkdownEdi
317346
this.selectedNode = node;
318347
}
319348

349+
openPluginEditor = (plugin: EuiMarkdownEditorUiPlugin) => {
350+
this.pluginEditorPlugin = plugin;
351+
};
352+
353+
onEditorPluginSave = (markdown: any, config: any) => {
354+
let { selectedNode, textareaRef } = this;
355+
356+
if (
357+
this.pluginEditorPlugin &&
358+
selectedNode &&
359+
// @ts-expect-error
360+
selectedNode.type === this.pluginEditorPlugin.name &&
361+
// @ts-expect-error
362+
selectedNode.position
363+
) {
364+
// modifying an existing node
365+
textareaRef!.setSelectionRange(
366+
// @ts-expect-error
367+
selectedNode.position.start.offset,
368+
// @ts-expect-error
369+
selectedNode.position.end.offset
370+
);
371+
} else {
372+
// creating a new node
373+
if (config.block) {
374+
// inject newlines if needed
375+
markdown = padWithNewlinesIfNeeded(textareaRef!, markdown);
376+
}
377+
}
378+
379+
insertText(textareaRef!, {
380+
text: markdown,
381+
selectionStart: undefined,
382+
selectionEnd: undefined
383+
});
384+
385+
this.pluginEditorPlugin = undefined;
386+
};
387+
320388
<template>
321389
<div
322390
class={{classNames
@@ -336,6 +404,7 @@ export default class EuiMarkdownEditorComponent extends Component<EuiMarkdownEdi
336404
@selectedNode={{this.selectedNode}}
337405
@markdownActions={{this.markdownActions}}
338406
@onClickPreview={{this.setViewMode}}
407+
@openPluginEditor={{this.openPluginEditor}}
339408
@viewMode={{this.viewMode}}
340409
@uiPlugins={{this.toolbarPlugins}}
341410
{{didInsert this.setEditorToolbarRef}}
@@ -380,6 +449,17 @@ export default class EuiMarkdownEditorComponent extends Component<EuiMarkdownEdi
380449
...attributes
381450
/>
382451
</EuiMarkdownEditorDropZone>
452+
{{#if this.pluginEditorPlugin.editor}}
453+
<EuiModal @onClose={{set this "pluginEditorPlugin" undefined}}>
454+
{{#let (component this.pluginEditorPlugin.editor) as |Editor|}}
455+
<Editor
456+
@node={{this.selectedNode}}
457+
@onCancel={{set this "pluginEditorPlugin" undefined}}
458+
@onSave={{this.onEditorPluginSave}}
459+
/>
460+
{{/let}}
461+
</EuiModal>
462+
{{/if}}
383463
</div>
384464
</div>
385465
</template>

packages/core/src/utils/markdown/markdown-types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export type EuiMarkdownEditorUiPlugin<NodeShape = any> = {
9090
label: string;
9191
iconType: IconType;
9292
};
93-
helpText?: Component;
93+
helpText?: Component | string;
9494
} & (PluginWithImmediateFormatting | PluginWithDelayedFormatting<NodeShape>);
9595

9696
export interface EuiMarkdownFormatting {

0 commit comments

Comments
 (0)