@kubex/zinc 1.0.4 → 1.0.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/custom-elements.json +1278 -191
- package/dist/vscode.html-custom-data.json +93 -16
- package/dist/web-types.json +235 -25
- package/dist/zn.d.ts +1313 -941
- package/dist/zn.min.css +1 -1
- package/dist/zn.min.js +2049 -931
- 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/bulk-actions/bulk-actions.component.ts +152 -84
- package/src/components/bulk-actions/bulk-actions.scss +2 -0
- 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/chart/chart.component.ts +14 -12
- 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/cols/cols.component.ts +7 -3
- package/src/components/cols/cols.scss +22 -14
- package/src/components/confirm/confirm.component.ts +1 -2
- package/src/components/content-block/content-block.component.ts +104 -14
- 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 +394 -430
- 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 +52 -6
- package/src/components/inline-edit/inline-edit.scss +15 -2
- package/src/components/input/input.scss +1 -1
- package/src/components/item/item.component.ts +30 -19
- package/src/components/item/item.scss +57 -23
- 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 +99 -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 +25 -1
- 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
|
@@ -1,29 +1,30 @@
|
|
|
1
1
|
import {type CSSResultGroup, html, type PropertyValues, unsafeCSS} from 'lit';
|
|
2
|
+
import {deepQuerySelectorAll} from "../../utilities/query";
|
|
2
3
|
import {FormControlController} from '../../internal/form';
|
|
4
|
+
import {on} from "../../utilities/on";
|
|
3
5
|
import {property, query} from 'lit/decorators.js';
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
6
|
+
import {trim} from "lodash";
|
|
7
|
+
import Attachment from "./modules/attachment/attachment";
|
|
8
|
+
import ContextMenu from "./modules/context-menu/context-menu";
|
|
9
|
+
import DatePicker from "./modules/date-picker/date-picker";
|
|
10
|
+
import Dialog from "./modules/dialog/dialog";
|
|
11
|
+
import DragAndDrop from "./modules/drag-drop/drag-drop";
|
|
12
|
+
import Emoji from "./modules/emoji/emoji";
|
|
13
|
+
import HeadlessEmoji from "./modules/emoji/headless/headless-emoji";
|
|
14
|
+
import ImageResize from "./modules/image-resize/image-resize";
|
|
15
|
+
import Quill from "quill";
|
|
16
|
+
import QuillAI from "./modules/ai";
|
|
17
|
+
import TimeTracking from "./modules/time-tracking/time-tracking";
|
|
18
|
+
import Toolbar from "./modules/toolbar/toolbar";
|
|
11
19
|
import ZincElement from '../../internal/zinc-element';
|
|
20
|
+
import ZnTextarea from "../textarea";
|
|
21
|
+
import type {OnEvent} from "../../utilities/on";
|
|
22
|
+
import type {Range} from "quill";
|
|
12
23
|
import type {ZincFormControl} from '../../internal/zinc-element';
|
|
13
|
-
import type
|
|
14
|
-
import type DialogModuleComponent from "./modules/dialog-module/dialog-module.component";
|
|
15
|
-
import type MenuModuleComponent from "./modules/menu-module/menu-module.component";
|
|
24
|
+
import type ToolbarComponent from "./modules/toolbar/toolbar.component";
|
|
16
25
|
|
|
17
26
|
import styles from './editor.scss';
|
|
18
27
|
|
|
19
|
-
export interface CannedResponse {
|
|
20
|
-
title: string;
|
|
21
|
-
content: string;
|
|
22
|
-
command: string;
|
|
23
|
-
labels?: string[];
|
|
24
|
-
count: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
28
|
/**
|
|
28
29
|
* @summary Short summary of the component's intended use.
|
|
29
30
|
* @documentation https://zinc.style/components/editor
|
|
@@ -52,21 +53,24 @@ export default class ZnEditor extends ZincElement implements ZincFormControl {
|
|
|
52
53
|
@query('#editorHtml')
|
|
53
54
|
private editorHtml: HTMLTextAreaElement;
|
|
54
55
|
|
|
56
|
+
@query('#toolbar')
|
|
57
|
+
private toolbar: ToolbarComponent;
|
|
58
|
+
|
|
59
|
+
@property({reflect: true}) id: string;
|
|
55
60
|
@property() name: string;
|
|
56
61
|
@property() value: string;
|
|
57
62
|
|
|
58
63
|
@property({attribute: 'interaction-type', type: String})
|
|
59
64
|
interactionType: 'ticket' | 'chat' = 'chat';
|
|
60
65
|
|
|
61
|
-
@property({attribute: 'canned-responses', type: Array})
|
|
62
|
-
cannedResponses: any[];
|
|
63
|
-
|
|
64
|
-
@property({attribute: 'canned-responses-url'}) cannedResponsesUri: string;
|
|
65
|
-
|
|
66
66
|
@property({attribute: 'attachment-url', type: String})
|
|
67
67
|
uploadAttachmentUrl: string;
|
|
68
68
|
|
|
69
|
+
@property({attribute: 'ai-path'}) aiPath: string = '';
|
|
70
|
+
|
|
69
71
|
private quillElement: Quill;
|
|
72
|
+
private _content: string = '';
|
|
73
|
+
private _selectionRange: Range = {index: 0, length: 0};
|
|
70
74
|
|
|
71
75
|
get validity(): ValidityState {
|
|
72
76
|
return this.editorHtml.validity;
|
|
@@ -98,78 +102,79 @@ export default class ZnEditor extends ZincElement implements ZincFormControl {
|
|
|
98
102
|
|
|
99
103
|
const bindings = this._getQuillKeyboardBindings();
|
|
100
104
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
let checkId = this.id
|
|
106
|
+
if (!checkId) {
|
|
107
|
+
checkId = "abc"
|
|
108
|
+
}
|
|
109
|
+
on(document, 'click', `[editor-id="` + checkId + `"]`, (e: OnEvent) => {
|
|
110
|
+
this._handleEditorChange(e);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
Quill.register({'modules/toolbar': Toolbar}, true);
|
|
114
|
+
Quill.register({'modules/datePicker': DatePicker}, true);
|
|
115
|
+
Quill.register({'modules/emoji': Emoji}, true);
|
|
116
|
+
Quill.register({'modules/headlessEmoji': HeadlessEmoji}, true);
|
|
117
|
+
Quill.register({'modules/attachment': Attachment}, true);
|
|
118
|
+
Quill.register({'modules/timeTracking': TimeTracking}, true);
|
|
119
|
+
Quill.register({'modules/dragAndDrop': DragAndDrop}, true);
|
|
120
|
+
Quill.register({'modules/imageResize': ImageResize}, true);
|
|
121
|
+
Quill.register({'modules/contextMenu': ContextMenu}, true);
|
|
122
|
+
Quill.register({'modules/dialog': Dialog}, true);
|
|
123
|
+
|
|
124
|
+
/* AI Modules */
|
|
125
|
+
Quill.register('modules/ai', QuillAI as any, true);
|
|
126
|
+
|
|
127
|
+
// Register a custom HR blot so we can insert an inline horizontal rule block
|
|
128
|
+
const BlockEmbed = Quill.import('blots/block/embed') as { new(...args: any[]): any };
|
|
129
|
+
|
|
130
|
+
class HrBlot extends BlockEmbed {
|
|
131
|
+
static blotName = 'hr';
|
|
132
|
+
static tagName = 'HR';
|
|
133
|
+
static className = 'ql-hr';
|
|
134
|
+
|
|
135
|
+
static create(): HTMLElement {
|
|
136
|
+
const node = document.createElement('hr');
|
|
137
|
+
node.classList.add('ql-hr');
|
|
138
|
+
return node;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
Quill.register({'formats/hr': HrBlot}, true);
|
|
107
143
|
|
|
108
|
-
this._updateIcons();
|
|
109
144
|
const attachmentInput = this.getForm()?.querySelector('input[name="attachments"]');
|
|
110
145
|
const startTimeInput = this.getForm()?.querySelector('input[name="startTime"]');
|
|
111
146
|
const openTimeInput = this.getForm()?.querySelector('input[name="openTime"]');
|
|
112
147
|
|
|
113
|
-
const container = [
|
|
114
|
-
['bold', 'italic', 'underline', 'strike'],
|
|
115
|
-
['undo', 'redo'],
|
|
116
|
-
[{'list': 'ordered'}, {'list': 'bullet'}],
|
|
117
|
-
];
|
|
118
|
-
container.push(this.interactionType === 'ticket' ? ['link', 'image', 'attachment'] : ['link', 'image', 'video']);
|
|
119
|
-
container.push(['remove-formatting']);
|
|
120
|
-
|
|
121
148
|
const quill = new Quill(this.editor, {
|
|
122
149
|
modules: {
|
|
123
150
|
toolbar: {
|
|
124
|
-
container,
|
|
125
|
-
handlers: {
|
|
126
|
-
'attachment': () => null,
|
|
127
|
-
'placeholder': function (value: string) {
|
|
128
|
-
if (value) {
|
|
129
|
-
const editor: Quill | null = this.quill;
|
|
130
|
-
if (editor) {
|
|
131
|
-
const cursorPosition = editor.getSelection()?.index;
|
|
132
|
-
if (cursorPosition) {
|
|
133
|
-
editor.insertText(cursorPosition, value);
|
|
134
|
-
editor.setSelection(new Range(cursorPosition, value.length));
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
},
|
|
139
|
-
'redo': () => this.quillElement.history.redo(),
|
|
140
|
-
'undo': () => this.quillElement.history.undo(),
|
|
141
|
-
'remove-formatting': () => {
|
|
142
|
-
const range = this.quillElement.getSelection();
|
|
143
|
-
if (range) {
|
|
144
|
-
this.quillElement.formatText(range.index, range.length, 'bold', false);
|
|
145
|
-
this.quillElement.formatText(range.index, range.length, 'italic', false);
|
|
146
|
-
this.quillElement.formatText(range.index, range.length, 'underline', false);
|
|
147
|
-
this.quillElement.formatText(range.index, range.length, 'strike', false);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
}
|
|
151
|
+
container: this.toolbar,
|
|
152
152
|
},
|
|
153
|
+
contextMenu: {},
|
|
153
154
|
keyboard: {
|
|
154
155
|
bindings: bindings
|
|
155
156
|
},
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
cannedResponses: this.cannedResponses,
|
|
159
|
-
cannedResponsesUri: this.cannedResponsesUri
|
|
157
|
+
dialog: {
|
|
158
|
+
editorId: this.id
|
|
160
159
|
},
|
|
161
|
-
|
|
160
|
+
emoji: {},
|
|
161
|
+
headlessEmoji: {},
|
|
162
|
+
datePicker: {},
|
|
163
|
+
ai: {
|
|
164
|
+
path: this.aiPath
|
|
165
|
+
},
|
|
166
|
+
timeTracking: {
|
|
162
167
|
startTimeInput: startTimeInput as HTMLInputElement,
|
|
163
168
|
openTimeInput: openTimeInput as HTMLInputElement
|
|
164
169
|
},
|
|
165
|
-
|
|
170
|
+
attachment: {
|
|
166
171
|
attachmentInput: attachmentInput,
|
|
167
172
|
onFileUploaded: () => {
|
|
168
173
|
window.onbeforeunload = () => null;
|
|
169
174
|
},
|
|
170
175
|
upload: (file: File) => {
|
|
171
176
|
window.onbeforeunload = () => 'You have unsaved changes. Are you sure you want to leave?';
|
|
172
|
-
return new Promise((resolve
|
|
177
|
+
return new Promise((resolve) => {
|
|
173
178
|
const fd = new FormData();
|
|
174
179
|
fd.append('filename', file.name);
|
|
175
180
|
fd.append('size', file.size.toString());
|
|
@@ -179,7 +184,11 @@ export default class ZnEditor extends ZincElement implements ZincFormControl {
|
|
|
179
184
|
xhr.open('POST', this.uploadAttachmentUrl, true);
|
|
180
185
|
xhr.onload = () => {
|
|
181
186
|
if (xhr.status === 200) {
|
|
182
|
-
const response = JSON.parse(xhr.responseText)
|
|
187
|
+
const response = JSON.parse(xhr.responseText) as {
|
|
188
|
+
uploadPath: string;
|
|
189
|
+
uploadUrl: string;
|
|
190
|
+
originalFilename: string;
|
|
191
|
+
};
|
|
183
192
|
resolve({path: response.uploadPath, url: response.uploadUrl, filename: response.originalFilename});
|
|
184
193
|
}
|
|
185
194
|
};
|
|
@@ -187,7 +196,8 @@ export default class ZnEditor extends ZincElement implements ZincFormControl {
|
|
|
187
196
|
});
|
|
188
197
|
},
|
|
189
198
|
},
|
|
190
|
-
|
|
199
|
+
imageResize: {},
|
|
200
|
+
history: {}
|
|
191
201
|
},
|
|
192
202
|
placeholder: 'Compose your reply...',
|
|
193
203
|
theme: 'snow',
|
|
@@ -196,8 +206,6 @@ export default class ZnEditor extends ZincElement implements ZincFormControl {
|
|
|
196
206
|
|
|
197
207
|
this.quillElement = quill;
|
|
198
208
|
|
|
199
|
-
this._supplyPlaceholderDialog();
|
|
200
|
-
|
|
201
209
|
// @ts-expect-error getSelection is available it lies.
|
|
202
210
|
const hasShadowRootSelection = !!(document.createElement('div').attachShadow({mode: 'open'}).getSelection);
|
|
203
211
|
// Each browser engine has a different implementation for retrieving the Range
|
|
@@ -209,16 +217,12 @@ export default class ZnEditor extends ZincElement implements ZincFormControl {
|
|
|
209
217
|
return rootNode?.getSelection()?.getRangeAt(0);
|
|
210
218
|
} else {
|
|
211
219
|
const selection = window.getSelection();
|
|
212
|
-
|
|
213
|
-
if (selection.getComposedRanges) {
|
|
220
|
+
if (selection && "getComposedRanges" in selection && typeof selection.getComposedRanges === 'function') {
|
|
214
221
|
// Webkit range retrieval is done with getComposedRanges (see: https://bugs.webkit.org/show_bug.cgi?id=163921)
|
|
215
|
-
|
|
216
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
217
|
-
return selection?.getComposedRanges(rootNode)[0];
|
|
222
|
+
return Array.isArray(selection.getComposedRanges(rootNode)) ? (selection.getComposedRanges(rootNode) as unknown[])[0] : undefined;
|
|
218
223
|
} else {
|
|
219
224
|
// Gecko implements the range API properly in Native Shadow: https://developer.mozilla.org/en-US/docs/Web/API/Selection/getRangeAt
|
|
220
|
-
|
|
221
|
-
return selection.getRangeAt(0);
|
|
225
|
+
return selection?.getRangeAt(0);
|
|
222
226
|
}
|
|
223
227
|
}
|
|
224
228
|
} catch {
|
|
@@ -266,7 +270,7 @@ export default class ZnEditor extends ZincElement implements ZincFormControl {
|
|
|
266
270
|
if (selection === null) return;
|
|
267
271
|
if (startNode !== null) {
|
|
268
272
|
if (!quill.selection.hasFocus()) quill.selection.root.focus();
|
|
269
|
-
const native =
|
|
273
|
+
const native = quill.selection.getNativeRange()?.native;
|
|
270
274
|
// @ts-ignore
|
|
271
275
|
if (native === null || force || startNode !== native?.startContainer || startOffset !== native.startOffset || endNode !== native.endContainer || endOffset !== native.endOffset) {
|
|
272
276
|
if (startNode.tagName === "BR") {
|
|
@@ -292,25 +296,20 @@ export default class ZnEditor extends ZincElement implements ZincFormControl {
|
|
|
292
296
|
}
|
|
293
297
|
}
|
|
294
298
|
|
|
295
|
-
// Listen for 'View All' click from menu module
|
|
296
|
-
document.addEventListener('zn-show-canned-response-dialog', (e: ZnShowCannedResponseDialogEvent) => {
|
|
297
|
-
const dialog: DialogModuleComponent | null = document.querySelector('zn-dialog-module');
|
|
298
|
-
const dialogModule = this.quillElement.getModule('dialogModule') as DialogModule;
|
|
299
|
-
if (dialog && dialogModule && e.detail.commands) {
|
|
300
|
-
dialogModule.setCommands(e.detail.commands);
|
|
301
|
-
dialog.allCommands = e.detail.commands; // Need a ref of original list when searching
|
|
302
|
-
}
|
|
303
|
-
});
|
|
304
|
-
|
|
305
299
|
/**
|
|
306
300
|
* Subscribe to selection change separately, because emitter in Quill doesn't catch this event in Shadow DOM
|
|
307
301
|
**/
|
|
308
|
-
document.addEventListener('selectionchange', () =>
|
|
302
|
+
document.addEventListener('selectionchange', () => {
|
|
303
|
+
this.quillElement.selection.update();
|
|
304
|
+
});
|
|
309
305
|
|
|
310
306
|
document.addEventListener('zn-editor-update', this._handleTextChange.bind(this));
|
|
311
307
|
quill.on('text-change', this._handleTextChange.bind(this));
|
|
312
308
|
|
|
313
|
-
|
|
309
|
+
// Track selection range inside the editor so other modules can access it
|
|
310
|
+
quill.on(Quill.events.SELECTION_CHANGE, (range: Range, oldRange: Range) => {
|
|
311
|
+
this._selectionRange = range ?? oldRange;
|
|
312
|
+
});
|
|
314
313
|
|
|
315
314
|
const delta = quill.clipboard.convert({html: this.value});
|
|
316
315
|
quill.setContents(delta, Quill.sources.SILENT);
|
|
@@ -325,95 +324,134 @@ export default class ZnEditor extends ZincElement implements ZincFormControl {
|
|
|
325
324
|
this.emit('zn-change');
|
|
326
325
|
}
|
|
327
326
|
|
|
328
|
-
private _updateIcons() {
|
|
329
|
-
// @ts-expect-error icons has no type
|
|
330
|
-
const icons: { [key: string]: string } = Quill.import("ui/icons");
|
|
331
|
-
if (icons) {
|
|
332
|
-
icons["undo"] = `<zn-icon src="undo" size="20"></zn-icon>`;
|
|
333
|
-
icons["redo"] = `<zn-icon src="redo" size="20"></zn-icon>`;
|
|
334
|
-
icons["remove-formatting"] = `<zn-icon src="format_clear" size="20"></zn-icon>`;
|
|
335
|
-
|
|
336
|
-
if (this.interactionType === 'ticket') {
|
|
337
|
-
icons["attachment"] = `<zn-icon src="attachment" size="20"></zn-icon>`;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
327
|
private _getQuillKeyboardBindings() {
|
|
343
|
-
const bindings = {
|
|
344
|
-
'remove-formatting': {
|
|
345
|
-
key: 'V',
|
|
346
|
-
shiftKey: true,
|
|
347
|
-
handler: (_range: any, context: any) => {
|
|
348
|
-
const clipboard = context.event.clipboardData;
|
|
349
|
-
const text = clipboard.getData('text/plain');
|
|
350
|
-
const html = clipboard.getData('text/html');
|
|
351
|
-
const delta = this.quillElement.clipboard.convert({html: html, text: text});
|
|
352
|
-
this.quillElement.setContents(delta, 'silent');
|
|
353
|
-
this.quillElement.setSelection(delta.length(), Quill.sources.SILENT);
|
|
354
|
-
}
|
|
355
|
-
},
|
|
356
|
-
};
|
|
357
|
-
|
|
358
328
|
const empty = (value: string) => {
|
|
359
329
|
const match = value.match(/[^<pbr\s>/]/);
|
|
360
330
|
return match === null;
|
|
361
331
|
};
|
|
362
332
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
333
|
+
// Always add an Enter binding to support emoji selection in all interaction types
|
|
334
|
+
return {
|
|
335
|
+
'enter': {
|
|
366
336
|
key: 'Enter',
|
|
367
337
|
shiftKey: false,
|
|
368
338
|
handler: () => {
|
|
369
|
-
const
|
|
370
|
-
const
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
339
|
+
const emoji = this.quillElement.getModule('headlessEmoji') as HeadlessEmoji;
|
|
340
|
+
const menu = this.quillElement.getModule('contextMenu') as ContextMenu;
|
|
341
|
+
const dialog = this.quillElement.getModule('dialog') as Dialog;
|
|
342
|
+
if (emoji.isOpen() || menu.isOpen() || dialog.isOpen()) {
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (this.interactionType === 'chat') {
|
|
374
347
|
const form = this.closest('form');
|
|
375
348
|
if (form && this.value && this.value.trim().length > 0 && !empty(this.value)) {
|
|
376
349
|
this.emit('zn-submit', {detail: {value: this.value, element: this}});
|
|
377
350
|
form.requestSubmit();
|
|
378
351
|
this.quillElement.setText('');
|
|
352
|
+
// prevent default Enter (submit completed)
|
|
353
|
+
return false;
|
|
379
354
|
}
|
|
355
|
+
// If we didn't submit, allow default so Enter inserts newline
|
|
356
|
+
return true;
|
|
380
357
|
}
|
|
358
|
+
|
|
359
|
+
// For non-chat interactions, allow default behavior (newline)
|
|
360
|
+
return true;
|
|
381
361
|
},
|
|
382
|
-
}
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
private _handleEditorChange(e: OnEvent) {
|
|
367
|
+
const target = e.selectedTarget as HTMLElement;
|
|
368
|
+
if (!target.hasAttribute('editor-mode')) return;
|
|
369
|
+
|
|
370
|
+
const editorMode = target.getAttribute('editor-mode');
|
|
371
|
+
if (!editorMode) return;
|
|
372
|
+
|
|
373
|
+
const contentContainer = target.getAttribute('editor-content-id');
|
|
374
|
+
if (!contentContainer) return;
|
|
375
|
+
|
|
376
|
+
// Find which element contains the content to insert
|
|
377
|
+
// Priority: currentTarget > selectedTarget > target > composedPath > querySelector
|
|
378
|
+
const contentElement: Element | null =
|
|
379
|
+
(e.currentTarget instanceof Element && e.currentTarget.closest('#' + contentContainer)) ||
|
|
380
|
+
(e.selectedTarget instanceof Element && e.selectedTarget.closest('#' + contentContainer)) ||
|
|
381
|
+
(e.target instanceof Element && e.target.closest('#' + contentContainer)) ||
|
|
382
|
+
((e.composedPath()[0] as Element)?.closest('#' + contentContainer)) ||
|
|
383
|
+
deepQuerySelectorAll(`#${contentContainer}`, document.documentElement, '')[0];
|
|
384
|
+
if (!contentElement) return;
|
|
385
|
+
|
|
386
|
+
let content = contentElement.textContent;
|
|
387
|
+
if (contentElement instanceof HTMLInputElement || contentElement instanceof ZnTextarea) {
|
|
388
|
+
content = contentElement.value;
|
|
389
|
+
}
|
|
390
|
+
if (!content || content === '') return;
|
|
391
|
+
|
|
392
|
+
this._content = trim(content);
|
|
393
|
+
|
|
394
|
+
if (editorMode === 'replace') {
|
|
395
|
+
this._replaceTextAtSelection();
|
|
396
|
+
} else if (editorMode === 'insert') {
|
|
397
|
+
this._insertTextAtSelection();
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
private _replaceTextAtSelection() {
|
|
402
|
+
const range = this.quillElement.getSelection();
|
|
403
|
+
if (range) {
|
|
404
|
+
if (range.length === 0) {
|
|
405
|
+
this.quillElement.setText(this._content || '');
|
|
406
|
+
this.quillElement.setSelection((this._content?.length || 0), 0);
|
|
407
|
+
} else {
|
|
408
|
+
this.quillElement.deleteText(range.index, range.length);
|
|
409
|
+
this.quillElement.insertText(range.index, this._content || '');
|
|
410
|
+
this.quillElement.setSelection(range.index + (this._content.length || 0), 0);
|
|
411
|
+
}
|
|
412
|
+
} else {
|
|
413
|
+
this.quillElement.setText(this._content || '');
|
|
414
|
+
this.quillElement.setSelection((this._content?.length || 0), 0);
|
|
383
415
|
}
|
|
384
416
|
|
|
385
|
-
|
|
417
|
+
this._closePopups();
|
|
386
418
|
}
|
|
387
419
|
|
|
388
|
-
private
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
420
|
+
private _insertTextAtSelection() {
|
|
421
|
+
const range = this.quillElement.getSelection();
|
|
422
|
+
const index = range ? range.index : this._selectionRange.index;
|
|
423
|
+
this.quillElement.insertText(index, this._content || '');
|
|
424
|
+
this.quillElement.setSelection(index + (this._content.length || 0), 0);
|
|
425
|
+
|
|
426
|
+
this._closePopups();
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
private _closePopups() {
|
|
430
|
+
this._closeAiPanel();
|
|
431
|
+
this._closeDialog();
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
private _closeAiPanel() {
|
|
435
|
+
const aiModule = this.quillElement.getModule('ai') as QuillAI;
|
|
436
|
+
if (aiModule) {
|
|
437
|
+
aiModule.resetComponent();
|
|
438
|
+
}
|
|
394
439
|
}
|
|
395
440
|
|
|
396
|
-
private
|
|
397
|
-
const
|
|
398
|
-
if (
|
|
399
|
-
|
|
400
|
-
toolbar.querySelector('button.ql-italic')?.setAttribute('title', 'Italic');
|
|
401
|
-
toolbar.querySelector('button.ql-underline')?.setAttribute('title', 'Underline');
|
|
402
|
-
toolbar.querySelector('button.ql-strike')?.setAttribute('title', 'Strikethrough');
|
|
403
|
-
toolbar.querySelector('button.ql-undo')?.setAttribute('title', 'Undo');
|
|
404
|
-
toolbar.querySelector('button.ql-redo')?.setAttribute('title', 'Redo');
|
|
405
|
-
toolbar.querySelector('button.ql-redo')?.setAttribute('title', 'Redo');
|
|
406
|
-
toolbar.querySelector('button.ql-list[value="ordered"]')?.setAttribute('title', 'Ordered List');
|
|
407
|
-
toolbar.querySelector('button.ql-list[value="bullet"]')?.setAttribute('title', 'Bullet List');
|
|
408
|
-
toolbar.querySelector('button.ql-link')?.setAttribute('title', 'Link');
|
|
409
|
-
toolbar.querySelector('button.ql-image')?.setAttribute('title', 'Image');
|
|
410
|
-
toolbar.querySelector('button.ql-remove-formatting')?.setAttribute('title', 'Remove Formatting');
|
|
441
|
+
private _closeDialog() {
|
|
442
|
+
const dialogModule = this.quillElement.getModule('dialog') as Dialog;
|
|
443
|
+
if (dialogModule) {
|
|
444
|
+
dialogModule.close();
|
|
411
445
|
}
|
|
412
446
|
}
|
|
413
447
|
|
|
414
448
|
render() {
|
|
415
449
|
return html`
|
|
416
|
-
<
|
|
450
|
+
<zn-toolbar id="toolbar">
|
|
451
|
+
<slot name="tools"></slot>
|
|
452
|
+
</zn-toolbar>
|
|
453
|
+
<slot name="context-items" style="display: none;"></slot>
|
|
454
|
+
<div id="editor" x-editor-id="${this.id}"></div>
|
|
417
455
|
<input type="text" id="editorHtml" name="${this.name}" value="${this.value}" style="display: none;">
|
|
418
456
|
<div id="action-container" class="ql-toolbar ql-snow">
|
|
419
457
|
<slot name="actions"></slot>
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
display: flex;
|
|
18
18
|
flex-direction: column;
|
|
19
|
-
background-color:
|
|
19
|
+
background-color: rgba(var(--zn-panel), var(--zn-panel-opacity));
|
|
20
20
|
margin: 0;
|
|
21
21
|
padding: 0;
|
|
22
22
|
max-height: 100%;
|
|
@@ -33,7 +33,6 @@
|
|
|
33
33
|
|
|
34
34
|
.ql-toolbar {
|
|
35
35
|
border: 0 solid rgb(var(--zn-border-color));
|
|
36
|
-
border-bottom-width: 1px;
|
|
37
36
|
color: rgb(var(--zn-text-heading));
|
|
38
37
|
display: flex;
|
|
39
38
|
flex-wrap: wrap;
|
|
@@ -63,7 +62,7 @@
|
|
|
63
62
|
color: rgb(var(--zn-text));
|
|
64
63
|
background-color: rgba(var(--zn-panel), var(--zn-panel-opacity));
|
|
65
64
|
font-size: 15px;
|
|
66
|
-
padding: var(--zn-spacing-medium);
|
|
65
|
+
padding: 0 var(--zn-spacing-medium);
|
|
67
66
|
|
|
68
67
|
img {
|
|
69
68
|
max-width: 40%;
|
|
@@ -92,7 +91,6 @@
|
|
|
92
91
|
}
|
|
93
92
|
|
|
94
93
|
#action-container.ql-toolbar {
|
|
95
|
-
border-top-width: 1px;
|
|
96
94
|
border-bottom-width: 0;
|
|
97
95
|
}
|
|
98
96
|
|
|
@@ -125,7 +123,29 @@
|
|
|
125
123
|
color: rgb(var(--zn-text))
|
|
126
124
|
}
|
|
127
125
|
|
|
128
|
-
|
|
129
126
|
.ql-code-block {
|
|
130
127
|
font-size: 90%;
|
|
131
128
|
}
|
|
129
|
+
|
|
130
|
+
/* Editor toolbar custom active states */
|
|
131
|
+
:host #toolbar zn-button.ql-active .button {
|
|
132
|
+
background-color: rgba(var(--zn-primary), 0.2);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
:host #toolbar zn-dropdown zn-menu zn-menu-item[checked] .menu-item {
|
|
136
|
+
background-color: rgba(var(--zn-panel-highlight), var(--zn-panel-highlight-opacity));
|
|
137
|
+
color: rgb(var(--zn-primary));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
:host .ql-editor hr,
|
|
141
|
+
:host .ql-editor .ql-hr {
|
|
142
|
+
border: none;
|
|
143
|
+
border-top: 1px solid rgb(var(--zn-border-color));
|
|
144
|
+
height: 0;
|
|
145
|
+
margin: var(--zn-spacing-small) 0;
|
|
146
|
+
opacity: 1;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
#toolbar {
|
|
150
|
+
padding: 4px;
|
|
151
|
+
}
|