kempo-ui 0.4.5 → 0.4.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/dist/components/Chat.js +201 -0
- package/dist/components/CodeEditor.js +21 -2
- package/dist/components/Dropdown.js +1 -1
- package/dist/components/HtmlEditor.js +42 -3
- package/dist/components/MarkdownEditor.js +173 -0
- package/dist/components/Resize.js +12 -2
- package/dist/components/markdownEditorControls/Bold.js +11 -0
- package/dist/components/markdownEditorControls/BulletList.js +11 -0
- package/dist/components/markdownEditorControls/Code.js +11 -0
- package/dist/components/markdownEditorControls/FormatBlock.js +30 -0
- package/dist/components/markdownEditorControls/Heading.js +11 -0
- package/dist/components/markdownEditorControls/Image.js +93 -0
- package/dist/components/markdownEditorControls/Italic.js +11 -0
- package/dist/components/markdownEditorControls/Link.js +11 -0
- package/dist/components/markdownEditorControls/MarkdownEditorControl.js +39 -0
- package/dist/components/markdownEditorControls/Menu.js +30 -0
- package/dist/components/markdownEditorControls/NumberedList.js +11 -0
- package/dist/components/markdownEditorControls/Quote.js +11 -0
- package/dist/components/markdownEditorControls/SpeechToText.js +16 -0
- package/dist/components/markdownEditorControls/Strikethrough.js +11 -0
- package/dist/components/markdownEditorControls/Table.js +80 -0
- package/dist/utils/marked.esm.js +1 -0
- package/dist/utils/renderMarkdown.js +1 -0
- package/dist/utils/sanitizeHtml.js +1 -0
- package/docs/components/accordion.html +12 -0
- package/docs/components/aside.html +12 -0
- package/docs/components/calendar.html +12 -0
- package/docs/components/card.html +12 -0
- package/docs/components/chat.html +715 -0
- package/docs/components/code-editor.html +48 -0
- package/docs/components/color-picker.html +12 -0
- package/docs/components/combobox.html +12 -0
- package/docs/components/content-slider.html +12 -0
- package/docs/components/context.html +12 -0
- package/docs/components/dialog.html +12 -0
- package/docs/components/dropdown.html +12 -0
- package/docs/components/filter-list.html +12 -0
- package/docs/components/focus-capture.html +12 -0
- package/docs/components/html-editor.html +53 -0
- package/docs/components/hybrid-component.html +12 -0
- package/docs/components/icon.html +12 -0
- package/docs/components/import.html +12 -0
- package/docs/components/light-component.html +12 -0
- package/docs/components/markdown-editor.html +924 -0
- package/docs/components/nav-spacer.html +12 -0
- package/docs/components/nav.html +12 -0
- package/docs/components/photo-viewer.html +12 -0
- package/docs/components/progress.html +12 -0
- package/docs/components/resize.html +15 -0
- package/docs/components/rich-textarea.html +650 -0
- package/docs/components/shadow-component.html +12 -0
- package/docs/components/show-more.html +12 -0
- package/docs/components/slider.html +12 -0
- package/docs/components/sortable.html +12 -0
- package/docs/components/speech-to-text.html +12 -0
- package/docs/components/spinner.html +12 -0
- package/docs/components/split.html +12 -0
- package/docs/components/table.html +12 -0
- package/docs/components/tableControls.html +12 -0
- package/docs/components/tableCustomFields.html +12 -0
- package/docs/components/tableFetchRecords.html +12 -0
- package/docs/components/tableFieldSortHide.html +12 -0
- package/docs/components/tablePagination.html +12 -0
- package/docs/components/tablePlaceholder.html +12 -0
- package/docs/components/tableRecordEditing.html +12 -0
- package/docs/components/tableRecordFiltering.html +12 -0
- package/docs/components/tableRecordHiding.html +12 -0
- package/docs/components/tableRecordSearching.html +12 -0
- package/docs/components/tableRecordSelection.html +12 -0
- package/docs/components/tableRowControls.html +12 -0
- package/docs/components/tableServerSync.html +12 -0
- package/docs/components/tableSorting.html +12 -0
- package/docs/components/tabs.html +12 -0
- package/docs/components/tags.html +12 -0
- package/docs/components/text-to-speech.html +12 -0
- package/docs/components/theme-select.html +12 -0
- package/docs/components/theme-switcher.html +12 -0
- package/docs/components/time.html +12 -0
- package/docs/components/timestamp.html +12 -0
- package/docs/components/toast.html +12 -0
- package/docs/components/toggle.html +12 -0
- package/docs/components/tree.html +12 -0
- package/docs/components/voice-selector.html +12 -0
- package/docs/icons/done_all.svg +1 -0
- package/docs/icons/format_quote.svg +1 -0
- package/docs/icons/format_strikethrough.svg +1 -0
- package/docs/icons/send.svg +1 -0
- package/docs/icons/text_fields.svg +1 -0
- package/docs/index.html +30 -0
- package/docs/src/components/Chat.js +201 -0
- package/docs/src/components/CodeEditor.js +21 -2
- package/docs/src/components/Dropdown.js +1 -1
- package/docs/src/components/HtmlEditor.js +42 -3
- package/docs/src/components/MarkdownEditor.js +173 -0
- package/docs/src/components/Resize.js +12 -2
- package/docs/src/components/markdownEditorControls/Bold.js +11 -0
- package/docs/src/components/markdownEditorControls/BulletList.js +11 -0
- package/docs/src/components/markdownEditorControls/Code.js +11 -0
- package/docs/src/components/markdownEditorControls/FormatBlock.js +30 -0
- package/docs/src/components/markdownEditorControls/Heading.js +11 -0
- package/docs/src/components/markdownEditorControls/Image.js +93 -0
- package/docs/src/components/markdownEditorControls/Italic.js +11 -0
- package/docs/src/components/markdownEditorControls/Link.js +11 -0
- package/docs/src/components/markdownEditorControls/MarkdownEditorControl.js +39 -0
- package/docs/src/components/markdownEditorControls/Menu.js +30 -0
- package/docs/src/components/markdownEditorControls/NumberedList.js +11 -0
- package/docs/src/components/markdownEditorControls/Quote.js +11 -0
- package/docs/src/components/markdownEditorControls/SpeechToText.js +16 -0
- package/docs/src/components/markdownEditorControls/Strikethrough.js +11 -0
- package/docs/src/components/markdownEditorControls/Table.js +80 -0
- package/docs/src/utils/marked.esm.js +1 -0
- package/docs/src/utils/renderMarkdown.js +1 -0
- package/docs/src/utils/sanitizeHtml.js +1 -0
- package/docs/utils/context.html +12 -0
- package/docs/utils/cookie.html +12 -0
- package/docs/utils/debounce.html +12 -0
- package/docs/utils/drag.html +12 -0
- package/docs/utils/elevation.html +16 -10
- package/docs/utils/formatTimestamp.html +12 -0
- package/docs/utils/object.html +12 -0
- package/docs/utils/propConverters.html +12 -0
- package/docs/utils/sanitizeHtml.html +555 -0
- package/docs/utils/string.html +12 -0
- package/docs/utils/theme.html +12 -0
- package/docs/utils/toTitleCase.html +12 -0
- package/docs/utils/type.html +12 -0
- package/docs/utils/voice.html +12 -0
- package/docs/utils/wait.html +12 -0
- package/docs-src/components/chat.page.html +230 -0
- package/docs-src/components/code-editor.page.html +36 -0
- package/docs-src/components/html-editor.page.html +41 -0
- package/docs-src/components/markdown-editor.page.html +439 -0
- package/docs-src/components/resize.page.html +3 -0
- package/docs-src/index.page.html +18 -0
- package/docs-src/nav.fragment.html +12 -0
- package/docs-src/utils/elevation.page.html +4 -10
- package/docs-src/utils/sanitizeHtml.page.html +73 -0
- package/icons/done_all.svg +1 -0
- package/icons/format_quote.svg +1 -0
- package/icons/format_strikethrough.svg +1 -0
- package/icons/send.svg +1 -0
- package/icons/text_fields.svg +1 -0
- package/llms.txt +5 -2
- package/package.json +3 -2
- package/src/components/Chat.js +435 -0
- package/src/components/CodeEditor.js +58 -2
- package/src/components/Dropdown.js +1 -1
- package/src/components/HtmlEditor.js +84 -4
- package/src/components/MarkdownEditor.js +654 -0
- package/src/components/Resize.js +17 -1
- package/src/components/markdownEditorControls/Bold.js +30 -0
- package/src/components/markdownEditorControls/BulletList.js +30 -0
- package/src/components/markdownEditorControls/Code.js +39 -0
- package/src/components/markdownEditorControls/FormatBlock.js +102 -0
- package/src/components/markdownEditorControls/Heading.js +45 -0
- package/src/components/markdownEditorControls/Image.js +250 -0
- package/src/components/markdownEditorControls/Italic.js +30 -0
- package/src/components/markdownEditorControls/Link.js +74 -0
- package/src/components/markdownEditorControls/MarkdownEditorControl.js +158 -0
- package/src/components/markdownEditorControls/Menu.js +86 -0
- package/src/components/markdownEditorControls/NumberedList.js +69 -0
- package/src/components/markdownEditorControls/Quote.js +30 -0
- package/src/components/markdownEditorControls/SpeechToText.js +65 -0
- package/src/components/markdownEditorControls/Strikethrough.js +35 -0
- package/src/components/markdownEditorControls/Table.js +164 -0
- package/src/utils/marked.esm.js +77 -0
- package/src/utils/renderMarkdown.js +29 -0
- package/src/utils/sanitizeHtml.js +137 -0
- package/tests/components/Chat.browser-test.js +540 -0
- package/tests/utils/sanitizeHtml.browser-test.js +179 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import ShadowComponent from '../ShadowComponent.js';
|
|
2
|
+
import { html, css } from '../../lit-all.min.js';
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
Base class for <k-markdown-editor> control buttons. Subclass and override
|
|
6
|
+
`command()` (or `render()` for custom UI) to wire up a toolbar button to a
|
|
7
|
+
text manipulation. The editor is found via closest('k-markdown-editor');
|
|
8
|
+
helper methods on the editor (wrapSelection, insertLinePrefix,
|
|
9
|
+
replaceSelection, insertAtCursor) handle the textarea selection math.
|
|
10
|
+
|
|
11
|
+
Mirrors HtmlEditorControl in spirit, scoped to MarkdownEditor.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export default class MarkdownEditorControl extends ShadowComponent {
|
|
15
|
+
static properties = {
|
|
16
|
+
btnClass: { type: String, attribute: 'btn-class' },
|
|
17
|
+
label: { type: String, reflect: true },
|
|
18
|
+
hidden: { type: Boolean, reflect: true }
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
constructor() {
|
|
22
|
+
super();
|
|
23
|
+
this.btnClass = 'no-btn ctrl';
|
|
24
|
+
this.label = '';
|
|
25
|
+
this.hidesInPreviewMode = true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
connectedCallback() {
|
|
29
|
+
super.connectedCallback();
|
|
30
|
+
const editor = this.editor;
|
|
31
|
+
if(!editor) return;
|
|
32
|
+
this.boundEditor = editor;
|
|
33
|
+
this.modeHandler = () => {
|
|
34
|
+
const shouldHide = this.hidesInPreviewMode && this.boundEditor.mode === 'preview';
|
|
35
|
+
if(this.hidden !== shouldHide){
|
|
36
|
+
this.hidden = shouldHide;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
// Reflect initial state
|
|
40
|
+
this.modeHandler();
|
|
41
|
+
editor.addEventListener('mode-change', this.modeHandler);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
disconnectedCallback() {
|
|
45
|
+
super.disconnectedCallback();
|
|
46
|
+
if(this.boundEditor && this.modeHandler){
|
|
47
|
+
this.boundEditor.removeEventListener('mode-change', this.modeHandler);
|
|
48
|
+
}
|
|
49
|
+
this.boundEditor = null;
|
|
50
|
+
this.modeHandler = null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/*
|
|
54
|
+
Editor lookup. `closest()` works when the control is slotted in light
|
|
55
|
+
DOM (the most common case). When the control is rendered as slot
|
|
56
|
+
fallback content it lives inside the editor's own shadow root, where
|
|
57
|
+
`closest()` can't cross the shadow boundary — fall through to the
|
|
58
|
+
shadow root's host in that case.
|
|
59
|
+
*/
|
|
60
|
+
get editor() {
|
|
61
|
+
const direct = this.closest('k-markdown-editor');
|
|
62
|
+
if(direct) return direct;
|
|
63
|
+
const root = this.getRootNode();
|
|
64
|
+
if(root instanceof ShadowRoot && root.host?.tagName === 'K-MARKDOWN-EDITOR'){
|
|
65
|
+
return root.host;
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/*
|
|
71
|
+
Convenience helpers — subclasses use these in `command()` to manipulate
|
|
72
|
+
the editor's textarea. Each is a thin pass-through with a guard.
|
|
73
|
+
*/
|
|
74
|
+
wrapSelection(prefix, suffix, placeholder) {
|
|
75
|
+
this.editor?.wrapSelection(prefix, suffix, placeholder);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
insertAtCursor(text) {
|
|
79
|
+
this.editor?.insertAtCursor(text);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
insertLinePrefix(prefix, replacePattern) {
|
|
83
|
+
this.editor?.insertLinePrefix(prefix, replacePattern);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
replaceInSelectedLines(pattern, replacement) {
|
|
87
|
+
this.editor?.replaceInSelectedLines(pattern, replacement);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
replaceSelection(text, options) {
|
|
91
|
+
this.editor?.replaceSelection(text, options);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
getSelection() {
|
|
95
|
+
return this.editor?.getSelection() || { start: 0, end: 0, text: '' };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/*
|
|
99
|
+
Override in subclasses to do the work. Default no-op.
|
|
100
|
+
*/
|
|
101
|
+
command() {}
|
|
102
|
+
|
|
103
|
+
handleClick = (e) => {
|
|
104
|
+
e.preventDefault();
|
|
105
|
+
if(this.hidden) return;
|
|
106
|
+
this.command();
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/*
|
|
110
|
+
Default render: a button with the provided label (or whatever you slot
|
|
111
|
+
in). Subclasses can override for custom UI (icon button, dropdown, etc.).
|
|
112
|
+
*/
|
|
113
|
+
render() {
|
|
114
|
+
return html`
|
|
115
|
+
<button
|
|
116
|
+
type="button"
|
|
117
|
+
class=${this.btnClass}
|
|
118
|
+
title=${this.label}
|
|
119
|
+
aria-label=${this.label}
|
|
120
|
+
@click=${this.handleClick}
|
|
121
|
+
>
|
|
122
|
+
<slot>${this.label}</slot>
|
|
123
|
+
</button>
|
|
124
|
+
`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
static styles = css`
|
|
128
|
+
:host {
|
|
129
|
+
display: inline-flex;
|
|
130
|
+
}
|
|
131
|
+
:host([hidden]) {
|
|
132
|
+
display: none;
|
|
133
|
+
}
|
|
134
|
+
.ctrl {
|
|
135
|
+
display: inline-flex;
|
|
136
|
+
align-items: center;
|
|
137
|
+
justify-content: center;
|
|
138
|
+
padding: var(--spacer_h);
|
|
139
|
+
border: 1px solid transparent;
|
|
140
|
+
border-radius: var(--radius);
|
|
141
|
+
background: transparent;
|
|
142
|
+
color: var(--tc_muted);
|
|
143
|
+
cursor: pointer;
|
|
144
|
+
font: inherit;
|
|
145
|
+
transition: background var(--animation_ms), color var(--animation_ms);
|
|
146
|
+
}
|
|
147
|
+
.ctrl:hover {
|
|
148
|
+
background: var(--c_bg__alt);
|
|
149
|
+
color: var(--tc);
|
|
150
|
+
}
|
|
151
|
+
.ctrl:focus-visible {
|
|
152
|
+
outline: none;
|
|
153
|
+
box-shadow: var(--focus_shadow);
|
|
154
|
+
}
|
|
155
|
+
`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
customElements.define('k-md-control', MarkdownEditorControl);
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import MarkdownEditorControl from './MarkdownEditorControl.js';
|
|
2
|
+
import { html, css } from '../../lit-all.min.js';
|
|
3
|
+
import '../Icon.js';
|
|
4
|
+
import '../Dropdown.js';
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
Menu control. Renders as a single toolbar button that, when clicked, opens
|
|
8
|
+
a Dropdown containing whatever child controls have been slotted in. Useful
|
|
9
|
+
for grouping less-frequent actions (like the heading picker) so they don't
|
|
10
|
+
take up space in the main toolbar.
|
|
11
|
+
|
|
12
|
+
The trigger button content is fully customizable via the `trigger` slot —
|
|
13
|
+
drop in whatever combination of icons / text you want. If nothing is
|
|
14
|
+
slotted, a default `menu` icon is shown.
|
|
15
|
+
|
|
16
|
+
The default slot is the menu's items.
|
|
17
|
+
|
|
18
|
+
Example (default trigger):
|
|
19
|
+
<k-md-menu label="Heading">
|
|
20
|
+
<k-md-format-block tag="h1"></k-md-format-block>
|
|
21
|
+
...
|
|
22
|
+
</k-md-menu>
|
|
23
|
+
|
|
24
|
+
Example (custom trigger showing a heading-range icon):
|
|
25
|
+
<k-md-menu label="Heading">
|
|
26
|
+
<span slot="trigger">
|
|
27
|
+
<k-icon name="format_h1"></k-icon> - <k-icon name="format_h6"></k-icon>
|
|
28
|
+
</span>
|
|
29
|
+
<k-md-format-block tag="h1"></k-md-format-block>
|
|
30
|
+
...
|
|
31
|
+
</k-md-menu>
|
|
32
|
+
*/
|
|
33
|
+
export default class MarkdownMenu extends MarkdownEditorControl {
|
|
34
|
+
constructor() {
|
|
35
|
+
super();
|
|
36
|
+
this.label = 'Menu';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/*
|
|
40
|
+
The trigger is wired to Dropdown's own click handling, so the
|
|
41
|
+
base-class handleClick (which calls command()) would fire on top and
|
|
42
|
+
create a feedback loop. command() is a no-op here; child controls inside
|
|
43
|
+
the dropdown drive their own commands.
|
|
44
|
+
*/
|
|
45
|
+
command() {}
|
|
46
|
+
|
|
47
|
+
render() {
|
|
48
|
+
return html`
|
|
49
|
+
<k-dropdown>
|
|
50
|
+
<button
|
|
51
|
+
slot="trigger"
|
|
52
|
+
type="button"
|
|
53
|
+
class=${this.btnClass}
|
|
54
|
+
title=${this.label}
|
|
55
|
+
aria-label=${this.label}
|
|
56
|
+
>
|
|
57
|
+
<slot name="trigger">
|
|
58
|
+
<k-icon name="menu"></k-icon>
|
|
59
|
+
</slot>
|
|
60
|
+
</button>
|
|
61
|
+
<slot></slot>
|
|
62
|
+
</k-dropdown>
|
|
63
|
+
`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static styles = [
|
|
67
|
+
MarkdownEditorControl.styles,
|
|
68
|
+
css`
|
|
69
|
+
:host {
|
|
70
|
+
display: inline-flex;
|
|
71
|
+
}
|
|
72
|
+
k-dropdown {
|
|
73
|
+
display: inline-flex;
|
|
74
|
+
}
|
|
75
|
+
/* Stretch slotted menu items so each one fills the dropdown
|
|
76
|
+
horizontally — Dropdown's built-in styles only target raw
|
|
77
|
+
<a>/<button> children, so custom-element items need this themselves. */
|
|
78
|
+
::slotted(:not([slot="trigger"])) {
|
|
79
|
+
display: block;
|
|
80
|
+
width: 100%;
|
|
81
|
+
}
|
|
82
|
+
`
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
customElements.define('k-md-menu', MarkdownMenu);
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import MarkdownEditorControl from './MarkdownEditorControl.js';
|
|
2
|
+
import { html } from '../../lit-all.min.js';
|
|
3
|
+
import '../Icon.js';
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
Numbered list control. Prefixes each non-empty selected line with `N. `
|
|
7
|
+
using sequential numbers. Markdown renderers accept all-1. as well, but
|
|
8
|
+
numbering them properly keeps the source readable.
|
|
9
|
+
*/
|
|
10
|
+
export default class MarkdownNumberedList extends MarkdownEditorControl {
|
|
11
|
+
constructor() {
|
|
12
|
+
super();
|
|
13
|
+
this.label = 'Numbered list';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
command() {
|
|
17
|
+
const editor = this.editor;
|
|
18
|
+
if(!editor) return;
|
|
19
|
+
const ta = editor.textarea;
|
|
20
|
+
if(!ta) return;
|
|
21
|
+
if(editor.mode !== 'write') editor.mode = 'write';
|
|
22
|
+
editor.updateComplete.then(() => {
|
|
23
|
+
ta.focus();
|
|
24
|
+
const { selectionStart: start, selectionEnd: end, value } = ta;
|
|
25
|
+
const lineStart = value.lastIndexOf('\n', start - 1) + 1;
|
|
26
|
+
const lineEndIdx = value.indexOf('\n', end);
|
|
27
|
+
const lineEnd = lineEndIdx === -1 ? value.length : lineEndIdx;
|
|
28
|
+
const block = value.substring(lineStart, lineEnd);
|
|
29
|
+
const lines = block.split('\n');
|
|
30
|
+
const numbered = /^\d+\. /;
|
|
31
|
+
const nonEmpty = lines.filter(l => l.length > 0);
|
|
32
|
+
const allNumbered = nonEmpty.length > 0 && nonEmpty.every(l => numbered.test(l));
|
|
33
|
+
let processed;
|
|
34
|
+
if(allNumbered){
|
|
35
|
+
processed = lines.map(l => l.replace(numbered, ''));
|
|
36
|
+
} else {
|
|
37
|
+
let n = 1;
|
|
38
|
+
processed = lines.map(l => {
|
|
39
|
+
if(!l) return l;
|
|
40
|
+
const stripped = l.replace(numbered, '');
|
|
41
|
+
return `${n++}. ${stripped}`;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
const nextBlock = processed.join('\n');
|
|
45
|
+
const next = value.substring(0, lineStart) + nextBlock + value.substring(lineEnd);
|
|
46
|
+
ta.value = next;
|
|
47
|
+
ta.selectionStart = lineStart;
|
|
48
|
+
ta.selectionEnd = lineStart + nextBlock.length;
|
|
49
|
+
editor.value = next;
|
|
50
|
+
ta.dispatchEvent(new Event('input', { bubbles: true }));
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
render() {
|
|
55
|
+
return html`
|
|
56
|
+
<button
|
|
57
|
+
type="button"
|
|
58
|
+
class=${this.btnClass}
|
|
59
|
+
title=${this.label}
|
|
60
|
+
aria-label=${this.label}
|
|
61
|
+
@click=${this.handleClick}
|
|
62
|
+
>
|
|
63
|
+
<k-icon name="format_list_numbered"></k-icon>
|
|
64
|
+
</button>
|
|
65
|
+
`;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
customElements.define('k-md-numbered-list', MarkdownNumberedList);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import MarkdownEditorControl from './MarkdownEditorControl.js';
|
|
2
|
+
import { html } from '../../lit-all.min.js';
|
|
3
|
+
import '../Icon.js';
|
|
4
|
+
|
|
5
|
+
export default class MarkdownQuote extends MarkdownEditorControl {
|
|
6
|
+
constructor() {
|
|
7
|
+
super();
|
|
8
|
+
this.label = 'Quote';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
command() {
|
|
12
|
+
this.insertLinePrefix('> ');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
render() {
|
|
16
|
+
return html`
|
|
17
|
+
<button
|
|
18
|
+
type="button"
|
|
19
|
+
class=${this.btnClass}
|
|
20
|
+
title=${this.label}
|
|
21
|
+
aria-label=${this.label}
|
|
22
|
+
@click=${this.handleClick}
|
|
23
|
+
>
|
|
24
|
+
<k-icon name="format_quote"></k-icon>
|
|
25
|
+
</button>
|
|
26
|
+
`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
customElements.define('k-md-quote', MarkdownQuote);
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import MarkdownEditorControl from './MarkdownEditorControl.js';
|
|
2
|
+
import { html, css } from '../../lit-all.min.js';
|
|
3
|
+
import '../SpeechToText.js';
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
Speech-to-text control. Reuses the standalone `<k-speech-to-text>`
|
|
7
|
+
component (which provides its own mic-button UI and handles the Web
|
|
8
|
+
Speech API) and just listens for its `end` event to drop the final
|
|
9
|
+
transcript into the editor at the cursor.
|
|
10
|
+
*/
|
|
11
|
+
export default class MarkdownSpeechToText extends MarkdownEditorControl {
|
|
12
|
+
static properties = {
|
|
13
|
+
...MarkdownEditorControl.properties,
|
|
14
|
+
language: { type: String, reflect: true },
|
|
15
|
+
continuous: { type: Boolean, reflect: true }
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
constructor() {
|
|
19
|
+
super();
|
|
20
|
+
this.label = 'Speech to text';
|
|
21
|
+
this.language = 'en-US';
|
|
22
|
+
this.continuous = false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/*
|
|
26
|
+
The trigger button lives inside the embedded `<k-speech-to-text>`,
|
|
27
|
+
which handles its own click-to-listen logic. Our base-class
|
|
28
|
+
handleClick (which calls command()) is unused; the speech component
|
|
29
|
+
drives the lifecycle and we just react to the `end` event.
|
|
30
|
+
*/
|
|
31
|
+
command() {}
|
|
32
|
+
|
|
33
|
+
handleEnd = (e) => {
|
|
34
|
+
const text = (e.detail?.text || '').trim();
|
|
35
|
+
if(!text) return;
|
|
36
|
+
// Trailing space so successive recordings don't run together.
|
|
37
|
+
this.editor?.replaceSelection(text + ' ', { selectInserted: false });
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
render() {
|
|
41
|
+
return html`
|
|
42
|
+
<k-speech-to-text
|
|
43
|
+
language=${this.language}
|
|
44
|
+
?continuous=${this.continuous}
|
|
45
|
+
@end=${this.handleEnd}
|
|
46
|
+
></k-speech-to-text>
|
|
47
|
+
`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static styles = [
|
|
51
|
+
MarkdownEditorControl.styles,
|
|
52
|
+
css`
|
|
53
|
+
:host {
|
|
54
|
+
margin: var(--spacer_q);
|
|
55
|
+
}
|
|
56
|
+
k-speech-to-text {
|
|
57
|
+
/* Shrink the default 2.5rem circular mic button so it sits
|
|
58
|
+
comfortably alongside other toolbar controls. */
|
|
59
|
+
--btn_size: 2rem;
|
|
60
|
+
}
|
|
61
|
+
`
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
customElements.define('k-md-speech-to-text', MarkdownSpeechToText);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import MarkdownEditorControl from './MarkdownEditorControl.js';
|
|
2
|
+
import { html } from '../../lit-all.min.js';
|
|
3
|
+
import '../Icon.js';
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
GFM strikethrough — wraps the selection in `~~...~~`. marked renders this
|
|
7
|
+
to `<del>` (allowed by sanitizeHtml's default tag set), so it survives
|
|
8
|
+
the sanitize pass.
|
|
9
|
+
*/
|
|
10
|
+
export default class MarkdownStrikethrough extends MarkdownEditorControl {
|
|
11
|
+
constructor() {
|
|
12
|
+
super();
|
|
13
|
+
this.label = 'Strikethrough';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
command() {
|
|
17
|
+
this.wrapSelection('~~', '~~', 'strikethrough');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
render() {
|
|
21
|
+
return html`
|
|
22
|
+
<button
|
|
23
|
+
type="button"
|
|
24
|
+
class=${this.btnClass}
|
|
25
|
+
title=${this.label}
|
|
26
|
+
aria-label=${this.label}
|
|
27
|
+
@click=${this.handleClick}
|
|
28
|
+
>
|
|
29
|
+
<k-icon name="format_strikethrough"></k-icon>
|
|
30
|
+
</button>
|
|
31
|
+
`;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
customElements.define('k-md-strikethrough', MarkdownStrikethrough);
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import MarkdownEditorControl from './MarkdownEditorControl.js';
|
|
2
|
+
import { html, css } from '../../lit-all.min.js';
|
|
3
|
+
import '../Icon.js';
|
|
4
|
+
import '../Dropdown.js';
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
Table control. Opens a small dropdown with two number inputs (cols × rows)
|
|
8
|
+
and a Create button, then inserts a GFM markdown table with that many
|
|
9
|
+
empty cells at the cursor.
|
|
10
|
+
*/
|
|
11
|
+
export default class MarkdownTable extends MarkdownEditorControl {
|
|
12
|
+
constructor() {
|
|
13
|
+
super();
|
|
14
|
+
this.label = 'Table';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/*
|
|
18
|
+
The trigger is wired to Dropdown's own click handling, so the
|
|
19
|
+
base-class handleClick (which calls command()) would fire on top and
|
|
20
|
+
create a feedback loop. command() is a no-op here; the Insert button
|
|
21
|
+
inside the dropdown drives insertion.
|
|
22
|
+
*/
|
|
23
|
+
command() {}
|
|
24
|
+
|
|
25
|
+
handleDropdownOpened = () => {
|
|
26
|
+
requestAnimationFrame(() => {
|
|
27
|
+
const cols = this.shadowRoot?.querySelector('.tbl-cols');
|
|
28
|
+
if(cols){ cols.value = '3'; cols.select(); }
|
|
29
|
+
const rows = this.shadowRoot?.querySelector('.tbl-rows');
|
|
30
|
+
if(rows) rows.value = '2';
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
handleFormKeydown = (e) => {
|
|
35
|
+
if(e.key === 'Enter'){
|
|
36
|
+
e.preventDefault();
|
|
37
|
+
this.#submit();
|
|
38
|
+
} else if(e.key === 'Escape'){
|
|
39
|
+
this.shadowRoot?.querySelector('k-dropdown')?.close();
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
handleInsertClick = () => this.#submit();
|
|
44
|
+
|
|
45
|
+
#submit() {
|
|
46
|
+
const cols = this.#clamp(this.shadowRoot?.querySelector('.tbl-cols')?.value, 1, 20);
|
|
47
|
+
const rows = this.#clamp(this.shadowRoot?.querySelector('.tbl-rows')?.value, 1, 50);
|
|
48
|
+
if(!cols || !rows) return;
|
|
49
|
+
const editor = this.editor;
|
|
50
|
+
if(!editor) return;
|
|
51
|
+
editor.replaceSelection(this.#buildTable(cols, rows), { selectInserted: false });
|
|
52
|
+
this.shadowRoot?.querySelector('k-dropdown')?.close();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#clamp(raw, min, max) {
|
|
56
|
+
const n = parseInt(raw, 10);
|
|
57
|
+
if(!Number.isFinite(n)) return 0;
|
|
58
|
+
return Math.max(min, Math.min(max, n));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
#buildTable(cols, rows) {
|
|
62
|
+
const headerCells = Array.from({ length: cols }, (_, i) => ` Header ${i + 1} `).join('|');
|
|
63
|
+
const separatorCells = Array.from({ length: cols }, () => ' --- ').join('|');
|
|
64
|
+
const bodyCells = Array.from({ length: cols }, () => ' ').join('|');
|
|
65
|
+
const lines = [
|
|
66
|
+
`|${headerCells}|`,
|
|
67
|
+
`|${separatorCells}|`,
|
|
68
|
+
...Array.from({ length: rows }, () => `|${bodyCells}|`)
|
|
69
|
+
];
|
|
70
|
+
// Surround with newlines so the table starts on its own line and the
|
|
71
|
+
// user's cursor lands on a fresh line below it.
|
|
72
|
+
return `\n${lines.join('\n')}\n`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
render() {
|
|
76
|
+
return html`
|
|
77
|
+
<k-dropdown @opened=${this.handleDropdownOpened}>
|
|
78
|
+
<button
|
|
79
|
+
slot="trigger"
|
|
80
|
+
type="button"
|
|
81
|
+
class=${this.btnClass}
|
|
82
|
+
title=${this.label}
|
|
83
|
+
aria-label=${this.label}
|
|
84
|
+
>
|
|
85
|
+
<k-icon name="table"></k-icon>
|
|
86
|
+
</button>
|
|
87
|
+
<div class="tbl-form" @keydown=${this.handleFormKeydown}>
|
|
88
|
+
<input
|
|
89
|
+
class="tbl-cols"
|
|
90
|
+
type="number"
|
|
91
|
+
min="1"
|
|
92
|
+
max="20"
|
|
93
|
+
inputmode="numeric"
|
|
94
|
+
aria-label="Columns"
|
|
95
|
+
value="3"
|
|
96
|
+
/>
|
|
97
|
+
<span class="tbl-x" aria-hidden="true">×</span>
|
|
98
|
+
<input
|
|
99
|
+
class="tbl-rows"
|
|
100
|
+
type="number"
|
|
101
|
+
min="1"
|
|
102
|
+
max="50"
|
|
103
|
+
inputmode="numeric"
|
|
104
|
+
aria-label="Rows"
|
|
105
|
+
value="2"
|
|
106
|
+
/>
|
|
107
|
+
<button type="button" class="primary" @click=${this.handleInsertClick}>Insert</button>
|
|
108
|
+
</div>
|
|
109
|
+
</k-dropdown>
|
|
110
|
+
`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
static styles = [
|
|
114
|
+
MarkdownEditorControl.styles,
|
|
115
|
+
css`
|
|
116
|
+
.tbl-form {
|
|
117
|
+
display: flex;
|
|
118
|
+
align-items: center;
|
|
119
|
+
gap: 0.4rem;
|
|
120
|
+
padding: 0.5rem;
|
|
121
|
+
}
|
|
122
|
+
.tbl-form input {
|
|
123
|
+
width: 2.5rem;
|
|
124
|
+
padding: 0.35rem 0.4rem;
|
|
125
|
+
border: 1px solid var(--c_border);
|
|
126
|
+
border-radius: var(--radius);
|
|
127
|
+
font: inherit;
|
|
128
|
+
background: var(--c_bg);
|
|
129
|
+
color: var(--tc);
|
|
130
|
+
text-align: center;
|
|
131
|
+
/* Hide the up/down spinner buttons that take up extra width. */
|
|
132
|
+
-moz-appearance: textfield;
|
|
133
|
+
}
|
|
134
|
+
.tbl-form input::-webkit-inner-spin-button,
|
|
135
|
+
.tbl-form input::-webkit-outer-spin-button {
|
|
136
|
+
-webkit-appearance: none;
|
|
137
|
+
margin: 0;
|
|
138
|
+
}
|
|
139
|
+
.tbl-form input:focus-visible {
|
|
140
|
+
outline: none;
|
|
141
|
+
box-shadow: var(--focus_shadow);
|
|
142
|
+
}
|
|
143
|
+
.tbl-x {
|
|
144
|
+
color: var(--tc_muted);
|
|
145
|
+
font-weight: bold;
|
|
146
|
+
}
|
|
147
|
+
.tbl-form button.primary {
|
|
148
|
+
padding: 0.4rem 0.8rem;
|
|
149
|
+
border: 1px solid var(--c_primary);
|
|
150
|
+
border-radius: var(--radius);
|
|
151
|
+
background: var(--c_primary);
|
|
152
|
+
color: white;
|
|
153
|
+
cursor: pointer;
|
|
154
|
+
font: inherit;
|
|
155
|
+
}
|
|
156
|
+
.tbl-form button.primary:focus-visible {
|
|
157
|
+
outline: none;
|
|
158
|
+
box-shadow: var(--focus_shadow);
|
|
159
|
+
}
|
|
160
|
+
`
|
|
161
|
+
];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
customElements.define('k-md-table', MarkdownTable);
|