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.
Files changed (91) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/components/Markdown.js +1 -0
  3. package/dist/components/Timestamp.js +1 -1
  4. package/dist/utils/formatTimestamp.js +1 -1
  5. package/docs/components/accordion.html +4 -0
  6. package/docs/components/aside.html +4 -0
  7. package/docs/components/calendar.html +4 -0
  8. package/docs/components/card.html +4 -0
  9. package/docs/components/chat.html +4 -0
  10. package/docs/components/code-editor.html +4 -0
  11. package/docs/components/color-picker.html +4 -0
  12. package/docs/components/combobox.html +4 -0
  13. package/docs/components/content-slider.html +4 -0
  14. package/docs/components/context.html +4 -0
  15. package/docs/components/dialog.html +4 -0
  16. package/docs/components/dropdown.html +4 -0
  17. package/docs/components/filter-list.html +4 -0
  18. package/docs/components/focus-capture.html +4 -0
  19. package/docs/components/html-editor.html +4 -0
  20. package/docs/components/hybrid-component.html +4 -0
  21. package/docs/components/icon.html +4 -0
  22. package/docs/components/import.html +4 -0
  23. package/docs/components/light-component.html +4 -0
  24. package/docs/components/markdown-editor.html +4 -0
  25. package/docs/components/markdown.html +650 -0
  26. package/docs/components/nav-spacer.html +4 -0
  27. package/docs/components/nav.html +4 -0
  28. package/docs/components/photo-viewer.html +4 -0
  29. package/docs/components/progress.html +4 -0
  30. package/docs/components/resize.html +4 -0
  31. package/docs/components/shadow-component.html +4 -0
  32. package/docs/components/show-more.html +4 -0
  33. package/docs/components/slider.html +4 -0
  34. package/docs/components/sortable.html +4 -0
  35. package/docs/components/speech-to-text.html +4 -0
  36. package/docs/components/spinner.html +4 -0
  37. package/docs/components/split.html +4 -0
  38. package/docs/components/table.html +4 -0
  39. package/docs/components/tableControls.html +4 -0
  40. package/docs/components/tableCustomFields.html +4 -0
  41. package/docs/components/tableFetchRecords.html +4 -0
  42. package/docs/components/tableFieldSortHide.html +4 -0
  43. package/docs/components/tablePagination.html +4 -0
  44. package/docs/components/tablePlaceholder.html +4 -0
  45. package/docs/components/tableRecordEditing.html +4 -0
  46. package/docs/components/tableRecordFiltering.html +4 -0
  47. package/docs/components/tableRecordHiding.html +4 -0
  48. package/docs/components/tableRecordSearching.html +4 -0
  49. package/docs/components/tableRecordSelection.html +4 -0
  50. package/docs/components/tableRowControls.html +4 -0
  51. package/docs/components/tableServerSync.html +4 -0
  52. package/docs/components/tableSorting.html +4 -0
  53. package/docs/components/tabs.html +4 -0
  54. package/docs/components/tags.html +4 -0
  55. package/docs/components/text-to-speech.html +4 -0
  56. package/docs/components/theme-select.html +4 -0
  57. package/docs/components/theme-switcher.html +4 -0
  58. package/docs/components/time.html +4 -0
  59. package/docs/components/timestamp.html +4 -0
  60. package/docs/components/toast.html +4 -0
  61. package/docs/components/toggle.html +4 -0
  62. package/docs/components/tree.html +4 -0
  63. package/docs/components/voice-selector.html +4 -0
  64. package/docs/index.html +10 -0
  65. package/docs/src/components/Markdown.js +1 -0
  66. package/docs/src/components/Timestamp.js +1 -1
  67. package/docs/src/utils/formatTimestamp.js +1 -1
  68. package/docs/utils/context.html +4 -0
  69. package/docs/utils/cookie.html +4 -0
  70. package/docs/utils/debounce.html +4 -0
  71. package/docs/utils/drag.html +4 -0
  72. package/docs/utils/elevation.html +4 -0
  73. package/docs/utils/formatTimestamp.html +4 -0
  74. package/docs/utils/object.html +4 -0
  75. package/docs/utils/propConverters.html +4 -0
  76. package/docs/utils/sanitizeHtml.html +4 -0
  77. package/docs/utils/string.html +4 -0
  78. package/docs/utils/theme.html +4 -0
  79. package/docs/utils/toTitleCase.html +4 -0
  80. package/docs/utils/type.html +4 -0
  81. package/docs/utils/voice.html +4 -0
  82. package/docs/utils/wait.html +4 -0
  83. package/docs-src/components/markdown.page.html +161 -0
  84. package/docs-src/index.page.html +6 -0
  85. package/docs-src/nav.fragment.html +4 -0
  86. package/llms.txt +1 -0
  87. package/package.json +1 -1
  88. package/src/components/Markdown.js +125 -0
  89. package/src/components/Timestamp.js +3 -3
  90. package/src/utils/formatTimestamp.js +2 -1
  91. 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 &mdash; 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">&lt;<span class="hljs-name">k-markdown</span>&gt;</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">&lt;/<span class="hljs-name">k-markdown</span>&gt;</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">&lt;<span class="hljs-name">k-markdown</span> <span class="hljs-attr">value</span>=<span class="hljs-string">&quot;Visit [Kempo UI](https://github.com/dustinpoissant/kempo-ui) for more.&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-markdown</span>&gt;</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>&lt;br&gt;</code> &mdash; the Slack/iMessage convention &mdash; 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">&lt;<span class="hljs-name">k-markdown</span> <span class="hljs-attr">breaks</span>&gt;</span><br> Line one<br> Line two<br> Line three<br><span class="hljs-tag">&lt;/<span class="hljs-name">k-markdown</span>&gt;</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 &mdash; the two are mutually exclusive. <code>&lt;script&gt;</code>, <code>&lt;iframe&gt;</code>, <code>&lt;style&gt;</code>, and similar tags are always stripped unless you opt in with <code>scripts-enabled</code> (which only restores <code>&lt;script&gt;</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">&lt;<span class="hljs-name">k-markdown</span> <span class="hljs-attr">allowed-tags</span>=<span class="hljs-string">&quot;p,strong,em,a&quot;</span>&gt;</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">&lt;/<span class="hljs-name">k-markdown</span>&gt;</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">&lt;<span class="hljs-name">k-markdown</span> <span class="hljs-attr">id</span>=<span class="hljs-string">&quot;dynamicMd&quot;</span> <span class="hljs-attr">value</span>=<span class="hljs-string">&quot;Click the button.&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-markdown</span>&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">&quot;dynamicMdBtn&quot;</span>&gt;</span>Update<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">&quot;module&quot;</span>&gt;</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">&#x27;dynamicMd&#x27;</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">&#x27;dynamicMdBtn&#x27;</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">&#x27;click&#x27;</span>, <span class="hljs-function">() =&gt;</span> {<br> toggled = !toggled;<br> md.<span class="hljs-property">value</span> = toggled<br> ? <span class="hljs-string">&#x27;## Updated!\n\nThis was set via JavaScript with **markdown** and a [link](https://example.com).&#x27;</span><br> : <span class="hljs-string">&#x27;Click the button.&#x27;</span>;<br> });<br></span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</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>&lt;br&gt;</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> &mdash; 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 &mdash; 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>&lt;script&gt;</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 &mdash; 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>
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kempo-ui",
3
- "version": "0.4.6",
3
+ "version": "0.4.8",
4
4
  "type": "module",
5
5
  "description": "A Lit based web-component library",
6
6
  "main": "index.js",
@@ -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: Number, reflect: true },
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 = 0;
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 date = new Date(parseInt(timestamp));
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 = {