Skip to content

Commit 6a00472

Browse files
authored
feat: MarkdownEditor, MarkdownFormat, Code, Copy, InnerText (#79)
* feat: MarkdownEditor, MarkdownFormat, Code, Copy, InnerText * fix: inline code * fix: pass down useSvg arg and support passing component to eui-icon * fix: remove rehype-sanitize * feat: finish markdown editor mvp * changelog
1 parent 8612733 commit 6a00472

File tree

84 files changed

+3018
-39
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+3018
-39
lines changed

docs/installation.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ var app = new EmberApp(defaults, {
2424
},
2525
sourceDirs: [
2626
'public/assets',
27+
'../node_modules/@ember-eui/core/public',
2728
'node_modules/@elastic/eui/lib/components/icon',
2829
],
2930
},

packages/core/CHANGELOG.md

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

33
### Master
44

5+
### 1.3.0
6+
🚀 Enhancements
7+
`@ember-eui/core`
8+
- `<EuiMarkdownEditor />` `<EuiMarkdownFormat />` `<EuiCode />` `<EuiCopy />` `<EuiCodeBlock />`
9+
10+
`@ember-eui/validated-form`
11+
- `<Form.FieldMarkdownEditor />`
12+
13+
514
### 1.2.6
615
🐛 Bug / Fixes
716
- Update ember-svg-jar dependency

packages/core/addon/components/common.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
// utility types:
2+
/**
3+
* XOR for some properties applied to a type
4+
* (XOR is one of these but not both or neither)
5+
*
6+
* Usage: OneOf<typeToExtend, one | but | not | multiple | of | these | are | required>
7+
*
8+
* To require aria-label or aria-labelledby but not both
9+
* Example: OneOf<Type, 'aria-label' | 'aria-labelledby'>
10+
*/
11+
export type OneOf<T, K extends keyof T> = Omit<T, K> &
12+
{ [k in K]: Pick<Required<T>, k> & { [k1 in Exclude<K, k>]?: never } }[K];
13+
114
export interface CommonArgs {
215
'aria-label'?: string;
316
'data-test-subj'?: string;

packages/core/addon/components/eui-button-content/index.hbs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
class="euiButtonContent__icon {{@iconClasses}}"
1313
@type={{@iconType}}
1414
@size="m"
15+
@useSvg={{@useSvg}}
1516
/>
1617
{{/if}}
1718
<span class={{@textClasses}}>

packages/core/addon/components/eui-button-empty/index.hbs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
@iconType={{@iconType}}
1818
@iconSide={{@iconSide}}
1919
@iconClasses={{@iconClasses}}
20+
@useSvg={{@useSvg}}
2021
@textClasses={{class-names "euiButtonEmpty__text" @textClasses}}
2122
>
2223
{{yield}}
@@ -42,6 +43,7 @@
4243
@iconType={{@iconType}}
4344
@iconSide={{@iconSide}}
4445
@iconClasses={{@iconClasses}}
46+
@useSvg={{@useSvg}}
4547
@textClasses={{class-names "euiButtonEmpty__text" @textClasses}}
4648
>
4749
{{yield}}

packages/core/addon/components/eui-button-icon/index.hbs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
class="euiButtonIcon__icon {{@iconClasses}}"
1111
@type={{@iconType}}
1212
@size={{arg-or-default @iconSize "m"}}
13+
@useSvg={{@useSvg}}
1314
aria-hidden="true"
1415
/>
1516
{{/if}}
@@ -27,6 +28,7 @@
2728
class="euiButtonIcon__icon {{@iconClasses}}"
2829
@type={{@iconType}}
2930
@size={{@iconSize}}
31+
@useSvg={{@useSvg}}
3032
aria-hidden="true"
3133
/>
3234
{{/if}}

packages/core/addon/components/eui-button/index.hbs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
@iconType={{@iconType}}
2121
@iconSide={{@iconSide}}
2222
@iconClasses={{@iconClasses}}
23+
@useSvg={{@useSvg}}
2324
@textClasses={{class-names "euiButton__text" @textClasses}}
2425
>
2526
{{yield}}
@@ -46,6 +47,7 @@
4647
@iconType={{@iconType}}
4748
@iconSide={{@iconSide}}
4849
@iconClasses={{@iconClasses}}
50+
@useSvg={{@useSvg}}
4951
@textClasses={{class-names "euiButton__text" @textClasses}}
5052
>
5153
{{yield}}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{{#if (or (and @isCopyable @innerText) @overflowHeight)}}
2+
<div class="euiCodeBlock__controls">
3+
{{#if @overflowHeight}}
4+
<EuiButtonIcon
5+
class="euiCodeBlock__fullScreenButton"
6+
@size="s"
7+
{{on "click" @toggleFullScreen}}
8+
@iconType={{if @isFullScreen "cross" "fullScreen"}}
9+
@color="text"
10+
aria-label={{if @isFullScreen "Collapse" "Expand"}}
11+
/>
12+
13+
{{/if}}
14+
{{#if (and @isCopyable @innerText)}}
15+
<div class="euiCodeBlock__copyButton">
16+
<EuiCopy
17+
@textToCopy={{@innerText}}
18+
@beforeMessage="Copy"
19+
@afterMessage="Copied"
20+
as |copy|
21+
>
22+
<EuiButtonIcon
23+
@size="s"
24+
{{on "click" copy}}
25+
@iconType="copy"
26+
@color="text"
27+
aria-label="Copy"
28+
/>
29+
</EuiCopy>
30+
</div>
31+
{{/if}}
32+
</div>
33+
{{/if}}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
{{#let
2+
(class-names
3+
"euiCodeBlock__pre"
4+
(if (eq this.whiteSpace "pre") "euiCodeBlock__pre--whiteSpacePre")
5+
(if (eq this.whiteSpace "pre-wrap") "euiCodeBlock__pre--whiteSpacePreWrap")
6+
)
7+
(class-names "euiCodeBlock__code" @language)
8+
(class-names
9+
"euiCodeBlock"
10+
(if this.transparentBackground "euiCodeBlock--transparentBackground")
11+
(if @inline "euiCodeBlock--inline")
12+
(if (or @isCopyable @overflowHeight) "euiCodeBlock--hasControls")
13+
componentName="EuiCodeBlockImpl"
14+
paddingSize=this.paddingSize
15+
fontSize=this.fontSize
16+
)
17+
as |preClasses codeClasses classes|
18+
}}
19+
{{#if @inline}}
20+
{{#if this.isPortalTargetReady}}
21+
{{#in-element this.codeTarget}}
22+
{{yield}}
23+
{{/in-element}}
24+
<span
25+
class={{classes}}
26+
{{style (if @overflowHeight (hash maxHeight=@overflowHeight))}}
27+
><code
28+
{{did-insert (set this "code")}}
29+
class={{codeClasses}}
30+
></code></span>
31+
{{/if}}
32+
{{else}}
33+
{{! Not inline }}
34+
{{#if this.isPortalTargetReady}}
35+
{{#in-element this.codeTarget}}
36+
{{yield}}
37+
{{/in-element}}
38+
<EuiInnerText @fallback="" as |setRef innerText|>
39+
<div
40+
class={{classes}}
41+
{{style (if @overflowHeight (hash maxHeight=@overflowHeight))}}
42+
>
43+
<pre
44+
{{did-insert setRef}}
45+
class={{preClasses}}
46+
{{style (if @overflowHeight (hash maxHeight=@overflowHeight))}}
47+
><code
48+
{{did-insert (set this "code")}}
49+
class={{codeClasses}}
50+
></code></pre>
51+
52+
<EuiCodeBlockImpl::CodeBlockControls
53+
@innerText={{innerText}}
54+
@isCopyable={{@isCopyable}}
55+
@overflowHeight={{@overflowHeight}}
56+
@toggleFullScreen={{set this "isFullScreen" (not this.isFullScreen)}}
57+
@isFullScreen={{this.isFullScreen}}
58+
/>
59+
60+
{{#if this.isFullScreen}}
61+
<EuiOverlayMask>
62+
<div
63+
{{focus-trap
64+
isActive=true
65+
focusTrapOptions=(hash
66+
clickOutsideDeactivates=true returnFocusOnDeactivate=true
67+
)
68+
}}
69+
>
70+
<div
71+
class={{class-names
72+
"euiCodeBlock euiCodeBlock-paddingLarge euiCodeBlock-isFullScreen"
73+
fontSize=this.fontSize
74+
componentName="EuiCodeBlockImpl"
75+
}}
76+
>
77+
<pre class={{preClasses}}>
78+
{{!template-lint-disable}}
79+
<code
80+
{{did-insert
81+
(queue (set this "codeFullScreen") this.update)
82+
}}
83+
{{will-destroy (set this "codeFullScreen" null)}}
84+
{{did-update this.update @language}}
85+
class={{codeClasses}}
86+
tabIndex={{0}}
87+
tabIndex={{0}}
88+
{{on "keydown" this.onKeyDown}}
89+
></code>
90+
{{!template-lint-enable}}
91+
</pre>
92+
<EuiCodeBlockImpl::CodeBlockControls
93+
@innerText={{innerText}}
94+
@isCopyable={{@isCopyable}}
95+
@overflowHeight={{@overflowHeight}}
96+
@toggleFullScreen={{set
97+
this
98+
"isFullScreen"
99+
(not this.isFullScreen)
100+
}}
101+
@isFullScreen={{this.isFullScreen}}
102+
/>
103+
</div>
104+
</div>
105+
</EuiOverlayMask>
106+
{{/if}}
107+
</div>
108+
</EuiInnerText>
109+
{{/if}}
110+
{{/if}}
111+
{{/let}}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import Component from '@glimmer/component';
2+
import { action } from '@ember/object';
3+
import { tracked } from '@glimmer/tracking';
4+
import { argOrDefaultDecorator as argOrDefault } from '../../helpers/arg-or-default';
5+
import { PaddingSize, FontSize } from '../eui-code-block';
6+
//@ts-ignore
7+
import hljs from 'highlight.js';
8+
import { keys } from '../../utils/keys';
9+
import { scheduleOnce } from '@ember/runloop';
10+
11+
type EuiCodeImplArgs = {
12+
fontSize?: FontSize;
13+
14+
/**
15+
* Displays the passed code in an inline format. Also removes any margins set.
16+
*/
17+
inline?: boolean;
18+
19+
/**
20+
* Displays an icon button to copy the code snippet to the clipboard.
21+
*/
22+
isCopyable?: boolean;
23+
24+
/**
25+
* Sets the syntax highlighting for a specific language
26+
*/
27+
language?: string;
28+
overflowHeight?: number;
29+
paddingSize?: PaddingSize;
30+
transparentBackground?: boolean;
31+
/**
32+
* Specify how `white-space` inside the element is handled.
33+
* `pre` respects line breaks/white space but doesn't force them to wrap the line
34+
* `pre-wrap` respects line breaks/white space but does force them to wrap the line when necessary.
35+
*/
36+
whiteSpace?: 'pre' | 'pre-wrap';
37+
};
38+
39+
export default class EuiAccordionAccordionComponent extends Component<EuiCodeImplArgs> {
40+
// Defaults
41+
@argOrDefault(false) declare transparentBackground: boolean;
42+
@argOrDefault('l') declare paddingSize: string;
43+
@argOrDefault('s') declare fontSize: string;
44+
@argOrDefault(false) declare isCopyable: boolean;
45+
@argOrDefault('pre-wrap') declare whiteSpace: string;
46+
47+
@tracked isFullScreen: boolean = false;
48+
@tracked isPortalTargetReady: boolean = false;
49+
@tracked codeTarget: HTMLDivElement | null = null;
50+
@tracked code: HTMLElement | null = null;
51+
@tracked codeFullScreen: HTMLElement | null = null;
52+
53+
observer: MutationObserver | null = null;
54+
55+
constructor(owner: unknown, args: EuiCodeImplArgs) {
56+
super(owner, args);
57+
this.codeTarget = document.createElement('div');
58+
this.isPortalTargetReady = true;
59+
this.setupObserver();
60+
}
61+
62+
setupObserver() {
63+
this.observer?.disconnect();
64+
this.observer = new MutationObserver((mutationsList) => {
65+
if (mutationsList.length) this.update();
66+
});
67+
if (this.codeTarget) {
68+
this.update();
69+
this.observer.observe(this.codeTarget, {
70+
characterData: true,
71+
subtree: true,
72+
childList: true
73+
});
74+
}
75+
}
76+
77+
willDestroy(): void {
78+
this.observer?.disconnect();
79+
}
80+
81+
@action
82+
update() {
83+
const render = () => {
84+
const language = this.args.language;
85+
const html = (
86+
this.isPortalTargetReady && this.codeTarget?.innerHTML
87+
? this.codeTarget.innerHTML
88+
: ''
89+
).trim();
90+
91+
const code = this.code;
92+
93+
if (code) {
94+
code.innerHTML = html;
95+
}
96+
97+
if (language) {
98+
if (code) {
99+
hljs.highlightBlock(code);
100+
}
101+
}
102+
103+
if (this.codeFullScreen) {
104+
this.codeFullScreen.innerHTML = html;
105+
if (language) {
106+
hljs.highlightBlock(this.codeFullScreen);
107+
}
108+
}
109+
};
110+
scheduleOnce('afterRender', this, render);
111+
}
112+
113+
@action
114+
onKeyDown(event: KeyboardEvent): void {
115+
if (event.key === keys.ESCAPE) {
116+
event.preventDefault();
117+
event.stopPropagation();
118+
this.isFullScreen = false;
119+
}
120+
}
121+
}

0 commit comments

Comments
 (0)