@kubex/zinc 1.0.3 → 1.0.5
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/.github/workflows/js_build_and_deploy.yaml +1 -1
- package/dist/custom-elements.json +25150 -0
- package/dist/vscode.html-custom-data.json +2874 -0
- package/dist/web-types.json +6611 -0
- package/dist/zn.d.ts +6664 -0
- package/dist/zn.min.css +1 -0
- package/dist/zn.min.js +4740 -0
- package/docs/_includes/default.njk +1 -0
- package/docs/assets/scripts/docs.js +4 -5
- package/docs/assets/scripts/search.js +0 -9
- package/docs/assets/styles/docs.css +3 -3
- package/docs/data/data-table.json +79 -0
- package/docs/data/products-table.json +36 -0
- package/docs/eleventy.config.cjs +3 -0
- package/docs/pages/components/data-select.md +10 -0
- package/docs/pages/components/data-table.md +84 -45
- package/docs/pages/components/dialog.md +1 -1
- package/docs/pages/components/editor.md +1 -3
- package/docs/pages/components/filter-wrapper.md +20 -0
- package/docs/pages/components/icon.md +2 -0
- package/docs/pages/components/item.md +2 -2
- package/docs/pages/components/menu-item.md +12 -3
- package/docs/pages/components/panel.md +17 -0
- package/docs/pages/components/progress-tile.md +17 -1
- package/docs/pages/components/settings-container.md +137 -0
- package/docs/pages/components/textarea.md +1 -1
- package/package.json +3 -1
- package/scss/_root.scss +26 -0
- package/scss/boot.scss +27 -1
- package/scss/mixins/index.scss +30 -1
- package/scss/shared/_base.scss +1 -1
- package/scss/shared/_button.scss +1 -1
- package/scss/shared/layout.scss +7 -0
- package/scss/themes/_dark.scss +5 -5
- package/scss/themes/_light.scss +6 -5
- package/scss/variables/index.scss +9 -0
- package/src/components/alert/alert.component.ts +1 -1
- package/src/components/alert/alert.scss +18 -4
- package/src/components/button/button.component.ts +6 -2
- package/src/components/button/button.scss +16 -1
- package/src/components/button-menu/button-menu.component.ts +23 -15
- package/src/components/button-menu/button-menu.scss +4 -4
- package/src/components/checkbox/checkbox.component.ts +5 -1
- package/src/components/checkbox/checkbox.scss +1 -1
- package/src/components/chip/chip.component.ts +7 -3
- package/src/components/chip/chip.scss +9 -1
- package/src/components/collapsible/collapsible.component.ts +48 -2
- package/src/components/confirm/confirm.component.ts +1 -2
- package/src/components/content-block/content-block.component.ts +25 -1
- package/src/components/content-block/content-block.scss +8 -0
- package/src/components/data-select/data-select.component.ts +1 -1
- package/src/components/data-table/data-table.component.ts +361 -422
- package/src/components/data-table/data-table.scss +6 -1
- package/src/components/datepicker/datepicker.scss +1 -1
- package/src/components/dialog/dialog.component.ts +52 -33
- package/src/components/dialog/dialog.scss +15 -1
- package/src/components/dropdown/dropdown.component.ts +1 -1
- package/src/components/editor/editor.component.ts +196 -158
- package/src/components/editor/editor.scss +25 -5
- package/src/components/editor/modules/ai/index.ts +281 -0
- package/src/components/editor/modules/ai/panel/ai-panel.component.ts +102 -0
- package/src/components/editor/modules/ai/panel/ai-panel.scss +83 -0
- package/src/components/editor/modules/ai/tooltip/ai-tooltip.component.ts +30 -0
- package/src/components/editor/modules/ai/tooltip/ai-tooltip.scss +28 -0
- package/src/components/editor/modules/{attachment-module.ts → attachment/attachment.ts} +7 -7
- package/src/components/editor/modules/context-menu/context-menu-component.ts +115 -0
- package/src/components/editor/modules/context-menu/context-menu.scss +49 -0
- package/src/components/editor/modules/context-menu/context-menu.ts +296 -0
- package/src/components/editor/modules/context-menu/quick-action/index.ts +12 -0
- package/src/components/editor/modules/context-menu/quick-action/quick-action.component.ts +18 -0
- package/src/components/editor/modules/date-picker/date-picker.ts +77 -0
- package/src/components/editor/modules/dialog/dialog.component.ts +115 -0
- package/src/components/editor/modules/dialog/dialog.scss +43 -0
- package/src/components/editor/modules/dialog/dialog.ts +46 -0
- package/src/components/editor/modules/{drag-drop-module.ts → drag-drop/drag-drop.ts} +6 -6
- package/src/components/editor/modules/emoji/emoji.ts +106 -0
- package/src/components/editor/modules/emoji/headless/headless-emoji.component.ts +122 -0
- package/src/components/editor/modules/emoji/headless/headless-emoji.scss +52 -0
- package/src/components/editor/modules/emoji/headless/headless-emoji.ts +223 -0
- package/src/components/editor/modules/{image-resize-module/image-resize-module.ts → image-resize/image-resize.ts} +5 -5
- package/src/components/editor/modules/{time-tracking-module.ts → time-tracking/time-tracking.ts} +5 -5
- package/src/components/editor/modules/toolbar/tool/index.ts +12 -0
- package/src/components/editor/modules/toolbar/tool/tool.component.ts +30 -0
- package/src/components/editor/modules/toolbar/tool/tool.scss +5 -0
- package/src/components/editor/modules/toolbar/toolbar.component.ts +738 -0
- package/src/components/editor/modules/toolbar/toolbar.scss +98 -0
- package/src/components/editor/modules/toolbar/toolbar.ts +328 -0
- package/src/components/expanding-action/expanding-action.component.ts +2 -2
- package/src/components/expanding-action/expanding-action.scss +2 -2
- package/src/components/filter-wrapper/filter-wrapper.component.ts +91 -0
- package/src/components/filter-wrapper/filter-wrapper.scss +11 -0
- package/src/components/filter-wrapper/filter-wrapper.test.ts +10 -0
- package/src/components/filter-wrapper/index.ts +12 -0
- package/src/components/header/header.component.ts +1 -1
- package/src/components/header/header.scss +8 -1
- package/src/components/hover-container/hover-container.scss +39 -36
- package/src/components/icon/icon.component.ts +11 -5
- package/src/components/icon/icon.scss +15 -2
- package/src/components/inline-edit/inline-edit.component.ts +28 -3
- package/src/components/inline-edit/inline-edit.scss +4 -1
- package/src/components/input/input.scss +1 -1
- package/src/components/item/item.component.ts +30 -19
- package/src/components/item/item.scss +56 -14
- package/src/components/linked-select/linked-select.component.ts +5 -1
- package/src/components/menu/menu.component.ts +1 -0
- package/src/components/menu/menu.scss +1 -1
- package/src/components/menu-item/menu-item.component.ts +22 -4
- package/src/components/menu-item/menu-item.scss +54 -7
- package/src/components/navbar/navbar.component.ts +7 -1
- package/src/components/navbar/navbar.scss +26 -16
- package/src/components/option/option.component.ts +4 -1
- package/src/components/option/option.scss +9 -0
- package/src/components/page-nav/page-nav.component.ts +2 -2
- package/src/components/page-nav/page-nav.scss +4 -4
- package/src/components/panel/panel.component.ts +20 -9
- package/src/components/panel/panel.scss +5 -1
- package/src/components/popup/popup.component.ts +16 -2
- package/src/components/progress-tile/progress-tile.component.ts +10 -4
- package/src/components/progress-tile/progress-tile.scss +7 -4
- package/src/components/query-builder/query-builder.component.ts +1 -1
- package/src/components/radio/radio.scss +1 -1
- package/src/components/scroll-container/scroll-container.component.ts +56 -9
- package/src/components/scroll-container/scroll-container.scss +37 -2
- package/src/components/select/select.component.ts +3 -0
- package/src/components/select/select.scss +1 -1
- package/src/components/settings-container/index.ts +12 -0
- package/src/components/settings-container/settings-container.component.ts +226 -0
- package/src/components/settings-container/settings-container.scss +92 -0
- package/src/components/settings-container/settings-container.test.ts +10 -0
- package/src/components/skeleton/skeleton.scss +5 -4
- package/src/components/sp/sp.component.ts +2 -0
- package/src/components/sp/sp.scss +2 -0
- package/src/components/split-button/split-button.component.ts +24 -18
- package/src/components/split-pane/split-pane.component.ts +5 -5
- package/src/components/stat/stat.component.ts +5 -1
- package/src/components/stat/stat.scss +24 -1
- package/src/components/style/style.component.ts +18 -0
- package/src/components/tabs/tabs.component.ts +12 -1
- package/src/components/textarea/textarea.component.ts +3 -1
- package/src/components/textarea/textarea.scss +21 -2
- package/src/components/vertical-stepper/vertical-stepper.scss +1 -1
- package/src/internal/form.ts +3 -1
- package/src/internal/slot.ts +29 -3
- package/src/utilities/on.ts +7 -8
- package/src/utilities/query.ts +5 -1
- package/src/wc.scss +7 -0
- package/src/zinc.ts +5 -0
- package/src/components/editor/modules/dialog-module/dialog-module.component.ts +0 -289
- package/src/components/editor/modules/dialog-module/dialog-module.scss +0 -115
- package/src/components/editor/modules/dialog-module/dialog-module.ts +0 -99
- package/src/components/editor/modules/events/zn-show-canned-response-dialog.ts +0 -15
- package/src/components/editor/modules/menu-module/menu-module.component.ts +0 -149
- package/src/components/editor/modules/menu-module/menu-module.scss +0 -17
- package/src/components/editor/modules/menu-module/menu-module.ts +0 -203
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import {init, Picker} from 'emoji-mart';
|
|
2
|
+
import data from '@emoji-mart/data';
|
|
3
|
+
import Quill from 'quill';
|
|
4
|
+
import type ToolbarComponent from "../toolbar/toolbar.component";
|
|
5
|
+
|
|
6
|
+
export interface EmojiResult {
|
|
7
|
+
native?: string;
|
|
8
|
+
skins?: { native?: string }[];
|
|
9
|
+
id?: string;
|
|
10
|
+
shortcodes?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
class Emoji {
|
|
14
|
+
private readonly _quill: Quill;
|
|
15
|
+
private _mo: MutationObserver | null = null;
|
|
16
|
+
|
|
17
|
+
constructor(quill: Quill) {
|
|
18
|
+
this._quill = quill;
|
|
19
|
+
try {
|
|
20
|
+
void init({data});
|
|
21
|
+
} catch {
|
|
22
|
+
// no-op if already initialised
|
|
23
|
+
}
|
|
24
|
+
this.initPicker();
|
|
25
|
+
|
|
26
|
+
const host = this.getHostEditor();
|
|
27
|
+
if (host) {
|
|
28
|
+
this._mo = new MutationObserver(() => this.initPicker());
|
|
29
|
+
this._mo.observe(host, {attributes: true, attributeFilter: ['t']});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private getHostEditor(): HTMLElement | null {
|
|
34
|
+
const root = this._quill.container?.getRootNode?.() as ShadowRoot | null;
|
|
35
|
+
return (root && (root as ShadowRoot).host) as HTMLElement | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private async getToolbarEmojiContainer(): Promise<HTMLElement | null> {
|
|
39
|
+
try {
|
|
40
|
+
const root = this._quill.container?.getRootNode?.() as ShadowRoot | null;
|
|
41
|
+
if (!root) return null;
|
|
42
|
+
|
|
43
|
+
const toolbar = root.getElementById?.('toolbar') as ToolbarComponent | null;
|
|
44
|
+
await toolbar?.updateComplete;
|
|
45
|
+
|
|
46
|
+
const shadow = toolbar?.shadowRoot as ShadowRoot | undefined;
|
|
47
|
+
const container = shadow?.querySelector?.('.emoji-picker') as HTMLElement | null;
|
|
48
|
+
return container ?? null;
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private getTheme(): 'light' | 'dark' {
|
|
55
|
+
const host: any = this.getHostEditor();
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
|
|
57
|
+
const t = host?.t ?? host?.getAttribute?.('t');
|
|
58
|
+
return t === 'dark' ? 'dark' : 'light';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public initPicker() {
|
|
62
|
+
this.getToolbarEmojiContainer().then((container) => {
|
|
63
|
+
if (!container) return;
|
|
64
|
+
|
|
65
|
+
container.innerHTML = '';
|
|
66
|
+
|
|
67
|
+
// eslint-disable-next-line no-new
|
|
68
|
+
new Picker({
|
|
69
|
+
parent: container,
|
|
70
|
+
data: data as Record<string, unknown>,
|
|
71
|
+
previewPosition: 'none',
|
|
72
|
+
skinTonePosition: 'none',
|
|
73
|
+
theme: this.getTheme(),
|
|
74
|
+
set: 'native',
|
|
75
|
+
icons: 'solid',
|
|
76
|
+
onEmojiSelect: (emoji: EmojiResult) => this.onEmojiSelect(emoji)
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private onEmojiSelect(emoji: EmojiResult | null) {
|
|
82
|
+
const text: string = (emoji?.native) ?? (emoji?.skins?.[0]?.native) ?? '';
|
|
83
|
+
if (!text || !this._quill) return;
|
|
84
|
+
|
|
85
|
+
const range = this._quill.getSelection(true);
|
|
86
|
+
if (range) {
|
|
87
|
+
this._quill.insertText(range.index, text, Quill.sources.USER);
|
|
88
|
+
this._quill.setSelection(range.index + text.length, 0, Quill.sources.USER);
|
|
89
|
+
} else {
|
|
90
|
+
const index = Math.max(0, this._quill.getLength() - 1);
|
|
91
|
+
this._quill.insertText(index, text, Quill.sources.USER);
|
|
92
|
+
this._quill.setSelection(index + text.length, 0, Quill.sources.USER);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
this.getToolbarEmojiContainer().then((container) => {
|
|
96
|
+
if (!container) return;
|
|
97
|
+
|
|
98
|
+
const root = container.getRootNode() as ShadowRoot;
|
|
99
|
+
const host = root.host as HTMLElement | undefined;
|
|
100
|
+
const dialog = host?.closest?.('zn-dialog') as { hide?: () => void } | null;
|
|
101
|
+
dialog?.hide?.();
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export default Emoji;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import {html, unsafeCSS} from 'lit';
|
|
2
|
+
import {property, state} from 'lit/decorators.js';
|
|
3
|
+
import ZincElement from '../../../../../internal/zinc-element';
|
|
4
|
+
import type {CSSResultGroup, PropertyValues} from 'lit';
|
|
5
|
+
|
|
6
|
+
import styles from './headless-emoji.scss';
|
|
7
|
+
|
|
8
|
+
export interface ResultItem {
|
|
9
|
+
emojiChar: string;
|
|
10
|
+
label: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default class HeadlessEmojiComponent extends ZincElement {
|
|
14
|
+
static styles: CSSResultGroup = unsafeCSS(styles);
|
|
15
|
+
|
|
16
|
+
@property({type: Boolean, reflect: true}) open = false;
|
|
17
|
+
@property({type: String}) query = '';
|
|
18
|
+
@property({type: Array}) results: ResultItem[] = [];
|
|
19
|
+
|
|
20
|
+
@state() private _activeIndex = -1;
|
|
21
|
+
|
|
22
|
+
show() {
|
|
23
|
+
this.open = true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
hide() {
|
|
27
|
+
this.open = false;
|
|
28
|
+
this._activeIndex = -1;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
setPosition(left: number, top: number) {
|
|
32
|
+
this.style.left = `${Math.max(0, left)}px`;
|
|
33
|
+
this.style.top = `${top}px`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
setActiveIndex(index: number) {
|
|
37
|
+
const len = this.results?.length ?? 0;
|
|
38
|
+
if (!len) {
|
|
39
|
+
this._activeIndex = -1;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (index < 0) {
|
|
44
|
+
index = len - 1;
|
|
45
|
+
}
|
|
46
|
+
if (index >= len) {
|
|
47
|
+
index = 0;
|
|
48
|
+
}
|
|
49
|
+
this._activeIndex = index;
|
|
50
|
+
this.requestUpdate();
|
|
51
|
+
|
|
52
|
+
requestAnimationFrame(() => {
|
|
53
|
+
const items = Array.from(this.renderRoot.querySelectorAll<HTMLButtonElement>('[data-emoji-item]'));
|
|
54
|
+
const active = items[this._activeIndex];
|
|
55
|
+
active?.scrollIntoView?.({block: 'nearest'});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
getActiveIndex() {
|
|
60
|
+
return this._activeIndex;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private onMouseEnterItem = (e: MouseEvent) => {
|
|
64
|
+
const target = e.currentTarget as HTMLElement | null;
|
|
65
|
+
if (!target) return;
|
|
66
|
+
|
|
67
|
+
const idx = parseInt(target.dataset.index || '-1', 10);
|
|
68
|
+
if (!Number.isNaN(idx)) {
|
|
69
|
+
this.setActiveIndex(idx);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private onClickItem = (e: MouseEvent) => {
|
|
74
|
+
const target = e.currentTarget as HTMLElement | null;
|
|
75
|
+
if (!target) return;
|
|
76
|
+
|
|
77
|
+
const idx = parseInt(target.dataset.index || '-1', 10);
|
|
78
|
+
if (Number.isNaN(idx)) return;
|
|
79
|
+
|
|
80
|
+
const item = this.results?.[idx];
|
|
81
|
+
if (!item) return;
|
|
82
|
+
|
|
83
|
+
this.dispatchEvent(new CustomEvent('zn-emoji-select', {
|
|
84
|
+
bubbles: true,
|
|
85
|
+
composed: true,
|
|
86
|
+
detail: {emojiChar: item.emojiChar, label: item.label}
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
protected willUpdate(changed: PropertyValues) {
|
|
91
|
+
if (changed.has('results')) {
|
|
92
|
+
// Reset active index when results change
|
|
93
|
+
this._activeIndex = this.results?.length ? 0 : -1;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
render() {
|
|
98
|
+
return html`
|
|
99
|
+
<div class="header">${this.query ? `Emoji: ${this.query}` : 'Emoji'}</div>
|
|
100
|
+
${Array.isArray(this.results) && this.results.length > 0 ? (
|
|
101
|
+
this.results.slice(0, 20).map((res, i) => html`
|
|
102
|
+
<button
|
|
103
|
+
type="button"
|
|
104
|
+
class="item"
|
|
105
|
+
role="option"
|
|
106
|
+
aria-selected="${String(i === this._activeIndex)}"
|
|
107
|
+
data-emoji-item
|
|
108
|
+
data-index="${i}"
|
|
109
|
+
@mouseenter="${this.onMouseEnterItem}"
|
|
110
|
+
@click="${this.onClickItem}"
|
|
111
|
+
>
|
|
112
|
+
<span class="emoji">${res.emojiChar}</span>
|
|
113
|
+
<span class="label">${res.label}</span>
|
|
114
|
+
</button>
|
|
115
|
+
`)
|
|
116
|
+
) : html`
|
|
117
|
+
<div class="empty">${this.query ? 'No results' : 'Type something'}</div>`}
|
|
118
|
+
`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
HeadlessEmojiComponent.define('zn-headless-emoji');
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
position: absolute;
|
|
3
|
+
z-index: var(--zn-z-index-editor-action);
|
|
4
|
+
display: none;
|
|
5
|
+
background: var(--zn-panel-background-color);
|
|
6
|
+
border: 1px solid var(--zn-border-color);
|
|
7
|
+
border-radius: 8px;
|
|
8
|
+
box-shadow: var(--zn-shadow-medium);
|
|
9
|
+
padding: 6px;
|
|
10
|
+
max-height: 220px;
|
|
11
|
+
overflow-y: auto;
|
|
12
|
+
min-width: 180px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
:host([open]) {
|
|
16
|
+
display: block;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.header {
|
|
20
|
+
padding: 4px 6px;
|
|
21
|
+
font-size: var(--zn-font-size-x-small);
|
|
22
|
+
opacity: 0.6;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.item {
|
|
26
|
+
display: flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
gap: 8px;
|
|
29
|
+
padding: 6px 8px;
|
|
30
|
+
width: 100%;
|
|
31
|
+
border: none;
|
|
32
|
+
background: transparent;
|
|
33
|
+
cursor: pointer;
|
|
34
|
+
font: inherit;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.item[aria-selected="true"] {
|
|
38
|
+
background: var(--zn-input-background-color-hover);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.emoji {
|
|
42
|
+
font-size: var(--zn-font-size-medium);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.label {
|
|
46
|
+
opacity: 0.7;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.empty {
|
|
50
|
+
padding: 8px;
|
|
51
|
+
opacity: 0.7;
|
|
52
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import './headless-emoji.component';
|
|
2
|
+
import {html} from 'lit';
|
|
3
|
+
import {init, SearchIndex} from 'emoji-mart';
|
|
4
|
+
import {litToHTML} from '../../../../../utilities/lit-to-html';
|
|
5
|
+
import data from '@emoji-mart/data';
|
|
6
|
+
import Quill from 'quill';
|
|
7
|
+
import type {EmojiResult} from "../emoji";
|
|
8
|
+
import type {ResultItem} from './headless-emoji.component';
|
|
9
|
+
import type HeadlessEmojiComponent from './headless-emoji.component';
|
|
10
|
+
|
|
11
|
+
class HeadlessEmoji {
|
|
12
|
+
private _quill: Quill;
|
|
13
|
+
private _component!: HeadlessEmojiComponent;
|
|
14
|
+
private _startIndex = -1;
|
|
15
|
+
private _keydownHandler = (e: KeyboardEvent) => this.onKeydown(e);
|
|
16
|
+
private _docClickHandler = (e: MouseEvent) => this.onDocumentClick(e);
|
|
17
|
+
|
|
18
|
+
constructor(quill: Quill) {
|
|
19
|
+
this._quill = quill;
|
|
20
|
+
// Initialize emoji-mart index once
|
|
21
|
+
try {
|
|
22
|
+
void init({data});
|
|
23
|
+
} catch {
|
|
24
|
+
// no-op
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
this.initComponent();
|
|
28
|
+
this.attachEvents();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private initComponent() {
|
|
32
|
+
this._component = this.createComponent()!;
|
|
33
|
+
this._quill.container.ownerDocument.body.appendChild(this._component);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private attachEvents() {
|
|
37
|
+
this._quill.on(Quill.events.TEXT_CHANGE, () => this.updateFromEditor());
|
|
38
|
+
this._quill.on(Quill.events.EDITOR_CHANGE, () => this.updateFromEditor());
|
|
39
|
+
this._quill.root.addEventListener('keydown', this._keydownHandler);
|
|
40
|
+
this._component.addEventListener('zn-emoji-select', (e: Event) => this.onEmojiSelect(e as CustomEvent));
|
|
41
|
+
this._quill.on('editor-change', () => this.positionComponent());
|
|
42
|
+
this._quill.focus();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private createComponent() {
|
|
46
|
+
const tpl = html`
|
|
47
|
+
<zn-headless-emoji></zn-headless-emoji>`;
|
|
48
|
+
return litToHTML<HeadlessEmojiComponent>(tpl);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private onDocumentClick(e: MouseEvent) {
|
|
52
|
+
const target = e.composedPath ? e.composedPath()[0] as Node : (e.target as Node);
|
|
53
|
+
if (!target) return;
|
|
54
|
+
|
|
55
|
+
if (!this._component.contains(target) && !this._quill.root.contains(target)) {
|
|
56
|
+
this.hide();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private async updateFromEditor() {
|
|
61
|
+
const info = this.getEmojiQuery();
|
|
62
|
+
if (!info) {
|
|
63
|
+
this.hide();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const {start, emojiQuery} = info;
|
|
68
|
+
this._startIndex = start;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const results = await SearchIndex.search(emojiQuery) as EmojiResult[];
|
|
72
|
+
const mapped: ResultItem[] = (Array.isArray(results) ? results : []).slice(0, 20).map((e) => ({
|
|
73
|
+
emojiChar: (e?.skins?.[0]?.native) || e?.native || '',
|
|
74
|
+
label: (e?.id || e?.shortcodes || '') || ''
|
|
75
|
+
})).filter(it => !!it.emojiChar);
|
|
76
|
+
|
|
77
|
+
this._component.results = mapped;
|
|
78
|
+
this._component.query = emojiQuery;
|
|
79
|
+
if (mapped.length > 0) {
|
|
80
|
+
this.show();
|
|
81
|
+
this.positionComponent();
|
|
82
|
+
} else {
|
|
83
|
+
this.show();
|
|
84
|
+
this.positionComponent();
|
|
85
|
+
}
|
|
86
|
+
} catch {
|
|
87
|
+
this._component.results = [];
|
|
88
|
+
this._component.query = emojiQuery;
|
|
89
|
+
this.show();
|
|
90
|
+
this.positionComponent();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private positionComponent() {
|
|
95
|
+
if (!this._component || !this._component.open) return;
|
|
96
|
+
|
|
97
|
+
const range = this._quill.getSelection();
|
|
98
|
+
if (!range) return;
|
|
99
|
+
|
|
100
|
+
const bounds = this._quill.getBounds(range.index);
|
|
101
|
+
if (!bounds) return;
|
|
102
|
+
|
|
103
|
+
const editorBounds = this._quill.container.getBoundingClientRect();
|
|
104
|
+
const left = editorBounds.left + Math.max(0, bounds.left);
|
|
105
|
+
const top = editorBounds.top + bounds.bottom + 4;
|
|
106
|
+
this._component.setPosition(left, top);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private getEmojiQuery(): { start: number; emojiQuery: string } | null {
|
|
110
|
+
try {
|
|
111
|
+
const sel = this._quill.getSelection();
|
|
112
|
+
if (!sel) return null;
|
|
113
|
+
|
|
114
|
+
const cursor = sel.index;
|
|
115
|
+
const characterLimit = 50;
|
|
116
|
+
const textBefore = this._quill.getText(Math.max(0, cursor - characterLimit), Math.min(characterLimit, cursor));
|
|
117
|
+
const offset = cursor - Math.max(0, cursor - characterLimit);
|
|
118
|
+
const uptoCursor = textBefore.slice(0, offset);
|
|
119
|
+
const cIndex = uptoCursor.lastIndexOf(':');
|
|
120
|
+
if (cIndex === -1) return null;
|
|
121
|
+
|
|
122
|
+
const prev = cIndex > 0 ? uptoCursor[cIndex - 1] : ' ';
|
|
123
|
+
if (prev && /[^\s\n]/.test(prev)) return null; // must start at word boundary
|
|
124
|
+
|
|
125
|
+
const emojiQuery = uptoCursor.substring(cIndex + 1);
|
|
126
|
+
if (/[\s\n]/.test(emojiQuery)) return null; // stop at whitespace
|
|
127
|
+
|
|
128
|
+
// Do not trigger for URLs like http://
|
|
129
|
+
if (/^\/\//.test(uptoCursor.substring(Math.max(0, cIndex - 5), cIndex + 1))) return null;
|
|
130
|
+
return {start: cursor - emojiQuery.length - 1, emojiQuery};
|
|
131
|
+
} catch {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private onKeydown(e: KeyboardEvent) {
|
|
137
|
+
if (!this._component?.open) return;
|
|
138
|
+
|
|
139
|
+
if (e.key === 'Escape') {
|
|
140
|
+
e.preventDefault();
|
|
141
|
+
e.stopPropagation();
|
|
142
|
+
this.hide();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (e.key === 'ArrowDown') {
|
|
147
|
+
e.preventDefault();
|
|
148
|
+
e.stopPropagation();
|
|
149
|
+
const next = (this._component.getActiveIndex?.() ?? -1) + 1;
|
|
150
|
+
this._component.setActiveIndex?.(next);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (e.key === 'ArrowUp') {
|
|
155
|
+
e.preventDefault();
|
|
156
|
+
e.stopPropagation();
|
|
157
|
+
const prev = (this._component.getActiveIndex?.() ?? 0) - 1;
|
|
158
|
+
this._component.setActiveIndex?.(prev);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (e.key === 'Enter') {
|
|
163
|
+
const idx = this._component.getActiveIndex?.();
|
|
164
|
+
const results = this._component.results || [];
|
|
165
|
+
const item = (typeof idx === 'number' && idx >= 0 && idx < results.length) ? results[idx] : undefined;
|
|
166
|
+
if (item) {
|
|
167
|
+
e.preventDefault();
|
|
168
|
+
e.stopPropagation();
|
|
169
|
+
this.replaceAtQuery(item.emojiChar);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private onEmojiSelect(e: CustomEvent) {
|
|
175
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
|
|
176
|
+
const emojiChar: string = e.detail?.emojiChar || '';
|
|
177
|
+
if (!emojiChar) return;
|
|
178
|
+
this.replaceAtQuery(emojiChar);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private replaceAtQuery(emojiChar: string) {
|
|
182
|
+
try {
|
|
183
|
+
const sel = this._quill.getSelection();
|
|
184
|
+
if (!sel || this._startIndex < 0) return;
|
|
185
|
+
|
|
186
|
+
const insertIndex = this._startIndex;
|
|
187
|
+
const length = sel.index - insertIndex;
|
|
188
|
+
if (length < 0) return;
|
|
189
|
+
|
|
190
|
+
// Remove ':' and the query
|
|
191
|
+
this._quill.deleteText(insertIndex, length, Quill.sources.USER);
|
|
192
|
+
|
|
193
|
+
const insertText = `${emojiChar} `;
|
|
194
|
+
this._quill.insertText(insertIndex, insertText, Quill.sources.USER);
|
|
195
|
+
|
|
196
|
+
const cursorPos = insertIndex + insertText.length;
|
|
197
|
+
setTimeout(() => this._quill.setSelection(cursorPos, 0, Quill.sources.SILENT), 0);
|
|
198
|
+
this._quill.focus();
|
|
199
|
+
this.hide();
|
|
200
|
+
} catch {
|
|
201
|
+
// no-op
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private show() {
|
|
206
|
+
if (!this._component.open) {
|
|
207
|
+
this._component.show();
|
|
208
|
+
this._quill.container.ownerDocument.addEventListener('click', this._docClickHandler);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private hide() {
|
|
213
|
+
this._component.hide();
|
|
214
|
+
this._startIndex = -1;
|
|
215
|
+
this._quill.container.ownerDocument.removeEventListener('click', this._docClickHandler);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
public isOpen() {
|
|
219
|
+
return this._component?.open;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export default HeadlessEmoji;
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import Quill, {Module} from "quill";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
interface ImageResizeOptions {
|
|
4
4
|
overlayStyles?: Partial<CSSStyleDeclaration>;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
class
|
|
7
|
+
class ImageResize extends Module<ImageResizeOptions>
|
|
8
8
|
{
|
|
9
9
|
|
|
10
|
-
static DEFAULTS:
|
|
10
|
+
static DEFAULTS: ImageResizeOptions = {};
|
|
11
11
|
|
|
12
12
|
private _focusedImage: HTMLImageElement | null = null;
|
|
13
13
|
private _overlay: HTMLDivElement | null = null;
|
|
14
14
|
|
|
15
|
-
constructor(quill: Quill, options:
|
|
15
|
+
constructor(quill: Quill, options: ImageResizeOptions)
|
|
16
16
|
{
|
|
17
17
|
super(quill, options);
|
|
18
18
|
|
|
@@ -154,4 +154,4 @@ class ImageResizeModule extends Module<ImageResizeModuleOptions>
|
|
|
154
154
|
};
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
export default
|
|
157
|
+
export default ImageResize;
|
package/src/components/editor/modules/{time-tracking-module.ts → time-tracking/time-tracking.ts}
RENAMED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import Quill from 'quill';
|
|
1
|
+
import type Quill from 'quill';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
interface TimeTrackingOptions {
|
|
4
4
|
startTimeInput?: HTMLInputElement;
|
|
5
5
|
openTimeInput?: HTMLInputElement;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
export default class
|
|
8
|
+
export default class TimeTracking {
|
|
9
9
|
private _quill: Quill;
|
|
10
|
-
private _options:
|
|
10
|
+
private _options: TimeTrackingOptions;
|
|
11
11
|
private _startTime: number;
|
|
12
12
|
private _openTime: number;
|
|
13
13
|
|
|
14
|
-
constructor(quill: Quill, options:
|
|
14
|
+
constructor(quill: Quill, options: TimeTrackingOptions) {
|
|
15
15
|
this._quill = quill;
|
|
16
16
|
this._options = options;
|
|
17
17
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import ZnEditorTool from './tool.component';
|
|
2
|
+
|
|
3
|
+
export * from './tool.component';
|
|
4
|
+
export default ZnEditorTool;
|
|
5
|
+
|
|
6
|
+
ZnEditorTool.define('zn-editor-tool');
|
|
7
|
+
|
|
8
|
+
declare global {
|
|
9
|
+
interface HTMLElementTagNameMap {
|
|
10
|
+
'zn-editor-tool': ZnEditorTool;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {type CSSResultGroup, html, unsafeCSS} from "lit";
|
|
2
|
+
import {property} from "lit/decorators.js";
|
|
3
|
+
import ZincElement from "../../../../../internal/zinc-element";
|
|
4
|
+
|
|
5
|
+
import styles from './tool.scss';
|
|
6
|
+
|
|
7
|
+
export default class ZnEditorTool extends ZincElement {
|
|
8
|
+
static styles: CSSResultGroup = unsafeCSS(styles);
|
|
9
|
+
|
|
10
|
+
@property() uri: string;
|
|
11
|
+
@property() label: string;
|
|
12
|
+
@property() key: string;
|
|
13
|
+
@property() icon: string;
|
|
14
|
+
@property() handler: string = 'dialog';
|
|
15
|
+
|
|
16
|
+
render() {
|
|
17
|
+
return html`
|
|
18
|
+
<zn-button class="tool-action"
|
|
19
|
+
color="transparent"
|
|
20
|
+
icon="${this.icon}"
|
|
21
|
+
icon-size="18"
|
|
22
|
+
data-format="${this.handler}"
|
|
23
|
+
data-format-type="${this.uri}"
|
|
24
|
+
data-toolbar-key="${this.key}"
|
|
25
|
+
aria-label="${this.label}"></zn-button>
|
|
26
|
+
<div class="${this.handler}"></div>`
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
ZnEditorTool.define('zn-editor-tool');
|