@kubex/zinc 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/custom-elements.json +464 -34
- package/dist/vscode.html-custom-data.json +38 -6
- package/dist/web-types.json +97 -10
- package/dist/zn.d.ts +73 -8
- package/dist/zn.min.js +393 -349
- package/docs/data/products-table.json +203 -0
- package/docs/pages/components/button.md +25 -0
- package/docs/pages/components/data-table.md +21 -0
- package/docs/pages/components/header.md +7 -0
- package/docs/pages/components/order-table.md +82 -5
- package/package.json +1 -1
- package/src/components/button/button.component.ts +168 -10
- package/src/components/button/button.scss +115 -0
- package/src/components/content-block/content-block.component.ts +186 -107
- package/src/components/content-block/content-block.scss +4 -2
- package/src/components/data-select/data-select.component.ts +50 -10
- package/src/components/data-table/data-table.component.ts +130 -43
- package/src/components/data-table/data-table.scss +85 -7
- package/src/components/data-table-filter/data-table-filter.component.ts +1 -1
- package/src/components/editor/editor.scss +1 -0
- package/src/components/editor/modules/attachment/attachment.ts +53 -41
- package/src/components/editor/modules/toolbar/toolbar.ts +42 -0
- package/src/components/empty-state/empty-state.component.ts +16 -8
- package/src/components/expanding-action/expanding-action.component.ts +1 -1
- package/src/components/header/header.component.ts +8 -2
- package/src/components/header/header.scss +5 -1
- package/src/components/icon/icon.component.ts +2 -0
- package/src/components/icon/icon.scss +13 -0
- package/src/components/item/item.component.ts +11 -3
- package/src/components/item/item.scss +7 -4
- package/src/components/linked-select/linked-select.component.ts +1 -0
- package/src/components/menu/menu.component.ts +0 -1
- package/src/components/navbar/navbar.component.ts +1 -1
- package/src/components/note/note.component.ts +70 -2
- package/src/components/note/note.scss +26 -4
- package/src/components/order-table/order-table.component.ts +49 -28
- package/src/components/panel/panel.component.ts +5 -1
- package/src/components/panel/panel.scss +6 -0
- package/src/components/rating/rating.component.ts +10 -9
- package/src/components/rating/rating.scss +15 -1
|
@@ -2,7 +2,7 @@ import {classMap} from "lit/directives/class-map.js";
|
|
|
2
2
|
import {type CSSResultGroup, html, nothing, type TemplateResult, unsafeCSS} from 'lit';
|
|
3
3
|
import {HasSlotController} from "../../internal/slot";
|
|
4
4
|
import {ifDefined} from "lit/directives/if-defined.js";
|
|
5
|
-
import {property} from 'lit/decorators.js';
|
|
5
|
+
import {property, query} from 'lit/decorators.js';
|
|
6
6
|
import {ref} from "lit/directives/ref.js";
|
|
7
7
|
import {Task} from "@lit/task";
|
|
8
8
|
import {unsafeHTML} from "lit/directives/unsafe-html.js";
|
|
@@ -192,6 +192,12 @@ export default class ZnDataTable extends ZincElement {
|
|
|
192
192
|
|
|
193
193
|
@property({attribute: "no-initial-load", type: Boolean}) noInitialLoad: boolean = false;
|
|
194
194
|
|
|
195
|
+
@property({attribute: 'group-by'}) groupBy = '';
|
|
196
|
+
|
|
197
|
+
@property() groups = '';
|
|
198
|
+
|
|
199
|
+
@query('#select-all-rows') selectAllButton: ZnButton;
|
|
200
|
+
|
|
195
201
|
// Data Table Properties
|
|
196
202
|
private _initialLoad = true;
|
|
197
203
|
private _lastTableContent: TemplateResult = html``;
|
|
@@ -226,6 +232,11 @@ export default class ZnDataTable extends ZincElement {
|
|
|
226
232
|
return {rows: [], page: 1, perPage: this.itemsPerPage, total: 0};
|
|
227
233
|
}
|
|
228
234
|
|
|
235
|
+
if (this.groupBy) {
|
|
236
|
+
// we want to load all the data possible so we can group and show multiple tables
|
|
237
|
+
this.itemsPerPage = 1000;
|
|
238
|
+
}
|
|
239
|
+
|
|
229
240
|
const requestData: DataRequest = {
|
|
230
241
|
page: this.page,
|
|
231
242
|
perPage: this.itemsPerPage,
|
|
@@ -278,8 +289,6 @@ export default class ZnDataTable extends ZincElement {
|
|
|
278
289
|
|
|
279
290
|
requestParams: Record<string, any> = {};
|
|
280
291
|
|
|
281
|
-
builtinRequestParams: Record<string, any> = {};
|
|
282
|
-
|
|
283
292
|
refresh() {
|
|
284
293
|
// Allow manual refresh to trigger the first data load when no-initial-load is set
|
|
285
294
|
this._initialLoad = false;
|
|
@@ -392,13 +401,58 @@ export default class ZnDataTable extends ZincElement {
|
|
|
392
401
|
return this.emptyState();
|
|
393
402
|
}
|
|
394
403
|
|
|
404
|
+
this._rows = this.getRows(data);
|
|
405
|
+
|
|
406
|
+
if (this.groupBy) {
|
|
407
|
+
// we need to group the rows by the group column
|
|
408
|
+
const groupedRows: Record<string, Row[]> = {};
|
|
409
|
+
|
|
410
|
+
// add groups to the current rows
|
|
411
|
+
const toAdd = this.groups.split(',').map(g => g.trim()).filter(g => g.length > 0);
|
|
412
|
+
toAdd.forEach((group) => {
|
|
413
|
+
if (!groupedRows[this.humanize(group)]) {
|
|
414
|
+
groupedRows[this.humanize(group)] = [];
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
this._rows.forEach((row: Row) => {
|
|
419
|
+
const groupCell = row.cells.find((cell: Cell) => cell.column === this.groupBy);
|
|
420
|
+
const groupValue = groupCell ? groupCell.text : 'Ungrouped';
|
|
421
|
+
if (!groupedRows[this.humanize(groupValue)] && (!this.groups || groupValue === 'Ungrouped')) {
|
|
422
|
+
groupedRows[this.humanize(groupValue)] = [];
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (groupedRows[this.humanize(groupValue)]) {
|
|
426
|
+
groupedRows[this.humanize(groupValue)].push(row);
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
// render a table for each group
|
|
431
|
+
return html`
|
|
432
|
+
<zn-sp flush>
|
|
433
|
+
${Object.keys(groupedRows).map((groupKey) => html`
|
|
434
|
+
<div class="table-group">
|
|
435
|
+
<h3 class="table-group__title">${groupKey === "Ungrouped" ? "" : groupKey}</h3>
|
|
436
|
+
${this.renderTableData(groupedRows[groupKey])}
|
|
437
|
+
</div>`
|
|
438
|
+
)}
|
|
439
|
+
</zn-sp>
|
|
440
|
+
`;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
return this.renderTableData(this._rows);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
public humanize(str: string) {
|
|
448
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
public renderTableData(data: any) {
|
|
395
452
|
const filteredHeaders = Object.values(this.headers).filter((header) => {
|
|
396
453
|
return !Object.values(this.hiddenColumns).includes(header.key);
|
|
397
454
|
});
|
|
398
455
|
|
|
399
|
-
this._rows = this.getRows(data);
|
|
400
|
-
|
|
401
|
-
const hasSelectedRows = this.selectedRows.length > 0;
|
|
402
456
|
this.rowHasActions = this._rows.some((row: Row) => row.actions && row.actions.length > 0);
|
|
403
457
|
|
|
404
458
|
// To make sure the cells are in the same order as the headers, we need to map them cell.col to heading.key
|
|
@@ -423,27 +477,18 @@ export default class ZnDataTable extends ZincElement {
|
|
|
423
477
|
<table class="${classMap({
|
|
424
478
|
'table': true,
|
|
425
479
|
'table--standalone': this.standalone,
|
|
426
|
-
'with-hover': !this.unsortable,
|
|
427
|
-
'table--with-checkboxes': !this.hideCheckboxes,
|
|
480
|
+
'with-hover': !this.unsortable && !this.hideCheckboxes,
|
|
428
481
|
})}">
|
|
429
482
|
<thead>
|
|
430
483
|
<tr>
|
|
431
|
-
${this.hideCheckboxes || !hasSelectedRows ? html`` : html`
|
|
432
|
-
<th>
|
|
433
|
-
<div><input type="checkbox" @change="${this.selectAll}"></div>
|
|
434
|
-
</th>`}
|
|
435
484
|
${filteredHeaders.map((header: HeaderConfig) => this.renderCellHeader(header))}
|
|
436
485
|
${this.rowHasActions ? html`
|
|
437
486
|
<th></th>` : html``}
|
|
438
487
|
</tr>
|
|
439
488
|
</thead>
|
|
440
489
|
<tbody>
|
|
441
|
-
${
|
|
442
|
-
<tr>
|
|
443
|
-
${this.hideCheckboxes ? html`` : html`
|
|
444
|
-
<td class="${classMap({'hidden': !hasSelectedRows})}">
|
|
445
|
-
<div><input type="checkbox" @change="${this.selectRow}"></div>
|
|
446
|
-
</td>`}
|
|
490
|
+
${data.map((row: Row) => html`
|
|
491
|
+
<tr class="${classMap({'table__row--selected': this.isRowSelected(row)})}">
|
|
447
492
|
${row.cells.map((value: Cell, index: number) => this.renderCellBody(index, value))}
|
|
448
493
|
${this.rowHasActions ? this.renderActions(row) : null}
|
|
449
494
|
</tr>`)}
|
|
@@ -490,7 +535,7 @@ export default class ZnDataTable extends ZincElement {
|
|
|
490
535
|
}
|
|
491
536
|
|
|
492
537
|
getRowsSelected() {
|
|
493
|
-
if (this.
|
|
538
|
+
if (this.selectedRows.length <= 0) return null;
|
|
494
539
|
|
|
495
540
|
return html`
|
|
496
541
|
<p>${this.numberOfRowsSelected} of ${this._rows.length} rows selected</p>`
|
|
@@ -559,10 +604,37 @@ export default class ZnDataTable extends ZincElement {
|
|
|
559
604
|
getActions() {
|
|
560
605
|
const actions = [];
|
|
561
606
|
|
|
607
|
+
const hasSlots = this.hasSlotController.test(ActionSlots.delete.valueOf())
|
|
608
|
+
|| this.hasSlotController.test(ActionSlots.modify.valueOf())
|
|
609
|
+
|| this.hasSlotController.test(ActionSlots.create.valueOf());
|
|
610
|
+
|
|
611
|
+
if(!hasSlots) {
|
|
612
|
+
return [];
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (!this.hideCheckboxes && this._rows.length > 0) {
|
|
616
|
+
actions.push(html`
|
|
617
|
+
<zn-button @click="${this.selectAll}"
|
|
618
|
+
id="select-all-rows"
|
|
619
|
+
color="transparent"
|
|
620
|
+
size="x-small"
|
|
621
|
+
icon="indeterminate_check_box"
|
|
622
|
+
icon-size="22"
|
|
623
|
+
icon-color="primary"
|
|
624
|
+
tooltip="Select All"
|
|
625
|
+
slot="trigger">
|
|
626
|
+
</zn-button>`);
|
|
627
|
+
}
|
|
628
|
+
|
|
562
629
|
if (this.selectedRows.length > 0) {
|
|
563
630
|
actions.push(html`
|
|
564
|
-
<zn-button @click="${this.clearSelectedRows}"
|
|
565
|
-
|
|
631
|
+
<zn-button @click="${this.clearSelectedRows}"
|
|
632
|
+
color="transparent"
|
|
633
|
+
size="x-small"
|
|
634
|
+
icon="disabled_by_default"
|
|
635
|
+
icon-size="22"
|
|
636
|
+
tooltip="Clear Selection"
|
|
637
|
+
slot="trigger">
|
|
566
638
|
</zn-button>`);
|
|
567
639
|
|
|
568
640
|
if (this.hasSlotController.test(ActionSlots.delete.valueOf())) {
|
|
@@ -620,21 +692,25 @@ export default class ZnDataTable extends ZincElement {
|
|
|
620
692
|
}
|
|
621
693
|
|
|
622
694
|
selectAll(event: Event) {
|
|
623
|
-
const
|
|
624
|
-
|
|
695
|
+
const button = event.target as ZnButton;
|
|
696
|
+
if (button.disabled) return;
|
|
625
697
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
698
|
+
if (this.numberOfRowsSelected === this._rows.length) {
|
|
699
|
+
this.clearSelectedRows(event);
|
|
700
|
+
return;
|
|
629
701
|
}
|
|
630
702
|
|
|
631
|
-
this.selectedRows =
|
|
703
|
+
this.selectedRows = this._rows;
|
|
632
704
|
this.numberOfRowsSelected = this.selectedRows.length;
|
|
633
705
|
this.updateKeys();
|
|
634
706
|
this.requestUpdate();
|
|
635
707
|
}
|
|
636
708
|
|
|
637
709
|
selectRow(e: Event) {
|
|
710
|
+
if (this.hideCheckboxes) {
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
|
|
638
714
|
if (!(e.target && (e.target instanceof Element))) {
|
|
639
715
|
return;
|
|
640
716
|
}
|
|
@@ -655,17 +731,19 @@ export default class ZnDataTable extends ZincElement {
|
|
|
655
731
|
return;
|
|
656
732
|
}
|
|
657
733
|
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
if (!isCheckboxTarget) {
|
|
662
|
-
checkbox.checked = !checkbox.checked;
|
|
663
|
-
}
|
|
664
|
-
}
|
|
734
|
+
const rows = Array.from(this.renderRoot.querySelectorAll('tbody tr'));
|
|
735
|
+
const index = rows.indexOf(parent as HTMLTableRowElement);
|
|
736
|
+
if (index === -1) return;
|
|
665
737
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
738
|
+
const row = this._rows[index] as Row;
|
|
739
|
+
if (!row) return;
|
|
740
|
+
|
|
741
|
+
const alreadySelected = this.selectedRows.some((r: Row) => r.id === row.id);
|
|
742
|
+
if (alreadySelected) {
|
|
743
|
+
this.selectedRows = this.selectedRows.filter((r: Row) => r.id !== row.id);
|
|
744
|
+
} else {
|
|
745
|
+
this.selectedRows = [...(this.selectedRows as Row[]), row];
|
|
746
|
+
}
|
|
669
747
|
|
|
670
748
|
this.numberOfRowsSelected = this.selectedRows.length;
|
|
671
749
|
this.updateKeys();
|
|
@@ -676,13 +754,9 @@ export default class ZnDataTable extends ZincElement {
|
|
|
676
754
|
const button = event.target as ZnButton;
|
|
677
755
|
if (button.disabled) return;
|
|
678
756
|
|
|
679
|
-
(this.renderRoot.querySelectorAll('thead input[type="checkbox"]')[0] as HTMLInputElement).checked = false;
|
|
680
|
-
for (const row of this.renderRoot.querySelectorAll('tbody input[type="checkbox"]')) {
|
|
681
|
-
(row as HTMLInputElement).checked = false;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
757
|
this.selectedRows = [];
|
|
685
758
|
this.numberOfRowsSelected = 0;
|
|
759
|
+
this.updateKeys();
|
|
686
760
|
this.requestUpdate();
|
|
687
761
|
}
|
|
688
762
|
|
|
@@ -842,7 +916,7 @@ export default class ZnDataTable extends ZincElement {
|
|
|
842
916
|
|
|
843
917
|
return html`
|
|
844
918
|
<td
|
|
845
|
-
@click="${this.selectRow}"
|
|
919
|
+
@click="${this.hideCheckboxes ? undefined : this.selectRow}"
|
|
846
920
|
class="${classMap({
|
|
847
921
|
'table__cell': true,
|
|
848
922
|
'table__cell--wide': headerKey === this.wideColumn,
|
|
@@ -852,6 +926,10 @@ export default class ZnDataTable extends ZincElement {
|
|
|
852
926
|
</td>`;
|
|
853
927
|
}
|
|
854
928
|
|
|
929
|
+
private isRowSelected(row: Row): boolean {
|
|
930
|
+
return this.selectedRows.some((r: Row) => r.id === row.id);
|
|
931
|
+
}
|
|
932
|
+
|
|
855
933
|
private getRows(data: Response): Row[] {
|
|
856
934
|
const sourceRows = data.rows;
|
|
857
935
|
|
|
@@ -894,10 +972,19 @@ export default class ZnDataTable extends ZincElement {
|
|
|
894
972
|
}
|
|
895
973
|
|
|
896
974
|
private updateKeys() {
|
|
975
|
+
this.updateSelectAll();
|
|
897
976
|
this.updateModifyKeys();
|
|
898
977
|
this.updateDeleteKeys();
|
|
899
978
|
}
|
|
900
979
|
|
|
980
|
+
private updateSelectAll() {
|
|
981
|
+
if (this.numberOfRowsSelected === this._rows.length) {
|
|
982
|
+
this.selectAllButton.icon = 'check_box';
|
|
983
|
+
} else {
|
|
984
|
+
this.selectAllButton.icon = 'indeterminate_check_box';
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
901
988
|
private updateModifyKeys() {
|
|
902
989
|
this.updateActionKeys('modify-action');
|
|
903
990
|
}
|
|
@@ -21,7 +21,6 @@ table {
|
|
|
21
21
|
&.table--standalone {
|
|
22
22
|
background-color: rgb(var(--zn-panel));
|
|
23
23
|
border-radius: var(--zn-border-radius-large);
|
|
24
|
-
box-shadow: var(--zn-shadow-medium);
|
|
25
24
|
border: 1px solid rgb(var(--zn-border-color));
|
|
26
25
|
overflow: hidden;
|
|
27
26
|
}
|
|
@@ -35,7 +34,7 @@ table {
|
|
|
35
34
|
border-bottom: 1px solid rgb(var(--zn-border-color)) !important;
|
|
36
35
|
}
|
|
37
36
|
|
|
38
|
-
|
|
37
|
+
&.with-hover tbody tr:hover {
|
|
39
38
|
background-color: rgba(var(--zn-primary), 0.05) !important;
|
|
40
39
|
cursor: pointer;
|
|
41
40
|
}
|
|
@@ -115,10 +114,10 @@ table {
|
|
|
115
114
|
}
|
|
116
115
|
}
|
|
117
116
|
|
|
118
|
-
&:first-of-type > div:has(
|
|
117
|
+
&:first-of-type > div:has(zn-checkbox) {
|
|
119
118
|
justify-content: center;
|
|
120
119
|
|
|
121
|
-
|
|
120
|
+
zn-checkbox {
|
|
122
121
|
margin: 0;
|
|
123
122
|
}
|
|
124
123
|
}
|
|
@@ -240,7 +239,7 @@ table {
|
|
|
240
239
|
flex-direction: row;
|
|
241
240
|
justify-content: space-between;
|
|
242
241
|
align-items: baseline;
|
|
243
|
-
padding: var(--zn-spacing-small);
|
|
242
|
+
padding-block: var(--zn-spacing-small);
|
|
244
243
|
gap: var(--zn-spacing-small);
|
|
245
244
|
|
|
246
245
|
&__actions,
|
|
@@ -306,9 +305,7 @@ table {
|
|
|
306
305
|
}
|
|
307
306
|
}
|
|
308
307
|
}
|
|
309
|
-
|
|
310
308
|
}
|
|
311
|
-
|
|
312
309
|
}
|
|
313
310
|
|
|
314
311
|
.reduced-opacity .table tbody {
|
|
@@ -333,3 +330,84 @@ table {
|
|
|
333
330
|
filter: drop-shadow(0 5px 4px rgba(0, 0, 0, 0.04)) drop-shadow(0 4px 3px rgba(0, 0, 0, 0.1));
|
|
334
331
|
background-color: rgb(var(--zn-panel, 255, 255, 255));
|
|
335
332
|
}
|
|
333
|
+
|
|
334
|
+
/* Table Select Styles */
|
|
335
|
+
table tbody tr.table__row--selected {
|
|
336
|
+
background-color: var(--zn-color-purple-100);
|
|
337
|
+
|
|
338
|
+
&:nth-of-type(even) {
|
|
339
|
+
background-color: hsla(from var(--zn-color-purple-200) h s l / 0.8);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
&:hover {
|
|
343
|
+
background-color: var(--zn-color-purple-200) !important;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
td {
|
|
347
|
+
border-top: 1px solid var(--zn-color-purple-600) !important;
|
|
348
|
+
border-bottom: 1px solid var(--zn-color-purple-600) !important;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
td:first-of-type {
|
|
352
|
+
border-left: 1px solid var(--zn-color-purple-600);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
td:last-of-type {
|
|
356
|
+
border-right: 1px solid var(--zn-color-purple-600);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
&:last-of-type td:first-of-type {
|
|
360
|
+
border-bottom-left-radius: var(--zn-border-radius-large);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
&:last-of-type td:last-of-type {
|
|
364
|
+
border-bottom-right-radius: var(--zn-border-radius-large);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
& + tr.table__row--selected td {
|
|
368
|
+
border-top: 0 !important;
|
|
369
|
+
|
|
370
|
+
&:first-of-type {
|
|
371
|
+
border-top-left-radius: 0;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
&:last-of-type {
|
|
375
|
+
border-top-right-radius: 0;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
&:has(+ tr.table__row--selected) td {
|
|
380
|
+
border-bottom: 0 !important;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
&:first-of-type td {
|
|
384
|
+
border-top: 0 !important;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
&:has(+ tr.table__row--selected) {
|
|
388
|
+
td:first-of-type {
|
|
389
|
+
border-bottom-left-radius: 0;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
td:last-of-type {
|
|
393
|
+
border-bottom-right-radius: 0;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
table:has(tbody tr:first-of-type.table__row--selected) thead tr th {
|
|
399
|
+
border-bottom-color: var(--zn-color-purple-600) !important;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
.table-group__title {
|
|
404
|
+
font-weight: var(--zn-font-weight-semibold);
|
|
405
|
+
font-size: var(--zn-font-size-large);
|
|
406
|
+
color: rgba(var(--zn-text-panel-title), 100%);
|
|
407
|
+
line-height: var(--zn-line-height-looser);
|
|
408
|
+
padding-left: var(--zn-spacing-medium);
|
|
409
|
+
margin: 0;
|
|
410
|
+
overflow: hidden;
|
|
411
|
+
white-space: nowrap;
|
|
412
|
+
text-overflow: ellipsis;
|
|
413
|
+
}
|
|
@@ -106,7 +106,7 @@ export default class ZnDataTableFilter extends ZincElement implements ZincFormCo
|
|
|
106
106
|
return html`
|
|
107
107
|
<zn-button id="slideout-trigger" color="transparent" size="x-small" icon="filter_alt" icon-size="22"
|
|
108
108
|
slot="trigger"
|
|
109
|
-
tooltip="
|
|
109
|
+
tooltip="Filter">
|
|
110
110
|
</zn-button>
|
|
111
111
|
<zn-slideout class="slideout-basic" trigger="slideout-trigger" label="Filters">
|
|
112
112
|
|
|
@@ -2,7 +2,7 @@ import type Quill from 'quill';
|
|
|
2
2
|
import type Toolbar from "../toolbar/toolbar";
|
|
3
3
|
|
|
4
4
|
interface AttachmentOptions {
|
|
5
|
-
upload: (file: File) => Promise<{ path: any
|
|
5
|
+
upload: (file: File) => Promise<{ path: any; url: any; filename: any }>;
|
|
6
6
|
onFileUploaded?: (node: HTMLElement, {url}: { url: string }) => void;
|
|
7
7
|
attachmentInput?: HTMLInputElement;
|
|
8
8
|
}
|
|
@@ -26,6 +26,9 @@ export default class Attachment {
|
|
|
26
26
|
if (typeof (this._options.upload) !== "function") {
|
|
27
27
|
console.warn("[Quill Attachment Module] No upload function provided");
|
|
28
28
|
}
|
|
29
|
+
if (typeof (this._options.onFileUploaded) !== "function") {
|
|
30
|
+
console.warn("[Quill Attachment Module] No file uploaded function provided");
|
|
31
|
+
}
|
|
29
32
|
|
|
30
33
|
(this._quill
|
|
31
34
|
.getModule('toolbar') as Toolbar)
|
|
@@ -48,24 +51,34 @@ export default class Attachment {
|
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
const file = this._fileHolder.files[0];
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
this.addAttachment(file);
|
|
55
|
+
}
|
|
53
56
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
public addAttachment(file: File, dataUrl?: string) {
|
|
58
|
+
const attachmentId = generateId();
|
|
59
|
+
const insertWithDataUrl = (base64: string) => {
|
|
60
|
+
this._insertAttachment({ dataUrl: base64, file, id: attachmentId });
|
|
61
|
+
};
|
|
58
62
|
|
|
59
|
-
if (
|
|
63
|
+
if (dataUrl) {
|
|
64
|
+
insertWithDataUrl(dataUrl);
|
|
65
|
+
} else {
|
|
66
|
+
const fileReader = new FileReader();
|
|
67
|
+
fileReader.addEventListener('load', () => {
|
|
68
|
+
const base64Content = fileReader.result as string;
|
|
69
|
+
insertWithDataUrl(base64Content);
|
|
70
|
+
}, false);
|
|
60
71
|
fileReader.readAsDataURL(file);
|
|
61
72
|
}
|
|
62
73
|
|
|
63
|
-
this._options.upload
|
|
64
|
-
this.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
74
|
+
if (typeof this._options.upload === 'function') {
|
|
75
|
+
this._options.upload(file).then(({ path, url }: { path: string; url: string }) => {
|
|
76
|
+
this._uploadAttachment(file, url);
|
|
77
|
+
this._updateAttachment(attachmentId, url, path);
|
|
78
|
+
}).catch((err: { message: string }) => {
|
|
79
|
+
console.warn(err.message);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
69
82
|
}
|
|
70
83
|
|
|
71
84
|
private _attachmentContainer = document.createElement('div');
|
|
@@ -85,37 +98,36 @@ export default class Attachment {
|
|
|
85
98
|
this._quill.container.appendChild(attachmentContainer);
|
|
86
99
|
}
|
|
87
100
|
|
|
88
|
-
private _insertAttachment({dataUrl, file, id}: { dataUrl: string
|
|
101
|
+
private _insertAttachment({dataUrl, file, id}: { dataUrl: string; file: File; id: string }) {
|
|
89
102
|
this._attachmentContainer.appendChild(this._createAttachment(dataUrl, file, id));
|
|
90
103
|
}
|
|
91
104
|
|
|
92
105
|
private _updateAttachment(id: string, url: string, filename: string) {
|
|
93
|
-
const element = this._quill.container.querySelector(`#${id}`)
|
|
94
|
-
if (element)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (typeof this._options.onFileUploaded === 'function') {
|
|
106
|
-
this._options.onFileUploaded(element, {url});
|
|
106
|
+
const element = this._quill.container.querySelector(`#${id}`);
|
|
107
|
+
if (!element) return;
|
|
108
|
+
|
|
109
|
+
element.setAttribute('href', url);
|
|
110
|
+
const attachmentName = element.querySelector('.attachment-name');
|
|
111
|
+
if (attachmentName) {
|
|
112
|
+
if (filename) {
|
|
113
|
+
attachmentName.textContent = filename;
|
|
114
|
+
} else {
|
|
115
|
+
attachmentName.textContent = 'Error uploading file';
|
|
107
116
|
}
|
|
117
|
+
}
|
|
108
118
|
|
|
119
|
+
if (this._options.onFileUploaded) {
|
|
120
|
+
this._options.onFileUploaded(element as HTMLElement, {url});
|
|
121
|
+
}
|
|
109
122
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
123
|
+
// add the url to the hidden input
|
|
124
|
+
const attachments = this._options.attachmentInput;
|
|
125
|
+
if (attachments && filename) {
|
|
126
|
+
// value should be an array of attachment names
|
|
127
|
+
const value = attachments.value;
|
|
128
|
+
const data: string[] = value ? JSON.parse(value) as string[] : [];
|
|
129
|
+
data.push(filename);
|
|
130
|
+
attachments.value = JSON.stringify(data);
|
|
119
131
|
}
|
|
120
132
|
}
|
|
121
133
|
|
|
@@ -162,9 +174,9 @@ export default class Attachment {
|
|
|
162
174
|
if (attachments) {
|
|
163
175
|
// value should be an array of attachment names
|
|
164
176
|
const value = attachments.value;
|
|
165
|
-
|
|
166
|
-
const data = value ? JSON.parse(value) : [];
|
|
167
|
-
const index = data.indexOf(attachmentName ? attachmentName.textContent : '');
|
|
177
|
+
const attachmentName = attachment?.querySelector('.attachment-name');
|
|
178
|
+
const data: string[] = value ? JSON.parse(value) as string[] : [];
|
|
179
|
+
const index = data.indexOf(attachmentName?.textContent ? attachmentName.textContent : '');
|
|
168
180
|
if (index > -1) {
|
|
169
181
|
data.splice(index, 1);
|
|
170
182
|
attachments.value = JSON.stringify(data);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import './toolbar.component';
|
|
2
2
|
import Quill from "quill";
|
|
3
3
|
import QuillToolbar from "quill/modules/toolbar";
|
|
4
|
+
import type Attachment from "../attachment/attachment";
|
|
4
5
|
import type DialogComponent from "../dialog/dialog.component";
|
|
5
6
|
import type Emoji from "../emoji/emoji";
|
|
6
7
|
import type ToolbarComponent from "./toolbar.component";
|
|
@@ -26,6 +27,7 @@ class Toolbar extends QuillToolbar {
|
|
|
26
27
|
this.addHandler('redo', () => quill.history.redo());
|
|
27
28
|
this.addHandler('undo', () => quill.history.undo());
|
|
28
29
|
this.addHandler('dialog', (value: string) => this._openDialog(value));
|
|
30
|
+
this.addHandler('image', () => this._addImage());
|
|
29
31
|
|
|
30
32
|
this._quill = quill;
|
|
31
33
|
this._component = options.container;
|
|
@@ -323,6 +325,46 @@ class Toolbar extends QuillToolbar {
|
|
|
323
325
|
const emoji = this._quill.getModule('emoji') as Emoji;
|
|
324
326
|
emoji?.initPicker();
|
|
325
327
|
}
|
|
328
|
+
|
|
329
|
+
private _addImage() {
|
|
330
|
+
const input = document.createElement('input')
|
|
331
|
+
input.setAttribute('type', 'file')
|
|
332
|
+
input.setAttribute('accept', 'image/*')
|
|
333
|
+
input.click()
|
|
334
|
+
|
|
335
|
+
input.onchange = () => {
|
|
336
|
+
const file = input.files?.[0];
|
|
337
|
+
if (!file?.type || !file.type.startsWith('image/')) return;
|
|
338
|
+
|
|
339
|
+
const reader = new window.FileReader();
|
|
340
|
+
reader.onload = () => {
|
|
341
|
+
const dataUrl = reader.result as string;
|
|
342
|
+
const selection = this._quill.getSelection(true);
|
|
343
|
+
const index = selection ? selection.index + selection.length : this._quill.getLength();
|
|
344
|
+
|
|
345
|
+
this._quill.insertEmbed(index, 'image', dataUrl, Quill.sources.USER);
|
|
346
|
+
|
|
347
|
+
const root = this._quill.root as HTMLElement;
|
|
348
|
+
const images = Array.from(root.querySelectorAll('img')) as HTMLImageElement[];
|
|
349
|
+
const inserted = images.reverse().find(img => img.getAttribute('src') === dataUrl) || null;
|
|
350
|
+
if (inserted) {
|
|
351
|
+
inserted.setAttribute('alt', file.name.replace(/\.[^/.]+$/, ''));
|
|
352
|
+
inserted.setAttribute('title', file.name);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
this._quill.setSelection(index + 1, 0, Quill.sources.USER);
|
|
356
|
+
this._syncToolbarState();
|
|
357
|
+
|
|
358
|
+
try {
|
|
359
|
+
const attachmentModule = this._quill.getModule('attachment') as Attachment | undefined;
|
|
360
|
+
attachmentModule?.addAttachment(file, dataUrl);
|
|
361
|
+
} catch (err) {
|
|
362
|
+
console.warn('[Toolbar] Failed to process image with attachment handler', err);
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
reader.readAsDataURL(file);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
326
368
|
}
|
|
327
369
|
|
|
328
370
|
export default Toolbar;
|