kempo-ui 0.4.6 → 0.4.8
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 +11 -0
- package/dist/components/Markdown.js +1 -0
- package/dist/components/Timestamp.js +1 -1
- package/dist/utils/formatTimestamp.js +1 -1
- package/docs/components/accordion.html +4 -0
- package/docs/components/aside.html +4 -0
- package/docs/components/calendar.html +4 -0
- package/docs/components/card.html +4 -0
- package/docs/components/chat.html +4 -0
- package/docs/components/code-editor.html +4 -0
- package/docs/components/color-picker.html +4 -0
- package/docs/components/combobox.html +4 -0
- package/docs/components/content-slider.html +4 -0
- package/docs/components/context.html +4 -0
- package/docs/components/dialog.html +4 -0
- package/docs/components/dropdown.html +4 -0
- package/docs/components/filter-list.html +4 -0
- package/docs/components/focus-capture.html +4 -0
- package/docs/components/html-editor.html +4 -0
- package/docs/components/hybrid-component.html +4 -0
- package/docs/components/icon.html +4 -0
- package/docs/components/import.html +4 -0
- package/docs/components/light-component.html +4 -0
- package/docs/components/markdown-editor.html +4 -0
- package/docs/components/markdown.html +650 -0
- package/docs/components/nav-spacer.html +4 -0
- package/docs/components/nav.html +4 -0
- package/docs/components/photo-viewer.html +4 -0
- package/docs/components/progress.html +4 -0
- package/docs/components/resize.html +4 -0
- package/docs/components/shadow-component.html +4 -0
- package/docs/components/show-more.html +4 -0
- package/docs/components/slider.html +4 -0
- package/docs/components/sortable.html +4 -0
- package/docs/components/speech-to-text.html +4 -0
- package/docs/components/spinner.html +4 -0
- package/docs/components/split.html +4 -0
- package/docs/components/table.html +4 -0
- package/docs/components/tableControls.html +4 -0
- package/docs/components/tableCustomFields.html +4 -0
- package/docs/components/tableFetchRecords.html +4 -0
- package/docs/components/tableFieldSortHide.html +4 -0
- package/docs/components/tablePagination.html +4 -0
- package/docs/components/tablePlaceholder.html +4 -0
- package/docs/components/tableRecordEditing.html +4 -0
- package/docs/components/tableRecordFiltering.html +4 -0
- package/docs/components/tableRecordHiding.html +4 -0
- package/docs/components/tableRecordSearching.html +4 -0
- package/docs/components/tableRecordSelection.html +4 -0
- package/docs/components/tableRowControls.html +4 -0
- package/docs/components/tableServerSync.html +4 -0
- package/docs/components/tableSorting.html +4 -0
- package/docs/components/tabs.html +4 -0
- package/docs/components/tags.html +4 -0
- package/docs/components/text-to-speech.html +4 -0
- package/docs/components/theme-select.html +4 -0
- package/docs/components/theme-switcher.html +4 -0
- package/docs/components/time.html +4 -0
- package/docs/components/timestamp.html +4 -0
- package/docs/components/toast.html +4 -0
- package/docs/components/toggle.html +4 -0
- package/docs/components/tree.html +4 -0
- package/docs/components/voice-selector.html +4 -0
- package/docs/index.html +10 -0
- package/docs/src/components/Markdown.js +1 -0
- package/docs/src/components/Timestamp.js +1 -1
- package/docs/src/utils/formatTimestamp.js +1 -1
- package/docs/utils/context.html +4 -0
- package/docs/utils/cookie.html +4 -0
- package/docs/utils/debounce.html +4 -0
- package/docs/utils/drag.html +4 -0
- package/docs/utils/elevation.html +4 -0
- package/docs/utils/formatTimestamp.html +4 -0
- package/docs/utils/object.html +4 -0
- package/docs/utils/propConverters.html +4 -0
- package/docs/utils/sanitizeHtml.html +4 -0
- package/docs/utils/string.html +4 -0
- package/docs/utils/theme.html +4 -0
- package/docs/utils/toTitleCase.html +4 -0
- package/docs/utils/type.html +4 -0
- package/docs/utils/voice.html +4 -0
- package/docs/utils/wait.html +4 -0
- package/docs-src/components/markdown.page.html +161 -0
- package/docs-src/index.page.html +6 -0
- package/docs-src/nav.fragment.html +4 -0
- package/llms.txt +1 -0
- package/package.json +1 -1
- package/src/components/Markdown.js +125 -0
- package/src/components/Timestamp.js +3 -3
- package/src/utils/formatTimestamp.js +2 -1
- package/tests/components/Markdown.browser-test.js +292 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
<page pageName="Markdown" title="Markdown - Components - Kempo Docs - A Web Components Solution">
|
|
2
|
+
<content>
|
|
3
|
+
<k-accordion persistent-id="toc" class="b r mb">
|
|
4
|
+
<k-accordion-header for-panel="toc-panel">Table of Contents</k-accordion-header>
|
|
5
|
+
<k-accordion-panel name="toc-panel">
|
|
6
|
+
<div class="m pl">
|
|
7
|
+
<h6>Examples</h6>
|
|
8
|
+
<a href="#basicUsage">Basic Usage</a><br />
|
|
9
|
+
<a href="#valueAttr">Value Attribute</a><br />
|
|
10
|
+
<a href="#breaks">Line Breaks</a><br />
|
|
11
|
+
<a href="#tagFiltering">Tag Filtering</a><br />
|
|
12
|
+
<a href="#dynamic">Updating At Runtime</a><br />
|
|
13
|
+
|
|
14
|
+
<h6 class="mt"><a href="#jsRef" class="no-link">JavaScript Reference</a></h6>
|
|
15
|
+
<a href="#constructor">Constructor</a><br />
|
|
16
|
+
<a href="#requirements">Requirements</a><br />
|
|
17
|
+
<a href="#properties">Properties</a><br />
|
|
18
|
+
<a href="#methods">Methods</a><br />
|
|
19
|
+
</div>
|
|
20
|
+
</k-accordion-panel>
|
|
21
|
+
</k-accordion>
|
|
22
|
+
|
|
23
|
+
<p>The <code>k-markdown</code> component takes its child text content (or a <code>value</code> attribute), parses it as markdown using <a href="https://marked.js.org" target="_blank">marked</a> (full CommonMark + GFM — tables, task lists, fenced code, autolinks, inline HTML), runs the result through <a href="{{pathToRoot}}utils/sanitizeHtml.html"><code>sanitizeHtml</code></a>, and renders the HTML in place. It renders to the light DOM so page-level typography styles cascade naturally into headings, paragraphs, tables, and so on.</p>
|
|
24
|
+
<p>Common leading whitespace is automatically stripped from the source so you can indent the markdown to match the surrounding HTML without breaking parsing.</p>
|
|
25
|
+
|
|
26
|
+
<h3 id="basicUsage"><a href="#basicUsage" class="no-link">Basic Usage</a></h3>
|
|
27
|
+
<p>Drop markdown text inside the tag and it will be replaced with the rendered HTML when the element connects.</p>
|
|
28
|
+
<div class="row -mx mb">
|
|
29
|
+
<div class="col d-span-6 m-span-12 px">
|
|
30
|
+
<k-card label="HTML">
|
|
31
|
+
<pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">k-markdown</span>></span><br> # Hello World<br><br> This is **bold** and *italic* text.<br><br> - list item 1<br> - list item 2<br><span class="hljs-tag"></<span class="hljs-name">k-markdown</span>></span></code></pre>
|
|
32
|
+
</k-card>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="col d-span-6 m-span-12 px">
|
|
35
|
+
<k-card label="Output">
|
|
36
|
+
<k-markdown>
|
|
37
|
+
# Hello World
|
|
38
|
+
|
|
39
|
+
This is **bold** and *italic* text.
|
|
40
|
+
|
|
41
|
+
- list item 1
|
|
42
|
+
- list item 2
|
|
43
|
+
</k-markdown>
|
|
44
|
+
</k-card>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<h3 id="valueAttr"><a href="#valueAttr" class="no-link">Value Attribute</a></h3>
|
|
49
|
+
<p>Skip the children and pass markdown directly via the <code>value</code> attribute when you only need a single line. The attribute also takes precedence over child content if both are provided.</p>
|
|
50
|
+
<div class="row -mx mb">
|
|
51
|
+
<div class="col d-span-6 m-span-12 px">
|
|
52
|
+
<k-card label="HTML">
|
|
53
|
+
<pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">k-markdown</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"Visit [Kempo UI](https://github.com/dustinpoissant/kempo-ui) for more."</span>></span><span class="hljs-tag"></<span class="hljs-name">k-markdown</span>></span></code></pre>
|
|
54
|
+
</k-card>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="col d-span-6 m-span-12 px">
|
|
57
|
+
<k-card label="Output">
|
|
58
|
+
<k-markdown value="Visit [Kempo UI](https://github.com/dustinpoissant/kempo-ui) for more."></k-markdown>
|
|
59
|
+
</k-card>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<h3 id="breaks"><a href="#breaks" class="no-link">Line Breaks</a></h3>
|
|
64
|
+
<p>By default, single newlines are joined into a paragraph (CommonMark behavior). Add the <code>breaks</code> attribute to convert single newlines to <code><br></code> — the Slack/iMessage convention — useful for chat-style content.</p>
|
|
65
|
+
<div class="row -mx mb">
|
|
66
|
+
<div class="col d-span-6 m-span-12 px">
|
|
67
|
+
<k-card label="HTML">
|
|
68
|
+
<pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">k-markdown</span> <span class="hljs-attr">breaks</span>></span><br> Line one<br> Line two<br> Line three<br><span class="hljs-tag"></<span class="hljs-name">k-markdown</span>></span></code></pre>
|
|
69
|
+
</k-card>
|
|
70
|
+
</div>
|
|
71
|
+
<div class="col d-span-6 m-span-12 px">
|
|
72
|
+
<k-card label="Output">
|
|
73
|
+
<k-markdown breaks>
|
|
74
|
+
Line one
|
|
75
|
+
Line two
|
|
76
|
+
Line three
|
|
77
|
+
</k-markdown>
|
|
78
|
+
</k-card>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<h3 id="tagFiltering"><a href="#tagFiltering" class="no-link">Tag Filtering</a></h3>
|
|
83
|
+
<p>The rendered HTML is sanitized with the same <code>allowed-tags</code> / <code>disallowed-tags</code> controls used by <a href="{{pathToRoot}}components/markdown-editor.html"><code>k-markdown-editor</code></a>. Pass a comma-separated allowlist (or <code>*</code> for everything), or a comma-separated denylist — the two are mutually exclusive. <code><script></code>, <code><iframe></code>, <code><style></code>, and similar tags are always stripped unless you opt in with <code>scripts-enabled</code> (which only restores <code><script></code>).</p>
|
|
84
|
+
<div class="row -mx mb">
|
|
85
|
+
<div class="col d-span-6 m-span-12 px">
|
|
86
|
+
<k-card label="HTML">
|
|
87
|
+
<pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">k-markdown</span> <span class="hljs-attr">allowed-tags</span>=<span class="hljs-string">"p,strong,em,a"</span>></span><br> Only **bold**, *italic*, [links](https://example.com), and paragraphs are kept.<br><br> # This heading will be unwrapped<br><span class="hljs-tag"></<span class="hljs-name">k-markdown</span>></span></code></pre>
|
|
88
|
+
</k-card>
|
|
89
|
+
</div>
|
|
90
|
+
<div class="col d-span-6 m-span-12 px">
|
|
91
|
+
<k-card label="Output">
|
|
92
|
+
<k-markdown allowed-tags="p,strong,em,a">
|
|
93
|
+
Only **bold**, *italic*, [links](https://example.com), and paragraphs are kept.
|
|
94
|
+
|
|
95
|
+
# This heading will be unwrapped
|
|
96
|
+
</k-markdown>
|
|
97
|
+
</k-card>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<h3 id="dynamic"><a href="#dynamic" class="no-link">Updating At Runtime</a></h3>
|
|
102
|
+
<p>Set the <code>value</code> property in JavaScript to re-render with new markdown.</p>
|
|
103
|
+
<div class="row -mx mb">
|
|
104
|
+
<div class="col d-span-6 m-span-12 px">
|
|
105
|
+
<k-card label="HTML">
|
|
106
|
+
<pre><code class="hljs html"><span class="hljs-tag"><<span class="hljs-name">k-markdown</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"dynamicMd"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"Click the button."</span>></span><span class="hljs-tag"></<span class="hljs-name">k-markdown</span>></span><br><span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"dynamicMdBtn"</span>></span>Update<span class="hljs-tag"></<span class="hljs-name">button</span>></span><br><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span>></span><span class="language-javascript"><br> <span class="hljs-keyword">const</span> md = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'dynamicMd'</span>);<br> <span class="hljs-keyword">const</span> btn = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'dynamicMdBtn'</span>);<br> <span class="hljs-keyword">let</span> toggled = <span class="hljs-literal">false</span>;<br> btn.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'click'</span>, <span class="hljs-function">() =></span> {<br> toggled = !toggled;<br> md.<span class="hljs-property">value</span> = toggled<br> ? <span class="hljs-string">'## Updated!\n\nThis was set via JavaScript with **markdown** and a [link](https://example.com).'</span><br> : <span class="hljs-string">'Click the button.'</span>;<br> });<br></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span></code></pre>
|
|
107
|
+
</k-card>
|
|
108
|
+
</div>
|
|
109
|
+
<div class="col d-span-6 m-span-12 px">
|
|
110
|
+
<k-card label="Output">
|
|
111
|
+
<k-markdown id="dynamicMd" value="Click the button."></k-markdown>
|
|
112
|
+
<button id="dynamicMdBtn" class="btn primary mt">Update</button>
|
|
113
|
+
</k-card>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<h2 id="jsRef"><a href="#jsRef" class="no-link">JavaScript Reference</a></h2>
|
|
118
|
+
|
|
119
|
+
<h3 id="constructor"><a href="#constructor" class="no-link">Constructor</a></h3>
|
|
120
|
+
<h6>Extends LightComponent</h6>
|
|
121
|
+
|
|
122
|
+
<h3 id="requirements"><a href="#requirements" class="no-link">Requirements</a></h3>
|
|
123
|
+
<ul>
|
|
124
|
+
<li><a href="{{pathToRoot}}components/light-component.html">LightComponent</a></li>
|
|
125
|
+
<li><code>marked</code> (vendored at <code>src/utils/marked.esm.js</code>) via <a href="{{pathToRoot}}utils/renderMarkdown.js"><code>renderMarkdown</code></a></li>
|
|
126
|
+
<li><a href="{{pathToRoot}}utils/sanitizeHtml.html"><code>sanitizeHtml</code></a></li>
|
|
127
|
+
</ul>
|
|
128
|
+
|
|
129
|
+
<h3 id="properties"><a href="#properties" class="no-link">Properties</a></h3>
|
|
130
|
+
<h5><code>value<i>: string</i></code></h5>
|
|
131
|
+
<p>The markdown source. If empty when the element connects, the component falls back to the dedented child text content. Setting this property at runtime re-renders the output.</p>
|
|
132
|
+
<h5><code>breaks<i>: boolean</i></code></h5>
|
|
133
|
+
<p>When <code>true</code>, single newlines convert to <code><br></code> instead of being joined into a paragraph. Defaults to <code>false</code>. Reflects to the <code>breaks</code> attribute.</p>
|
|
134
|
+
<h5><code>allowedTags<i>: string</i></code></h5>
|
|
135
|
+
<p>Comma-separated allowlist for the rendered HTML. Pass <code>*</code> to allow every tag. Mutually exclusive with <code>disallowedTags</code> — if both are set, <code>allowedTags</code> wins and a console warning is logged. Reflects to the <code>allowed-tags</code> attribute.</p>
|
|
136
|
+
<h5><code>disallowedTags<i>: string</i></code></h5>
|
|
137
|
+
<p>Comma-separated denylist — everything is allowed except the listed tags. Reflects to the <code>disallowed-tags</code> attribute.</p>
|
|
138
|
+
<h5><code>scriptsEnabled<i>: boolean</i></code></h5>
|
|
139
|
+
<p>When present, <code><script></code> tags survive sanitization (provided they also pass the allow/deny tag check). Defaults to off so script content can never reach the DOM. Reflects to the <code>scripts-enabled</code> attribute.</p>
|
|
140
|
+
|
|
141
|
+
<h3 id="methods"><a href="#methods" class="no-link">Methods</a></h3>
|
|
142
|
+
<h5><code>renderedHtml<i>: string</i></code> (getter)</h5>
|
|
143
|
+
<p>Returns the sanitized HTML string that was rendered — useful for copying the output or feeding it into another component.</p>
|
|
144
|
+
</content>
|
|
145
|
+
<content location="scripts">
|
|
146
|
+
<script type="module" src="{{pathToRoot}}src/components/Markdown.js"></script>
|
|
147
|
+
<script type="module" src="{{pathToRoot}}src/components/Accordion.js"></script>
|
|
148
|
+
<script type="module" src="{{pathToRoot}}src/components/Card.js"></script>
|
|
149
|
+
<script type="module">
|
|
150
|
+
const md = document.getElementById('dynamicMd');
|
|
151
|
+
const btn = document.getElementById('dynamicMdBtn');
|
|
152
|
+
let toggled = false;
|
|
153
|
+
btn.addEventListener('click', () => {
|
|
154
|
+
toggled = !toggled;
|
|
155
|
+
md.value = toggled
|
|
156
|
+
? '## Updated!\n\nThis was set via JavaScript with **markdown** and a [link](https://example.com).'
|
|
157
|
+
: 'Click the button.';
|
|
158
|
+
});
|
|
159
|
+
</script>
|
|
160
|
+
</content>
|
|
161
|
+
</page>
|
package/docs-src/index.page.html
CHANGED
|
@@ -125,6 +125,12 @@
|
|
|
125
125
|
<p class="tc-muted">WYSIWYG editor with formatting tools and dual editing modes.</p>
|
|
126
126
|
</a>
|
|
127
127
|
</div>
|
|
128
|
+
<div class="span-12 t-span-6 d-span-4 px">
|
|
129
|
+
<a href="{{pathToRoot}}components/markdown.html" class="card mb no-link d-b">
|
|
130
|
+
<h3 class="tc-primary">Markdown</h3>
|
|
131
|
+
<p class="tc-muted">Renders the markdown text inside the element as sanitized HTML in place.</p>
|
|
132
|
+
</a>
|
|
133
|
+
</div>
|
|
128
134
|
<div class="span-12 t-span-6 d-span-4 px">
|
|
129
135
|
<a href="{{pathToRoot}}components/markdown-editor.html" class="card mb no-link d-b">
|
|
130
136
|
<h3 class="tc-primary">Markdown Editor</h3>
|
|
@@ -82,6 +82,9 @@
|
|
|
82
82
|
<k-filter-item filter-keywords="html editor components"><a
|
|
83
83
|
href="{{pathToRoot}}components/html-editor.html">HTML
|
|
84
84
|
Editor<br><small>Component</small></a></k-filter-item>
|
|
85
|
+
<k-filter-item filter-keywords="markdown render parser marked gfm components"><a
|
|
86
|
+
href="{{pathToRoot}}components/markdown.html"
|
|
87
|
+
>Markdown<br><small>Component</small></a></k-filter-item>
|
|
85
88
|
<k-filter-item filter-keywords="markdown editor markdowneditor write preview marked gfm tables components"><a
|
|
86
89
|
href="{{pathToRoot}}components/markdown-editor.html"
|
|
87
90
|
>Markdown Editor<br><small>Component</small></a></k-filter-item>
|
|
@@ -292,6 +295,7 @@
|
|
|
292
295
|
<a href="{{pathToRoot}}components/filter-list.html">Filter List</a>
|
|
293
296
|
<a href="{{pathToRoot}}components/focus-capture.html">FocusCapture</a>
|
|
294
297
|
<a href="{{pathToRoot}}components/html-editor.html">HTML Editor</a>
|
|
298
|
+
<a href="{{pathToRoot}}components/markdown.html">Markdown</a>
|
|
295
299
|
<a href="{{pathToRoot}}components/markdown-editor.html">Markdown Editor</a>
|
|
296
300
|
<a href="{{pathToRoot}}components/icon.html">Icon</a>
|
|
297
301
|
<a href="{{pathToRoot}}components/import.html">Import</a>
|
package/llms.txt
CHANGED
|
@@ -67,6 +67,7 @@ import { html, css, LitElement } from 'kempo-ui/src/lit-all.min.js';
|
|
|
67
67
|
| `<k-filter-list>` `<k-filter-item>` | `FilterList.js` `FilterItem.js` | Filter/search list; items have `filter` attribute with keywords | [filter-list.html](https://dustinpoissant.github.io/kempo-ui/components/filter-list.html) |
|
|
68
68
|
| `<k-focus-capture>` | `FocusCapture.js` | Traps keyboard focus within children | [focus-capture.html](https://dustinpoissant.github.io/kempo-ui/components/focus-capture.html) |
|
|
69
69
|
| `<k-html-editor>` | `HtmlEditor.js` | WYSIWYG rich text editor; `value` property; `controls` attribute (`full`/`normal`/`minimal`/`none`) for built-in toolbars; `disabled`/`readonly`/`required` (Lexical's `setEditable()` + Monaco's `readOnly`); form-associated | [html-editor.html](https://dustinpoissant.github.io/kempo-ui/components/html-editor.html) |
|
|
70
|
+
| `<k-markdown>` | `Markdown.js` | Renders child markdown text as sanitized HTML in place; `value` attribute (overrides children); `breaks` (single newline → `<br>`); `allowed-tags`/`disallowed-tags`/`scripts-enabled` for sanitization; light DOM render; `renderedHtml` getter | [markdown.html](https://dustinpoissant.github.io/kempo-ui/components/markdown.html) |
|
|
70
71
|
| `<k-markdown-editor>` | `MarkdownEditor.js` | Markdown editor with Write/Preview tabs; raw markdown is the form value; rendered with vendored `marked` (full CommonMark + GFM — tables, task lists, fenced code, autolinks, inline HTML) via `renderMarkdown.js`; then sanitized via `sanitizeHtml` using either `allowed-tags` (comma-separated allowlist; pass `*` for everything) OR `disallowed-tags` (denylist) — mutually exclusive, allowlist wins if both set; defaults cover standard markdown output incl. img/hr/table family; `<script>` is always stripped unless `scripts-enabled` attribute is set; iframe/style/etc. are always stripped; `value`, `name`, `placeholder`, `disabled`/`readonly`/`required`, `mode` (`write`/`preview`), `allowed-tags`, `controls` (`'minimal'`/`'normal'`/`'full'`/`'none'` — pre-built toolbar; auto-imports the relevant control modules; user-slotted children override the preset); methods `focus()`, `blur()`, `clear()`, `setMode()`, `togglePreview()`, plus selection helpers `getSelection()`, `replaceSelection()`, `wrapSelection()`, `insertAtCursor()`, `insertLinePrefix()`; `controls-top`/`controls-bottom` slots for toolbar buttons; built-in controls under `markdownEditorControls/` (`k-md-bold`, `k-md-italic`, `k-md-strikethrough` (`~~...~~`), `k-md-heading` w/ `level`, `k-md-code`, `k-md-link`, `k-md-image` w/ `popup` (opens a Dropdown by default — `popup="dialog"` to switch to modal Dialog — for URL + alt text), `k-md-table` (small Dropdown with cols × rows inputs + Create button, inserts a GFM markdown table), `k-md-menu` w/ `label` (toolbar button that opens a Dropdown of slotted child controls; `trigger` slot fully customizes the button content — defaults to a `menu` icon), `k-md-format-block` w/ `tag` (heading picker designed to live inside `k-md-menu` — `h1`-`h6`), `k-md-speech-to-text` w/ `language`/`continuous` (wraps `<k-speech-to-text>`; final transcript is inserted at the cursor when the user stops speaking), `k-md-bullet-list`, `k-md-numbered-list`, `k-md-quote`); extend `MarkdownEditorControl` for custom buttons (incl. ones that insert raw HTML — adjust `allowed-tags` accordingly); fires `input`, `change`, `mode-change` | [markdown-editor.html](https://dustinpoissant.github.io/kempo-ui/components/markdown-editor.html) |
|
|
71
72
|
| `<k-icon>` | `Icon.js` | SVG icon; `name` (looked up in `pathsToIcons`) or `src` (direct URL); `rotation`, `direction`, `animation` attributes | [icon.html](https://dustinpoissant.github.io/kempo-ui/components/icon.html) |
|
|
72
73
|
| `<k-import>` | `Import.js` | Includes external HTML fragments; `src` attribute | [import.html](https://dustinpoissant.github.io/kempo-ui/components/import.html) |
|
package/package.json
CHANGED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { html, unsafeHTML } from '../lit-all.min.js';
|
|
2
|
+
import LightComponent from './LightComponent.js';
|
|
3
|
+
import renderMarkdown from '../utils/renderMarkdown.js';
|
|
4
|
+
import sanitizeHtml, { STRIP_COMPLETELY } from '../utils/sanitizeHtml.js';
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
k-markdown
|
|
8
|
+
|
|
9
|
+
Reads markdown from its child text content (or the `value` attribute) and
|
|
10
|
+
renders the HTML equivalent in-place. Markdown is parsed with the vendored
|
|
11
|
+
`marked` (CommonMark + GFM) and the resulting HTML is run through
|
|
12
|
+
`sanitizeHtml` before being inserted, with the same allowed-tags /
|
|
13
|
+
disallowed-tags / scripts-enabled controls as MarkdownEditor's preview.
|
|
14
|
+
|
|
15
|
+
Renders to the light DOM so page-level typography and styles cascade into
|
|
16
|
+
headings, paragraphs, tables, etc. without extra wiring.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export default class Markdown extends LightComponent {
|
|
20
|
+
static properties = {
|
|
21
|
+
value: { type: String },
|
|
22
|
+
breaks: { type: Boolean, reflect: true },
|
|
23
|
+
allowedTags: { type: String, reflect: true, attribute: 'allowed-tags' },
|
|
24
|
+
disallowedTags: { type: String, reflect: true, attribute: 'disallowed-tags' },
|
|
25
|
+
scriptsEnabled: { type: Boolean, reflect: true, attribute: 'scripts-enabled' }
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
#captured = false;
|
|
29
|
+
|
|
30
|
+
/*
|
|
31
|
+
Lifecycle Callbacks
|
|
32
|
+
*/
|
|
33
|
+
constructor() {
|
|
34
|
+
super();
|
|
35
|
+
this.value = '';
|
|
36
|
+
this.breaks = false;
|
|
37
|
+
this.allowedTags = '';
|
|
38
|
+
this.disallowedTags = '';
|
|
39
|
+
this.scriptsEnabled = false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
connectedCallback() {
|
|
43
|
+
// Capture and clear the original markdown children BEFORE Lit appends
|
|
44
|
+
// its lightRoot div — once super runs, lightRoot lives among the
|
|
45
|
+
// siblings we'd otherwise scan. After capture, the markdown lives on
|
|
46
|
+
// the `value` property; the source nodes are dropped so the parsed
|
|
47
|
+
// HTML doesn't render alongside its raw source.
|
|
48
|
+
if(!this.#captured){
|
|
49
|
+
this.#captured = true;
|
|
50
|
+
const text = this.textContent || '';
|
|
51
|
+
const dedented = dedent(text);
|
|
52
|
+
while(this.firstChild) this.removeChild(this.firstChild);
|
|
53
|
+
if(!this.hasAttribute('value') && !this.value && dedented){
|
|
54
|
+
this.value = dedented;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
super.connectedCallback();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/*
|
|
61
|
+
Public Methods
|
|
62
|
+
*/
|
|
63
|
+
get renderedHtml() {
|
|
64
|
+
const opts = {};
|
|
65
|
+
const resolved = this.#resolvedAllowedTags;
|
|
66
|
+
if(resolved) opts.allowedTags = resolved;
|
|
67
|
+
if(this.scriptsEnabled){
|
|
68
|
+
const stripCompletely = new Set(STRIP_COMPLETELY);
|
|
69
|
+
stripCompletely.delete('SCRIPT');
|
|
70
|
+
opts.stripCompletely = stripCompletely;
|
|
71
|
+
}
|
|
72
|
+
return sanitizeHtml(renderMarkdown(this.value || '', { breaks: this.breaks }), opts);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/*
|
|
76
|
+
Utility
|
|
77
|
+
*/
|
|
78
|
+
get #resolvedAllowedTags() {
|
|
79
|
+
const allow = (this.allowedTags || '').trim();
|
|
80
|
+
const deny = (this.disallowedTags || '').trim();
|
|
81
|
+
if(allow && deny){
|
|
82
|
+
console.warn('[k-markdown] `allowed-tags` and `disallowed-tags` are mutually exclusive; using `allowed-tags`.');
|
|
83
|
+
}
|
|
84
|
+
if(allow){
|
|
85
|
+
if(allow === '*') return { has: () => true };
|
|
86
|
+
return new Set(allow.split(',').map(t => t.trim().toUpperCase()).filter(Boolean));
|
|
87
|
+
}
|
|
88
|
+
if(deny){
|
|
89
|
+
const denySet = new Set(deny.split(',').map(t => t.trim().toUpperCase()).filter(Boolean));
|
|
90
|
+
return { has: (tag) => !denySet.has(tag) };
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/*
|
|
96
|
+
Rendering
|
|
97
|
+
*/
|
|
98
|
+
renderLightDom() {
|
|
99
|
+
if(!this.value) return html``;
|
|
100
|
+
return html`${unsafeHTML(this.renderedHtml)}`;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/*
|
|
105
|
+
Strip a common leading indent from every line so users can indent the
|
|
106
|
+
markdown source to match the surrounding HTML without breaking parsing.
|
|
107
|
+
Also trims fully-blank leading/trailing lines.
|
|
108
|
+
*/
|
|
109
|
+
const dedent = (text) => {
|
|
110
|
+
const lines = text.split('\n');
|
|
111
|
+
while(lines.length && lines[0].trim() === '') lines.shift();
|
|
112
|
+
while(lines.length && lines[lines.length - 1].trim() === '') lines.pop();
|
|
113
|
+
if(!lines.length) return '';
|
|
114
|
+
let minIndent = Infinity;
|
|
115
|
+
for(const line of lines){
|
|
116
|
+
if(line.trim().length === 0) continue;
|
|
117
|
+
const match = line.match(/^(\s*)/);
|
|
118
|
+
const indent = match ? match[1].length : 0;
|
|
119
|
+
if(indent < minIndent) minIndent = indent;
|
|
120
|
+
}
|
|
121
|
+
if(minIndent === Infinity) minIndent = 0;
|
|
122
|
+
return lines.map(l => l.slice(minIndent)).join('\n');
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
customElements.define('k-markdown', Markdown);
|
|
@@ -4,14 +4,14 @@ import formatTimestamp from '../utils/formatTimestamp.js';
|
|
|
4
4
|
|
|
5
5
|
export default class Timestamp extends ShadowComponent {
|
|
6
6
|
static properties = {
|
|
7
|
-
timestamp: { type:
|
|
7
|
+
timestamp: { type: String, reflect: true },
|
|
8
8
|
format: { type: String, reflect: true },
|
|
9
9
|
locale: { type: String, reflect: true }
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
constructor() {
|
|
13
13
|
super();
|
|
14
|
-
this.timestamp =
|
|
14
|
+
this.timestamp = '';
|
|
15
15
|
this.format = '';
|
|
16
16
|
this.locale = '';
|
|
17
17
|
}
|
|
@@ -20,7 +20,7 @@ export default class Timestamp extends ShadowComponent {
|
|
|
20
20
|
Rendering Logic
|
|
21
21
|
*/
|
|
22
22
|
render() {
|
|
23
|
-
const formattedTime = this.timestamp
|
|
23
|
+
const formattedTime = this.timestamp
|
|
24
24
|
? formatTimestamp(this.timestamp, this.format, this.locale || navigator.language)
|
|
25
25
|
: '';
|
|
26
26
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export default (timestamp, format, forceLocale) => {
|
|
2
|
-
const
|
|
2
|
+
const ms = isNaN(timestamp) ? Date.parse(timestamp) : parseInt(timestamp);
|
|
3
|
+
const date = new Date(ms);
|
|
3
4
|
if (format) {
|
|
4
5
|
const pad = (num, size) => ('000' + num).slice(size * -1);
|
|
5
6
|
const tokens = {
|