@aquera/nile-elements 1.8.4 → 1.8.6
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/README.md +7 -0
- package/demo/nxtgen-classes.css +10658 -19
- package/demo/nxtgen-utilities.css +10658 -19
- package/dist/index.cjs.js +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.js +849 -300
- package/dist/nile-filter-chip/nile-filter-chip.cjs.js +1 -1
- package/dist/nile-filter-chip/nile-filter-chip.cjs.js.map +1 -1
- package/dist/nile-filter-chip/nile-filter-chip.esm.js +1 -1
- package/dist/nile-markdown/index.cjs.js +2 -0
- package/dist/nile-markdown/index.cjs.js.map +1 -0
- package/dist/nile-markdown/index.esm.js +1 -0
- package/dist/nile-markdown/nile-markdown.cjs.js +30 -0
- package/dist/nile-markdown/nile-markdown.cjs.js.map +1 -0
- package/dist/nile-markdown/nile-markdown.css.cjs.js +2 -0
- package/dist/nile-markdown/nile-markdown.css.cjs.js.map +1 -0
- package/dist/nile-markdown/nile-markdown.css.esm.js +152 -0
- package/dist/nile-markdown/nile-markdown.esm.js +3 -0
- package/dist/nile-markdown-editor/index.cjs.js +2 -0
- package/dist/nile-markdown-editor/index.cjs.js.map +1 -0
- package/dist/nile-markdown-editor/index.esm.js +1 -0
- package/dist/nile-markdown-editor/nile-markdown-editor.cjs.js +2 -0
- package/dist/nile-markdown-editor/nile-markdown-editor.cjs.js.map +1 -0
- package/dist/nile-markdown-editor/nile-markdown-editor.css.cjs.js +2 -0
- package/dist/nile-markdown-editor/nile-markdown-editor.css.cjs.js.map +1 -0
- package/dist/nile-markdown-editor/nile-markdown-editor.css.esm.js +255 -0
- package/dist/nile-markdown-editor/nile-markdown-editor.esm.js +143 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/nile-filter-chip/nile-filter-chip.js +1 -1
- package/dist/src/nile-filter-chip/nile-filter-chip.js.map +1 -1
- package/dist/src/nile-markdown/index.d.ts +1 -0
- package/dist/src/nile-markdown/index.js +2 -0
- package/dist/src/nile-markdown/index.js.map +1 -0
- package/dist/src/nile-markdown/nile-markdown.css.d.ts +10 -0
- package/dist/src/nile-markdown/nile-markdown.css.js +163 -0
- package/dist/src/nile-markdown/nile-markdown.css.js.map +1 -0
- package/dist/src/nile-markdown/nile-markdown.d.ts +91 -0
- package/dist/src/nile-markdown/nile-markdown.js +167 -0
- package/dist/src/nile-markdown/nile-markdown.js.map +1 -0
- package/dist/src/nile-markdown/nile-markdown.test.d.ts +1 -0
- package/dist/src/nile-markdown/nile-markdown.test.js +192 -0
- package/dist/src/nile-markdown/nile-markdown.test.js.map +1 -0
- package/dist/src/nile-markdown-editor/index.d.ts +1 -0
- package/dist/src/nile-markdown-editor/index.js +2 -0
- package/dist/src/nile-markdown-editor/index.js.map +1 -0
- package/dist/src/nile-markdown-editor/nile-markdown-editor.css.d.ts +10 -0
- package/dist/src/nile-markdown-editor/nile-markdown-editor.css.js +266 -0
- package/dist/src/nile-markdown-editor/nile-markdown-editor.css.js.map +1 -0
- package/dist/src/nile-markdown-editor/nile-markdown-editor.d.ts +121 -0
- package/dist/src/nile-markdown-editor/nile-markdown-editor.js +615 -0
- package/dist/src/nile-markdown-editor/nile-markdown-editor.js.map +1 -0
- package/dist/src/nile-markdown-editor/nile-markdown-editor.test.d.ts +1 -0
- package/dist/src/nile-markdown-editor/nile-markdown-editor.test.js +268 -0
- package/dist/src/nile-markdown-editor/nile-markdown-editor.test.js.map +1 -0
- package/dist/src/version.js +2 -2
- package/dist/src/version.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -1
- package/src/index.ts +3 -1
- package/src/nile-filter-chip/nile-filter-chip.ts +1 -1
- package/src/nile-markdown/index.ts +1 -0
- package/src/nile-markdown/nile-markdown.css.ts +164 -0
- package/src/nile-markdown/nile-markdown.test.ts +252 -0
- package/src/nile-markdown/nile-markdown.ts +179 -0
- package/src/nile-markdown-editor/index.ts +1 -0
- package/src/nile-markdown-editor/nile-markdown-editor.css.ts +267 -0
- package/src/nile-markdown-editor/nile-markdown-editor.test.ts +402 -0
- package/src/nile-markdown-editor/nile-markdown-editor.ts +710 -0
- package/vscode-html-custom-data.json +82 -0
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Webcomponent nile-elements following open-wc recommendations",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "nile-elements",
|
|
6
|
-
"version": "1.8.
|
|
6
|
+
"version": "1.8.6",
|
|
7
7
|
"main": "dist/src/index.js",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"module": "dist/src/index.js",
|
|
@@ -167,6 +167,7 @@
|
|
|
167
167
|
"element-internals-polyfill": "^1.1.20",
|
|
168
168
|
"figlet": "1.7.0",
|
|
169
169
|
"lit": "^3.0.0",
|
|
170
|
+
"marked": "^15.0.12",
|
|
170
171
|
"tippy.js": "^6.3.7"
|
|
171
172
|
},
|
|
172
173
|
"devDependencies": {
|
package/src/index.ts
CHANGED
|
@@ -131,4 +131,6 @@ export { NileContextMenu } from './nile-context-menu';
|
|
|
131
131
|
export { NileContextMenuGroup } from './nile-context-menu-group';
|
|
132
132
|
export { NileContextMenuItem } from './nile-context-menu-item';
|
|
133
133
|
export { NileInlineSidebarItemHeader } from './nile-inline-sidebar-item-header';
|
|
134
|
-
export { NileInlineSidebarItemBody } from './nile-inline-sidebar-item-body';
|
|
134
|
+
export { NileInlineSidebarItemBody } from './nile-inline-sidebar-item-body';
|
|
135
|
+
export { NileMarkdown } from './nile-markdown';
|
|
136
|
+
export { NileMarkdownEditor } from './nile-markdown-editor';
|
|
@@ -115,7 +115,7 @@ export class NileFilterChip extends NileElement {
|
|
|
115
115
|
|
|
116
116
|
public render(): TemplateResult {
|
|
117
117
|
return html`
|
|
118
|
-
<div class="chip" @click="${this.handleClick}">
|
|
118
|
+
<div class="chip" part="base" @click="${this.handleClick}">
|
|
119
119
|
<slot name="icon">
|
|
120
120
|
${this.icon ? html`<span class="icon">${this.icon}</span>` : html``}
|
|
121
121
|
</slot>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { NileMarkdown } from './nile-markdown';
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Aquera Inc 2023
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the BSD-3-Clause license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { css } from 'lit';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Markdown CSS
|
|
12
|
+
*/
|
|
13
|
+
export const styles = css`
|
|
14
|
+
:host {
|
|
15
|
+
display: block;
|
|
16
|
+
-webkit-font-smoothing: var(
|
|
17
|
+
--nile-webkit-font-smoothing,
|
|
18
|
+
var(--ng-webkit-font-smoothing)
|
|
19
|
+
);
|
|
20
|
+
-moz-osx-font-smoothing: var(
|
|
21
|
+
--nile-moz-osx-font-smoothing,
|
|
22
|
+
var(--ng-moz-osx-font-smoothing)
|
|
23
|
+
);
|
|
24
|
+
text-rendering: var(--nile-text-rendering, var(--ng-text-rendering));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.markdown {
|
|
28
|
+
font-family: var(--nile-font-family-serif, var(--ng-font-family-body));
|
|
29
|
+
font-size: var(--nile-type-scale-3, var(--ng-font-size-text-md));
|
|
30
|
+
line-height: var(--nile-line-height-medium, var(--ng-line-height-text-md));
|
|
31
|
+
color: var(--nile-colors-dark-900, var(--ng-colors-text-primary-900));
|
|
32
|
+
word-wrap: break-word;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.markdown > :first-child {
|
|
36
|
+
margin-top: 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.markdown > :last-child {
|
|
40
|
+
margin-bottom: 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.markdown h1,
|
|
44
|
+
.markdown h2,
|
|
45
|
+
.markdown h3,
|
|
46
|
+
.markdown h4,
|
|
47
|
+
.markdown h5,
|
|
48
|
+
.markdown h6 {
|
|
49
|
+
margin: 1.5em 0 0.5em;
|
|
50
|
+
font-weight: var(--nile-font-weight-medium, var(--ng-font-weight-semibold));
|
|
51
|
+
line-height: 1.25;
|
|
52
|
+
color: var(--nile-colors-dark-900, var(--ng-colors-text-primary-900));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.markdown h1 {
|
|
56
|
+
font-size: var(--nile-type-scale-7, var(--ng-font-size-display-sm));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.markdown h2 {
|
|
60
|
+
font-size: var(--nile-type-scale-6, var(--ng-font-size-text-xl));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.markdown h3 {
|
|
64
|
+
font-size: var(--nile-type-scale-5, var(--ng-font-size-text-lg));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.markdown h4 {
|
|
68
|
+
font-size: var(--nile-type-scale-3, var(--ng-font-size-text-md));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.markdown h5 {
|
|
72
|
+
font-size: var(--nile-type-scale-2, var(--ng-font-size-text-sm));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.markdown h6 {
|
|
76
|
+
font-size: var(--nile-type-scale-2, var(--ng-font-size-text-sm));
|
|
77
|
+
color: var(--nile-colors-neutral-700, var(--ng-colors-text-secondary-700));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.markdown p {
|
|
81
|
+
margin: 0 0 1em;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.markdown a {
|
|
85
|
+
color: var(--nile-colors-link-primary, var(--ng-colors-text-brand-secondary-700)
|
|
86
|
+
);
|
|
87
|
+
text-decoration: underline;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.markdown a:hover {
|
|
91
|
+
color: var(--nile-colors-link-hover, var(--ng-colors-text-brand-secondary-hover));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.markdown ul,
|
|
95
|
+
.markdown ol {
|
|
96
|
+
margin: 0 0 1em;
|
|
97
|
+
padding-inline-start: 2em;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.markdown li > ul,
|
|
101
|
+
.markdown li > ol {
|
|
102
|
+
margin-bottom: 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.markdown blockquote {
|
|
106
|
+
margin: 0 0 1em;
|
|
107
|
+
padding: 0 1em;
|
|
108
|
+
border-inline-start: 4px solid var(--nile-colors-neutral-400, var(--ng-colors-fg-quaternary-400));
|
|
109
|
+
color: var(--nile-colors-neutral-700, var(--ng-colors-text-secondary-700));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.markdown code {
|
|
113
|
+
font-family: var(--nile-font-family-mono, monospace);
|
|
114
|
+
font-size: var(--nile-type-scale-2, var(--ng-font-size-text-sm));
|
|
115
|
+
background: var(--nile-colors-dark-100, var(--ng-colors-bg-secondary));
|
|
116
|
+
border-radius: var(--nile-radius-sm, var(--ng-radius-sm));
|
|
117
|
+
padding: 0.125em 0.375em;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.markdown pre {
|
|
121
|
+
margin: 0 0 1em;
|
|
122
|
+
padding: var(--nile-spacing-md, var(--ng-spacing-md));
|
|
123
|
+
background: var(--nile-colors-dark-100, var(--ng-colors-bg-secondary));
|
|
124
|
+
border-radius: var(--nile-radius-md, var(--ng-radius-md));
|
|
125
|
+
overflow-x: auto;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.markdown pre code {
|
|
129
|
+
background: none;
|
|
130
|
+
border-radius: 0;
|
|
131
|
+
padding: 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.markdown table {
|
|
135
|
+
margin: 0 0 1em;
|
|
136
|
+
border-collapse: collapse;
|
|
137
|
+
width: max-content;
|
|
138
|
+
max-width: 100%;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.markdown th,
|
|
142
|
+
.markdown td {
|
|
143
|
+
border: 1px solid var(--nile-colors-gray-300, var(--ng-colors-gray-300));
|
|
144
|
+
padding: var(--nile-spacing-xs, var(--ng-spacing-xs))
|
|
145
|
+
var(--nile-spacing-md, var(--ng-spacing-md));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.markdown th {
|
|
149
|
+
background: var(--nile-colors-dark-100, var(--ng-colors-bg-secondary));
|
|
150
|
+
font-weight: var(--nile-font-weight-medium, var(--ng-font-weight-semibold));
|
|
151
|
+
text-align: left;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.markdown img {
|
|
155
|
+
max-width: 100%;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.markdown hr {
|
|
159
|
+
height: 1px;
|
|
160
|
+
margin: 1.5em 0;
|
|
161
|
+
border: none;
|
|
162
|
+
background: var(--nile-colors-dark-200, var(--ng-colors-border-secondary));
|
|
163
|
+
}
|
|
164
|
+
`;
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { expect, fixture, html, oneEvent } from '@open-wc/testing';
|
|
2
|
+
import './nile-markdown';
|
|
3
|
+
import NileMarkdown from './nile-markdown';
|
|
4
|
+
|
|
5
|
+
describe('NileMarkdown', () => {
|
|
6
|
+
// === RENDERING ===
|
|
7
|
+
it('1. should render without errors', async () => {
|
|
8
|
+
const el = await fixture<NileMarkdown>(
|
|
9
|
+
html`<nile-markdown></nile-markdown>`
|
|
10
|
+
);
|
|
11
|
+
expect(el).to.exist;
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('2. should have a shadow root', async () => {
|
|
15
|
+
const el = await fixture<NileMarkdown>(
|
|
16
|
+
html`<nile-markdown></nile-markdown>`
|
|
17
|
+
);
|
|
18
|
+
expect(el.shadowRoot).to.not.be.null;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('3. should have base part', async () => {
|
|
22
|
+
const el = await fixture<NileMarkdown>(
|
|
23
|
+
html`<nile-markdown></nile-markdown>`
|
|
24
|
+
);
|
|
25
|
+
const base = el.shadowRoot!.querySelector('[part~="base"]');
|
|
26
|
+
expect(base).to.exist;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('4. should be instance of NileMarkdown', async () => {
|
|
30
|
+
const el = await fixture<NileMarkdown>(
|
|
31
|
+
html`<nile-markdown></nile-markdown>`
|
|
32
|
+
);
|
|
33
|
+
expect(el).to.be.instanceOf(NileMarkdown);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('5. should have correct tag name', async () => {
|
|
37
|
+
const el = await fixture<NileMarkdown>(
|
|
38
|
+
html`<nile-markdown></nile-markdown>`
|
|
39
|
+
);
|
|
40
|
+
expect(el.tagName.toLowerCase()).to.equal('nile-markdown');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// === DEFAULT PROPERTIES ===
|
|
44
|
+
it('6. should have value default to empty string', async () => {
|
|
45
|
+
const el = await fixture<NileMarkdown>(
|
|
46
|
+
html`<nile-markdown></nile-markdown>`
|
|
47
|
+
);
|
|
48
|
+
expect(el.value).to.equal('');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('7. should have tabSize default to 4', async () => {
|
|
52
|
+
const el = await fixture<NileMarkdown>(
|
|
53
|
+
html`<nile-markdown></nile-markdown>`
|
|
54
|
+
);
|
|
55
|
+
expect(el.tabSize).to.equal(4);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// === VALUE PROPERTY ===
|
|
59
|
+
it('8. should render markdown from value property', async () => {
|
|
60
|
+
const el = await fixture<NileMarkdown>(
|
|
61
|
+
html`<nile-markdown value="# Heading"></nile-markdown>`
|
|
62
|
+
);
|
|
63
|
+
await el.updateComplete;
|
|
64
|
+
const h1 = el.shadowRoot!.querySelector('h1');
|
|
65
|
+
expect(h1).to.exist;
|
|
66
|
+
expect(h1!.textContent).to.equal('Heading');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('9. should render bold text', async () => {
|
|
70
|
+
const el = await fixture<NileMarkdown>(
|
|
71
|
+
html`<nile-markdown value="This is **bold**"></nile-markdown>`
|
|
72
|
+
);
|
|
73
|
+
await el.updateComplete;
|
|
74
|
+
const strong = el.shadowRoot!.querySelector('strong');
|
|
75
|
+
expect(strong).to.exist;
|
|
76
|
+
expect(strong!.textContent).to.equal('bold');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('10. should render links', async () => {
|
|
80
|
+
const el = await fixture<NileMarkdown>(
|
|
81
|
+
html`<nile-markdown value="[link](https://example.com)"></nile-markdown>`
|
|
82
|
+
);
|
|
83
|
+
await el.updateComplete;
|
|
84
|
+
const a = el.shadowRoot!.querySelector('a');
|
|
85
|
+
expect(a).to.exist;
|
|
86
|
+
expect(a!.getAttribute('href')).to.equal('https://example.com');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('11. should render lists', async () => {
|
|
90
|
+
const el = await fixture<NileMarkdown>(
|
|
91
|
+
html`<nile-markdown></nile-markdown>`
|
|
92
|
+
);
|
|
93
|
+
el.value = '- one\n- two\n- three';
|
|
94
|
+
await el.updateComplete;
|
|
95
|
+
const items = el.shadowRoot!.querySelectorAll('li');
|
|
96
|
+
expect(items.length).to.equal(3);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('12. should render code blocks', async () => {
|
|
100
|
+
const el = await fixture<NileMarkdown>(
|
|
101
|
+
html`<nile-markdown></nile-markdown>`
|
|
102
|
+
);
|
|
103
|
+
el.value = '```\nconst a = 1;\n```';
|
|
104
|
+
await el.updateComplete;
|
|
105
|
+
const pre = el.shadowRoot!.querySelector('pre code');
|
|
106
|
+
expect(pre).to.exist;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('13. should render GFM tables', async () => {
|
|
110
|
+
const el = await fixture<NileMarkdown>(
|
|
111
|
+
html`<nile-markdown></nile-markdown>`
|
|
112
|
+
);
|
|
113
|
+
el.value = '| a | b |\n| - | - |\n| 1 | 2 |';
|
|
114
|
+
await el.updateComplete;
|
|
115
|
+
const table = el.shadowRoot!.querySelector('table');
|
|
116
|
+
expect(table).to.exist;
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('14. should render blockquotes', async () => {
|
|
120
|
+
const el = await fixture<NileMarkdown>(
|
|
121
|
+
html`<nile-markdown></nile-markdown>`
|
|
122
|
+
);
|
|
123
|
+
el.value = '> quoted';
|
|
124
|
+
await el.updateComplete;
|
|
125
|
+
const quote = el.shadowRoot!.querySelector('blockquote');
|
|
126
|
+
expect(quote).to.exist;
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// === SCRIPT CHILD ===
|
|
130
|
+
it('15. should render markdown from a script child', async () => {
|
|
131
|
+
const el = await fixture<NileMarkdown>(html`
|
|
132
|
+
<nile-markdown>
|
|
133
|
+
<script type="text/markdown">
|
|
134
|
+
# From Script
|
|
135
|
+
</script>
|
|
136
|
+
</nile-markdown>
|
|
137
|
+
`);
|
|
138
|
+
await el.updateComplete;
|
|
139
|
+
const h1 = el.shadowRoot!.querySelector('h1');
|
|
140
|
+
expect(h1).to.exist;
|
|
141
|
+
expect(h1!.textContent).to.equal('From Script');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('16. should normalize indented script content', async () => {
|
|
145
|
+
const el = await fixture<NileMarkdown>(html`
|
|
146
|
+
<nile-markdown>
|
|
147
|
+
<script type="text/markdown">
|
|
148
|
+
# Title
|
|
149
|
+
|
|
150
|
+
Paragraph text, indented to match the HTML.
|
|
151
|
+
</script>
|
|
152
|
+
</nile-markdown>
|
|
153
|
+
`);
|
|
154
|
+
await el.updateComplete;
|
|
155
|
+
// Indented content must not be treated as a code block
|
|
156
|
+
expect(el.shadowRoot!.querySelector('pre')).to.not.exist;
|
|
157
|
+
expect(el.shadowRoot!.querySelector('h1')).to.exist;
|
|
158
|
+
expect(el.shadowRoot!.querySelector('p')).to.exist;
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('17. value should take precedence over script child', async () => {
|
|
162
|
+
const el = await fixture<NileMarkdown>(html`
|
|
163
|
+
<nile-markdown value="# From Value">
|
|
164
|
+
<script type="text/markdown">
|
|
165
|
+
# From Script
|
|
166
|
+
</script>
|
|
167
|
+
</nile-markdown>
|
|
168
|
+
`);
|
|
169
|
+
await el.updateComplete;
|
|
170
|
+
const h1 = el.shadowRoot!.querySelector('h1');
|
|
171
|
+
expect(h1!.textContent).to.equal('From Value');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// === DYNAMIC UPDATES ===
|
|
175
|
+
it('18. should re-render when value changes', async () => {
|
|
176
|
+
const el = await fixture<NileMarkdown>(
|
|
177
|
+
html`<nile-markdown value="# One"></nile-markdown>`
|
|
178
|
+
);
|
|
179
|
+
await el.updateComplete;
|
|
180
|
+
el.value = '# Two';
|
|
181
|
+
await el.updateComplete;
|
|
182
|
+
await el.updateComplete;
|
|
183
|
+
const h1 = el.shadowRoot!.querySelector('h1');
|
|
184
|
+
expect(h1!.textContent).to.equal('Two');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('19. should re-render when script content changes', async () => {
|
|
188
|
+
const el = await fixture<NileMarkdown>(html`
|
|
189
|
+
<nile-markdown>
|
|
190
|
+
<script type="text/markdown">
|
|
191
|
+
# Before
|
|
192
|
+
</script>
|
|
193
|
+
</nile-markdown>
|
|
194
|
+
`);
|
|
195
|
+
await el.updateComplete;
|
|
196
|
+
const script = el.querySelector('script')!;
|
|
197
|
+
script.textContent = '# After';
|
|
198
|
+
// MutationObserver fires async
|
|
199
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
200
|
+
await el.updateComplete;
|
|
201
|
+
const h1 = el.shadowRoot!.querySelector('h1');
|
|
202
|
+
expect(h1!.textContent).to.equal('After');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('20. renderMarkdown() should re-parse manually', async () => {
|
|
206
|
+
const el = await fixture<NileMarkdown>(
|
|
207
|
+
html`<nile-markdown value="# Manual"></nile-markdown>`
|
|
208
|
+
);
|
|
209
|
+
await el.updateComplete;
|
|
210
|
+
el.renderMarkdown();
|
|
211
|
+
await el.updateComplete;
|
|
212
|
+
expect(el.shadowRoot!.querySelector('h1')!.textContent).to.equal('Manual');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// === EVENTS ===
|
|
216
|
+
it('21. should emit nile-markdown-rendered after rendering', async () => {
|
|
217
|
+
const el = document.createElement('nile-markdown') as NileMarkdown;
|
|
218
|
+
const listener = oneEvent(el, 'nile-markdown-rendered');
|
|
219
|
+
el.value = '# Event';
|
|
220
|
+
document.body.appendChild(el);
|
|
221
|
+
const event = await listener;
|
|
222
|
+
expect(event).to.exist;
|
|
223
|
+
el.remove();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// === STATIC API ===
|
|
227
|
+
it('22. getMarked() should return a shared instance', () => {
|
|
228
|
+
const a = NileMarkdown.getMarked();
|
|
229
|
+
const b = NileMarkdown.getMarked();
|
|
230
|
+
expect(a).to.equal(b);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('23. updateAll() should not throw', async () => {
|
|
234
|
+
await fixture<NileMarkdown>(
|
|
235
|
+
html`<nile-markdown value="# A"></nile-markdown>`
|
|
236
|
+
);
|
|
237
|
+
expect(() => NileMarkdown.updateAll()).to.not.throw();
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// === EDGE CASES ===
|
|
241
|
+
it('24. should handle empty content', async () => {
|
|
242
|
+
const el = await fixture<NileMarkdown>(
|
|
243
|
+
html`<nile-markdown></nile-markdown>`
|
|
244
|
+
);
|
|
245
|
+
const base = el.shadowRoot!.querySelector('[part~="base"]');
|
|
246
|
+
expect(base!.textContent!.trim()).to.equal('');
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('25. should have static styles', () => {
|
|
250
|
+
expect(NileMarkdown.styles).to.exist;
|
|
251
|
+
});
|
|
252
|
+
});
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Aquera Inc 2023
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the BSD-3-Clause license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { html } from 'lit';
|
|
9
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
10
|
+
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
|
11
|
+
import { Marked } from 'marked';
|
|
12
|
+
import { styles } from './nile-markdown.css';
|
|
13
|
+
import NileElement from '../internal/nile-element';
|
|
14
|
+
import type { CSSResultGroup, PropertyValues, TemplateResult } from 'lit';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Nile markdown component.
|
|
18
|
+
*
|
|
19
|
+
* @tag nile-markdown
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @summary Renders markdown content as HTML in the browser using GitHub Flavored Markdown.
|
|
24
|
+
* @status experimental
|
|
25
|
+
*
|
|
26
|
+
* Content can be provided either through the `value` property or through a
|
|
27
|
+
* `<script type="text/markdown">` element placed as a direct child:
|
|
28
|
+
*
|
|
29
|
+
* ```html
|
|
30
|
+
* <nile-markdown>
|
|
31
|
+
* <script type="text/markdown">
|
|
32
|
+
* # Hello
|
|
33
|
+
* This is **markdown**.
|
|
34
|
+
* </script>
|
|
35
|
+
* </nile-markdown>
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* Leading whitespace common to all lines is stripped, so the markdown can be
|
|
39
|
+
* indented to match the surrounding HTML without rendering as a code block.
|
|
40
|
+
*
|
|
41
|
+
* All instances share a single Marked parser. Use `NileMarkdown.getMarked()`
|
|
42
|
+
* to customize it (extensions, renderers, etc.) and `NileMarkdown.updateAll()`
|
|
43
|
+
* to re-render existing instances after changing the configuration.
|
|
44
|
+
*
|
|
45
|
+
* WARNING: The markdown is converted to HTML without sanitization. Do not
|
|
46
|
+
* render unsanitized user input, as this can expose users to XSS attacks.
|
|
47
|
+
*
|
|
48
|
+
* @event nile-markdown-rendered - Emitted after the markdown has been parsed and rendered.
|
|
49
|
+
*
|
|
50
|
+
* @csspart base - The component's base wrapper containing the rendered HTML.
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
@customElement('nile-markdown')
|
|
54
|
+
export class NileMarkdown extends NileElement {
|
|
55
|
+
static styles: CSSResultGroup = styles;
|
|
56
|
+
|
|
57
|
+
/** The shared Marked instance used by every nile-markdown on the page. */
|
|
58
|
+
private static marked: Marked | undefined;
|
|
59
|
+
|
|
60
|
+
/** All connected instances, used by `updateAll()`. */
|
|
61
|
+
private static instances = new Set<NileMarkdown>();
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Returns the shared Marked instance so it can be configured with custom
|
|
65
|
+
* options, extensions, or renderers. Changes affect all instances; call
|
|
66
|
+
* `NileMarkdown.updateAll()` afterwards to re-render existing ones.
|
|
67
|
+
*/
|
|
68
|
+
static getMarked(): Marked {
|
|
69
|
+
if (!NileMarkdown.marked) {
|
|
70
|
+
NileMarkdown.marked = new Marked({ gfm: true, async: false });
|
|
71
|
+
}
|
|
72
|
+
return NileMarkdown.marked;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Re-renders every connected nile-markdown instance. */
|
|
76
|
+
static updateAll(): void {
|
|
77
|
+
NileMarkdown.instances.forEach(instance => instance.renderMarkdown());
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The markdown to render. Takes precedence over a
|
|
82
|
+
* `<script type="text/markdown">` child when set.
|
|
83
|
+
*/
|
|
84
|
+
@property() value = '';
|
|
85
|
+
|
|
86
|
+
/** Number of spaces a tab is converted to during whitespace normalization. */
|
|
87
|
+
@property({ type: Number, attribute: 'tab-size' }) tabSize = 4;
|
|
88
|
+
|
|
89
|
+
@state() private renderedHtml = '';
|
|
90
|
+
|
|
91
|
+
/** Re-renders automatically when the markdown script child changes. */
|
|
92
|
+
private mutationObserver = new MutationObserver(() => this.renderMarkdown());
|
|
93
|
+
|
|
94
|
+
connectedCallback(): void {
|
|
95
|
+
super.connectedCallback();
|
|
96
|
+
NileMarkdown.instances.add(this);
|
|
97
|
+
this.mutationObserver.observe(this, {
|
|
98
|
+
childList: true,
|
|
99
|
+
subtree: true,
|
|
100
|
+
characterData: true,
|
|
101
|
+
});
|
|
102
|
+
this.renderMarkdown();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
disconnectedCallback(): void {
|
|
106
|
+
this.mutationObserver.disconnect();
|
|
107
|
+
NileMarkdown.instances.delete(this);
|
|
108
|
+
super.disconnectedCallback();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
protected willUpdate(changed: PropertyValues): void {
|
|
112
|
+
super.willUpdate(changed);
|
|
113
|
+
if (changed.has('value') || changed.has('tabSize')) {
|
|
114
|
+
this.computeHtml();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
protected updated(changed: PropertyValues): void {
|
|
119
|
+
super.updated(changed);
|
|
120
|
+
if (changed.has('renderedHtml')) {
|
|
121
|
+
this.emit('nile-markdown-rendered', undefined, true, true);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Reads the markdown source from `value` or the script child. */
|
|
126
|
+
private getSource(): string {
|
|
127
|
+
if (this.value) return this.value;
|
|
128
|
+
const script = this.querySelector(':scope > script[type="text/markdown"]');
|
|
129
|
+
return script?.textContent ?? '';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Converts tabs to spaces and removes the leading indentation common to all
|
|
134
|
+
* lines, so markdown indented to match the HTML doesn't render as code.
|
|
135
|
+
*/
|
|
136
|
+
private normalizeWhitespace(text: string): string {
|
|
137
|
+
const tab = ' '.repeat(Math.max(1, this.tabSize));
|
|
138
|
+
const lines = text.replace(/\t/g, tab).split('\n');
|
|
139
|
+
|
|
140
|
+
let minIndent = Infinity;
|
|
141
|
+
for (const line of lines) {
|
|
142
|
+
if (!line.trim()) continue;
|
|
143
|
+
const indent = line.length - line.trimStart().length;
|
|
144
|
+
minIndent = Math.min(minIndent, indent);
|
|
145
|
+
}
|
|
146
|
+
if (!Number.isFinite(minIndent) || minIndent === 0) {
|
|
147
|
+
return text.trim();
|
|
148
|
+
}
|
|
149
|
+
return lines
|
|
150
|
+
.map(line => line.slice(minIndent))
|
|
151
|
+
.join('\n')
|
|
152
|
+
.trim();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Parses the markdown source into `renderedHtml`. */
|
|
156
|
+
private computeHtml(): void {
|
|
157
|
+
const source = this.normalizeWhitespace(this.getSource());
|
|
158
|
+
this.renderedHtml = NileMarkdown.getMarked().parse(source) as string;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Parses the markdown source and renders the resulting HTML. */
|
|
162
|
+
renderMarkdown(): void {
|
|
163
|
+
this.computeHtml();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
render(): TemplateResult {
|
|
167
|
+
return html`
|
|
168
|
+
<div class="markdown" part="base">${unsafeHTML(this.renderedHtml)}</div>
|
|
169
|
+
`;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export default NileMarkdown;
|
|
174
|
+
|
|
175
|
+
declare global {
|
|
176
|
+
interface HTMLElementTagNameMap {
|
|
177
|
+
'nile-markdown': NileMarkdown;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { NileMarkdownEditor } from './nile-markdown-editor';
|