@kubex/zinc 1.1.185 → 2.0.1
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/.browserslistrc +2 -0
- package/.github/workflows/js_build_and_deploy.yaml +54 -1
- package/.github/workflows/test.yaml +27 -0
- package/dist/chunks/zn.UWJLIXDT.js +1 -0
- package/dist/custom-elements.json +10319 -2666
- package/dist/vscode.html-custom-data.json +1012 -57
- package/dist/web-types.json +2097 -145
- package/dist/zn.d.ts +2294 -416
- package/dist/zn.min.css +1 -1
- package/dist/zn.min.js +2310 -1637
- package/docs/_includes/default.njk +4 -1
- package/docs/assets/scripts/docs.js +22 -6
- package/docs/assets/styles/docs.css +9 -2
- package/docs/data/report-table-day-compare.json +380 -0
- package/docs/data/report-table.json +314 -0
- package/docs/pages/components/avatar-group.md +57 -0
- package/docs/pages/components/badge.md +67 -0
- package/docs/pages/components/button.md +41 -1
- package/docs/pages/components/chat-message.md +16 -0
- package/docs/pages/components/checkbox.md +1 -1
- package/docs/pages/components/chip.md +25 -13
- package/docs/pages/components/color-picker.md +37 -0
- package/docs/pages/components/copy-button.md +2 -2
- package/docs/pages/components/data-table-filter.md +33 -50
- package/docs/pages/components/data-table-search.md +4 -4
- package/docs/pages/components/data-table.md +27 -3
- package/docs/pages/components/date-range.md +116 -0
- package/docs/pages/components/dialog.md +29 -37
- package/docs/pages/components/diff-row.md +28 -0
- package/docs/pages/components/diff.md +46 -0
- package/docs/pages/components/editor.md +55 -0
- package/docs/pages/components/icon.md +46 -1
- package/docs/pages/components/input.md +30 -11
- package/docs/pages/components/item.md +35 -0
- package/docs/pages/components/key-value.md +25 -0
- package/docs/pages/components/menu.md +74 -7
- package/docs/pages/components/nav-item.md +15 -0
- package/docs/pages/components/nav-list.md +132 -0
- package/docs/pages/components/navbar.md +100 -1
- package/docs/pages/components/page.md +242 -74
- package/docs/pages/components/priority-list.md +43 -5
- package/docs/pages/components/query-builder.md +23 -0
- package/docs/pages/components/radio-group.md +33 -1
- package/docs/pages/components/radio.md +1 -1
- package/docs/pages/components/report-table.md +202 -0
- package/docs/pages/components/stat.md +30 -0
- package/docs/pages/components/tile.md +11 -0
- package/docs/pages/components/tooltip.md +1 -1
- package/docs/pages/components/translation-grid.md +86 -0
- package/docs/pages/components/vertical-stepper.md +108 -64
- package/package.json +2 -1
- package/scss/_global-spacing.scss +2 -2
- package/scss/_root.scss +11 -171
- package/scss/boot.scss +14 -0
- package/scss/mixins/_helper-mixins.scss +24 -2
- package/scss/mixins/_motion-mixins.scss +126 -0
- package/scss/mixins/_spacing-mixins.scss +4 -3
- package/scss/mixins/_tab-indicator-mixins.scss +12 -0
- package/scss/mixins/_typography-mixins.scss +1 -1
- package/scss/mixins/index.scss +14 -0
- package/scss/shared/_base.scss +32 -1
- package/scss/shared/_button-mixin.scss +7 -5
- package/scss/shared/_table.scss +35 -4
- package/scss/shared/nav.scss +4 -0
- package/scss/shared/shimmer.scss +1 -1
- package/scss/table-controls.scss +206 -0
- package/scss/themes/_dark.scss +1 -3
- package/scss/themes/_index.scss +1 -1
- package/scss/themes/_light.scss +46 -6
- package/scss/themes/_modes.scss +11 -11
- package/scss/themes/_openbyte.scss +944 -0
- package/scss/themes/_typography.scss +4 -2
- package/scss/variables/index.scss +0 -9
- package/src/components/alert/alert.component.ts +14 -4
- package/src/components/alert/alert.scss +55 -69
- package/src/components/alert/alert.test.ts +8 -0
- package/src/components/animated-button/animated-button.component.ts +3 -2
- package/src/components/animated-button/animated-button.scss +6 -23
- package/src/components/animated-button/animated-button.test.ts +4 -3
- package/src/components/avatar-group/avatar-group.component.ts +84 -0
- package/src/components/avatar-group/avatar-group.scss +54 -0
- package/src/components/avatar-group/avatar-group.test.ts +59 -0
- package/src/components/avatar-group/index.ts +12 -0
- package/src/components/badge/badge.component.ts +49 -0
- package/src/components/badge/badge.scss +80 -0
- package/src/components/badge/badge.test.ts +34 -0
- package/src/components/badge/index.ts +12 -0
- package/src/components/bulk-actions/bulk-actions.component.ts +14 -2
- package/src/components/button/button.component.ts +228 -8
- package/src/components/button/button.scss +115 -32
- package/src/components/button/button.test.ts +122 -0
- package/src/components/button-menu/button-menu.component.ts +32 -11
- package/src/components/button-menu/button-menu.scss +5 -1
- package/src/components/channel-tile/channel-tile.component.ts +34 -27
- package/src/components/channel-tile/channel-tile.scss +76 -43
- package/src/components/chart/builders.test.ts +46 -0
- package/src/components/chart/builders.ts +27 -3
- package/src/components/chart/chart-live.test.ts +4 -6
- package/src/components/chart/chart.component.ts +125 -5
- package/src/components/chart/chart.scss +121 -0
- package/src/components/chart/chart.test.ts +81 -0
- package/src/components/chat-message/chat-message.component.ts +21 -1
- package/src/components/chat-message/chat-message.scss +13 -2
- package/src/components/chat-message/chat-message.test.ts +9 -0
- package/src/components/checkbox/checkbox.component.ts +61 -27
- package/src/components/checkbox/checkbox.scss +58 -25
- package/src/components/checkbox-group/checkbox-group.component.ts +7 -1
- package/src/components/chip/chip.component.ts +11 -1
- package/src/components/chip/chip.scss +91 -26
- package/src/components/chip/chip.test.ts +17 -0
- package/src/components/collapsible/collapsible.component.ts +17 -3
- package/src/components/collapsible/collapsible.scss +19 -10
- package/src/components/color-picker/color-picker.component.ts +277 -0
- package/src/components/color-picker/color-picker.scss +141 -0
- package/src/components/color-picker/color-picker.test.ts +57 -0
- package/src/components/color-picker/index.ts +12 -0
- package/src/components/cols/cols.component.ts +10 -0
- package/src/components/cols/cols.test.ts +17 -0
- package/src/components/confirm/confirm.component.ts +20 -6
- package/src/components/confirm/confirm.test.ts +52 -0
- package/src/components/content-block/content-block.component.ts +38 -9
- package/src/components/content-block/content-block.scss +11 -5
- package/src/components/content-block/content-block.test.ts +26 -0
- package/src/components/copy-button/copy-button.component.ts +2 -2
- package/src/components/copy-button/copy-button.scss +17 -4
- package/src/components/data-select/data-select.component.ts +3 -1
- package/src/components/data-table/data-table.component.ts +362 -140
- package/src/components/data-table/data-table.scss +136 -60
- package/src/components/data-table/data-table.test.ts +356 -4
- package/src/components/data-table-filter/data-table-filter.component.ts +426 -406
- package/src/components/data-table-filter/data-table-filter.scss +176 -148
- package/src/components/data-table-filter/data-table-filter.test.ts +271 -273
- package/src/components/data-table-search/data-table-search.component.ts +4 -2
- package/src/components/data-table-search/data-table-search.scss +8 -1
- package/src/components/data-table-search/data-table-search.test.ts +29 -0
- package/src/components/date-range/date-range-math.test.ts +78 -0
- package/src/components/date-range/date-range-math.ts +105 -0
- package/src/components/date-range/date-range.component.ts +375 -0
- package/src/components/date-range/date-range.scss +272 -0
- package/src/components/date-range/date-range.test.ts +247 -0
- package/src/components/date-range/index.ts +12 -0
- package/src/components/datepicker/datepicker.component.ts +44 -7
- package/src/components/datepicker/datepicker.scss +9 -0
- package/src/components/datepicker/datepicker.test.ts +2 -2
- package/src/components/dialog/dialog.component.ts +38 -19
- package/src/components/dialog/dialog.scss +65 -36
- package/src/components/dialog/dialog.test.ts +14 -0
- package/src/components/diff/diff.component.ts +55 -0
- package/src/components/diff/diff.scss +46 -0
- package/src/components/diff/diff.test.ts +15 -0
- package/src/components/diff/index.ts +12 -0
- package/src/components/diff-row/diff-row.component.ts +75 -0
- package/src/components/diff-row/diff-row.scss +70 -0
- package/src/components/diff-row/diff-row.test.ts +37 -0
- package/src/components/diff-row/index.ts +12 -0
- package/src/components/dropdown/dropdown.component.ts +12 -10
- package/src/components/dropdown/dropdown.scss +2 -0
- package/src/components/editor/editor.component.ts +159 -11
- package/src/components/editor/editor.scss +250 -1
- package/src/components/editor/editor.test.ts +31 -3
- package/src/components/editor/modules/ai/panel/ai-panel.scss +0 -1
- package/src/components/editor/modules/attachment/attachment.ts +53 -31
- package/src/components/editor/modules/context-menu/context-menu.ts +17 -0
- package/src/components/editor/modules/dialog/dialog.component.ts +23 -1
- package/src/components/editor/modules/dialog/dialog.test.ts +28 -0
- package/src/components/editor/modules/emoji/emoji.ts +4 -5
- package/src/components/editor/modules/merge-field/merge-field.ts +83 -0
- package/src/components/editor/modules/toolbar/toolbar.component.ts +121 -68
- package/src/components/editor/modules/toolbar/toolbar.scss +38 -0
- package/src/components/editor/modules/toolbar/toolbar.ts +25 -1
- package/src/components/empty-state/empty-state.component.ts +6 -1
- package/src/components/empty-state/empty-state.scss +21 -4
- package/src/components/expanding-action/expanding-action.component.ts +22 -34
- package/src/components/expanding-action/expanding-action.scss +3 -45
- package/src/components/expanding-action/expanding-action.test.ts +2 -1
- package/src/components/file/file.component.ts +30 -2
- package/src/components/file/file.scss +6 -0
- package/src/components/flow-builder/flow-builder.component.ts +36 -3
- package/src/components/flow-builder/flow-builder.scss +0 -6
- package/src/components/flow-builder/modules/flow-canvas/flow-canvas.component.ts +8 -0
- package/src/components/flow-builder/modules/flow-canvas/flow-canvas.scss +10 -5
- package/src/components/flow-builder/modules/flow-node/flow-node.component.ts +17 -0
- package/src/components/flow-builder/modules/flow-node/flow-node.scss +10 -0
- package/src/components/form-actions/form-actions.component.ts +48 -2
- package/src/components/form-actions/form-actions.scss +1 -1
- package/src/components/form-group/form-group.component.ts +10 -3
- package/src/components/form-group/form-group.scss +37 -4
- package/src/components/form-group/form-group.test.ts +10 -0
- package/src/components/header/header.component.ts +11 -4
- package/src/components/header/header.scss +47 -5
- package/src/components/hover-container/hover-container.component.ts +4 -5
- package/src/components/icon/icon.component.ts +95 -9
- package/src/components/icon/icon.scss +70 -6
- package/src/components/icon/icon.test.ts +55 -0
- package/src/components/icon-picker/icon-picker.component.ts +35 -1
- package/src/components/inline-edit/inline-edit.component.ts +38 -8
- package/src/components/inline-edit/inline-edit.scss +128 -19
- package/src/components/input/input.component.ts +173 -297
- package/src/components/input/input.scss +65 -43
- package/src/components/input/input.test.ts +86 -4
- package/src/components/item/item.component.ts +12 -0
- package/src/components/item/item.scss +102 -3
- package/src/components/item/item.test.ts +23 -0
- package/src/components/key/key.component.ts +2 -2
- package/src/components/key/key.scss +11 -0
- package/src/components/key-value/index.ts +12 -0
- package/src/components/key-value/key-value.component.ts +160 -0
- package/src/components/key-value/key-value.scss +20 -0
- package/src/components/key-value/key-value.test.ts +22 -0
- package/src/components/markdown-editor/markdown-editor.component.ts +8 -1
- package/src/components/menu/menu.component.ts +21 -2
- package/src/components/menu/menu.scss +43 -23
- package/src/components/menu/menu.test.ts +33 -0
- package/src/components/menu-item/menu-item.component.ts +13 -2
- package/src/components/menu-item/menu-item.scss +56 -23
- package/src/components/menu-item/menu-item.test.ts +31 -0
- package/src/components/menu-item/submenu-controller.ts +25 -15
- package/src/components/menu-label/index.ts +12 -0
- package/src/components/menu-label/menu-label.component.ts +30 -0
- package/src/components/menu-label/menu-label.scss +18 -0
- package/src/components/nav-item/index.ts +12 -0
- package/src/components/nav-item/nav-item.component.ts +83 -0
- package/src/components/nav-item/nav-item.scss +128 -0
- package/src/components/nav-list/index.ts +12 -0
- package/src/components/nav-list/nav-list.component.ts +313 -0
- package/src/components/nav-list/nav-list.scss +87 -0
- package/src/components/nav-list/nav-list.test.ts +250 -0
- package/src/components/navbar/navbar.component.ts +235 -171
- package/src/components/navbar/navbar.scss +333 -169
- package/src/components/navbar/navbar.test.ts +63 -143
- package/src/components/page/page.component.ts +114 -29
- package/src/components/page/page.scss +223 -95
- package/src/components/page/page.test.ts +214 -3
- package/src/components/page-builder/page-builder.component.ts +51 -5
- package/src/components/page-builder/page-builder.scss +6 -1
- package/src/components/page-nav/page-nav.component.ts +25 -3
- package/src/components/page-nav/page-nav.scss +10 -2
- package/src/components/pagination/pagination.scss +1 -1
- package/src/components/panel/panel.component.ts +14 -13
- package/src/components/panel/panel.scss +14 -10
- package/src/components/panel/panel.test.ts +42 -0
- package/src/components/popup/popup.component.ts +20 -13
- package/src/components/popup/popup.test.ts +21 -1
- package/src/components/priority-list/priority-list.component.ts +157 -172
- package/src/components/priority-list/priority-list.scss +113 -17
- package/src/components/priority-list/priority-list.test.ts +301 -3
- package/src/components/progress-bar/progress-bar.component.ts +1 -1
- package/src/components/progress-bar/progress-bar.scss +16 -4
- package/src/components/progress-tile/progress-tile.scss +1 -1
- package/src/components/query-builder/query-builder.component.ts +85 -10
- package/src/components/query-builder/query-builder.scss +7 -1
- package/src/components/query-builder/query-builder.test.ts +40 -0
- package/src/components/radio/radio.component.ts +57 -18
- package/src/components/radio/radio.scss +101 -26
- package/src/components/radio-group/radio-group.component.ts +45 -7
- package/src/components/radio-group/radio-group.scss +17 -0
- package/src/components/radio-group/radio-group.test.ts +63 -2
- package/src/components/rating/rating.component.ts +23 -0
- package/src/components/rating/rating.scss +1 -1
- package/src/components/remarkd-editor/remarkd-editor.component.ts +60 -5
- package/src/components/remarkd-editor/remarkd-editor.scss +12 -4
- package/src/components/report-table/index.ts +12 -0
- package/src/components/report-table/report-format.test.ts +86 -0
- package/src/components/report-table/report-format.ts +72 -0
- package/src/components/report-table/report-math.test.ts +74 -0
- package/src/components/report-table/report-math.ts +252 -0
- package/src/components/report-table/report-table.component.ts +681 -0
- package/src/components/report-table/report-table.scss +328 -0
- package/src/components/report-table/report-table.test.ts +702 -0
- package/src/components/reveal/reveal.component.ts +15 -1
- package/src/components/reveal/reveal.scss +5 -0
- package/src/components/schedule-builder/schedule-builder.component.ts +23 -8
- package/src/components/scroll-container/scroll-container.component.ts +19 -1
- package/src/components/scroll-container/scroll-container.scss +6 -1
- package/src/components/select/select.component.ts +52 -36
- package/src/components/select/select.scss +42 -25
- package/src/components/select/select.test.ts +14 -2
- package/src/components/settings-container/settings-container.scss +18 -5
- package/src/components/sidebar/sidebar.component.ts +8 -1
- package/src/components/sidebar/sidebar.scss +6 -1
- package/src/components/skeleton/skeleton.component.ts +3 -3
- package/src/components/skeleton/skeleton.scss +6 -6
- package/src/components/skeleton/skeleton.test.ts +9 -0
- package/src/components/slash-menu/slash-menu.scss +1 -1
- package/src/components/slideout/slideout.component.ts +20 -3
- package/src/components/slideout/slideout.scss +13 -6
- package/src/components/slideout/slideout.test.ts +19 -0
- package/src/components/split-button/split-button.component.ts +1 -1
- package/src/components/split-pane/split-pane.component.ts +71 -8
- package/src/components/split-pane/split-pane.scss +145 -41
- package/src/components/split-pane/split-pane.test.ts +23 -3
- package/src/components/stat/stat.component.ts +209 -14
- package/src/components/stat/stat.scss +305 -5
- package/src/components/stat/stat.test.ts +143 -2
- package/src/components/status-indicator/status-indicator.scss +22 -21
- package/src/components/stepper/stepper.scss +3 -3
- package/src/components/tab/tab.component.ts +1 -0
- package/src/components/table/table.component.ts +12 -1
- package/src/components/table/table.scss +21 -13
- package/src/components/table/table.test.ts +17 -0
- package/src/components/tabs/tabs.component.ts +150 -37
- package/src/components/tabs/tabs.scss +48 -7
- package/src/components/tabs/tabs.test.ts +169 -0
- package/src/components/textarea/textarea.component.ts +25 -3
- package/src/components/textarea/textarea.scss +10 -0
- package/src/components/textarea/textarea.test.ts +8 -4
- package/src/components/theme-editor/theme-editor.scss +0 -6
- package/src/components/tile/tile.component.ts +32 -2
- package/src/components/tile/tile.scss +40 -7
- package/src/components/tile/tile.test.ts +10 -0
- package/src/components/toggle/toggle.component.ts +23 -7
- package/src/components/toggle/toggle.scss +36 -29
- package/src/components/toggle/toggle.test.ts +17 -0
- package/src/components/tooltip/tooltip.component.ts +3 -0
- package/src/components/tooltip/tooltip.scss +11 -6
- package/src/components/tooltip/tooltip.test.ts +12 -0
- package/src/components/translation-field/index.ts +12 -0
- package/src/components/translation-field/translation-field.component.ts +55 -0
- package/src/components/translation-field/translation-field.scss +101 -0
- package/src/components/translation-fields/index.ts +12 -0
- package/src/components/translation-fields/translation-fields.component.ts +80 -0
- package/src/components/translation-fields/translation-fields.scss +60 -0
- package/src/components/translation-grid/index.ts +12 -0
- package/src/components/translation-grid/translation-grid.component.ts +157 -0
- package/src/components/translation-grid/translation-grid.scss +138 -0
- package/src/components/translation-grid/translation-grid.test.ts +28 -0
- package/src/components/vertical-stepper/vertical-stepper.component.ts +35 -9
- package/src/components/vertical-stepper/vertical-stepper.scss +110 -39
- package/src/components/vertical-stepper/vertical-stepper.test.ts +35 -0
- package/src/components/well/well.scss +2 -2
- package/src/events/events.ts +2 -0
- package/src/events/zn-chart-click.ts +13 -0
- package/src/events/zn-report-change.ts +7 -0
- package/src/form-control.scss +36 -3
- package/src/internal/anchored-popover.ts +175 -0
- package/src/internal/animate.ts +19 -5
- package/src/internal/cosmic-border.ts +15 -0
- package/src/internal/form.ts +1 -1
- package/src/internal/hover-intent.ts +15 -0
- package/src/internal/input-modality.ts +15 -0
- package/src/internal/pointer-intent.ts +37 -0
- package/src/internal/reorder.ts +632 -0
- package/src/internal/scroll.ts +51 -3
- package/src/internal/selection-card.ts +1 -1
- package/src/internal/tab-indicator.ts +92 -0
- package/src/internal/table-sort.ts +25 -0
- package/src/internal/table-tabs.ts +108 -0
- package/src/internal/test-drag.ts +35 -0
- package/src/internal/theme-openbyte.test.ts +126 -0
- package/src/internal/theme.ts +4 -4
- package/src/internal/zinc-element.ts +2 -2
- package/src/selection-card.scss +25 -0
- package/src/types/web-test-runner-commands.d.ts +1 -0
- package/src/utilities/color.test.ts +43 -0
- package/src/utilities/color.ts +189 -0
- package/src/zinc.ts +14 -0
- package/.github/workflows/eleventy_build.yml +0 -42
- package/scss/themes/_aura.scss +0 -114
|
@@ -49,8 +49,7 @@ describe('<zn-editor>', () => {
|
|
|
49
49
|
document.dispatchEvent(new Event('zn-editor-update'));
|
|
50
50
|
await el.updateComplete;
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
expect(sessionStorage.getItem(KEY)).to.contain('Typed reply');
|
|
52
|
+
expect(sessionStorage.getItem(KEY)).to.contain('Typed reply');
|
|
54
53
|
});
|
|
55
54
|
|
|
56
55
|
it('should clear cached content on form submit', async () => {
|
|
@@ -62,10 +61,39 @@ describe('<zn-editor>', () => {
|
|
|
62
61
|
|
|
63
62
|
document.dispatchEvent(new Event('zn-editor-update'));
|
|
64
63
|
await el.updateComplete;
|
|
65
|
-
expect(sessionStorage.getItem(KEY)).to.contain('Typed
|
|
64
|
+
expect(sessionStorage.getItem(KEY)).to.contain('Typed reply');
|
|
66
65
|
|
|
67
66
|
form.requestSubmit();
|
|
68
67
|
expect(sessionStorage.getItem(KEY)).to.equal(null);
|
|
69
68
|
});
|
|
70
69
|
});
|
|
70
|
+
|
|
71
|
+
describe('merge fields', () => {
|
|
72
|
+
it('should show tokens as labelled chips and keep the raw token in the value', async () => {
|
|
73
|
+
const el = await fixture<ZnEditor>(html`
|
|
74
|
+
<zn-editor merge-fields .value=${'<p>Hi {{ recipient.firstName }}</p>'}>
|
|
75
|
+
<zn-editor-quick-action slot="context-items" icon="braces@lu" label="First name"
|
|
76
|
+
content="{{recipient.firstName}}"></zn-editor-quick-action>
|
|
77
|
+
</zn-editor>`);
|
|
78
|
+
|
|
79
|
+
const chip = el.shadowRoot!.querySelector<HTMLElement>('.ql-merge-field')!;
|
|
80
|
+
expect(chip.textContent).to.contain('First name');
|
|
81
|
+
|
|
82
|
+
(el as unknown as { quillElement: { insertText: (i: number, t: string, s: string) => void } })
|
|
83
|
+
.quillElement.insertText(0, 'Oh ', 'user');
|
|
84
|
+
expect(el.value).to.equal('<p>Oh Hi {{recipient.firstName}}</p>');
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('document mode', () => {
|
|
89
|
+
it('should render designed HTML in place and keep it as written', async () => {
|
|
90
|
+
const markup = '<table width="600"><tbody><tr><td style="color:red">Hi</td></tr></tbody></table>';
|
|
91
|
+
const el = await fixture<ZnEditor>(html`<zn-editor document-mode .value=${markup}></zn-editor>`);
|
|
92
|
+
|
|
93
|
+
const frame = el.shadowRoot!.querySelector<HTMLIFrameElement>('.editor__document')!;
|
|
94
|
+
await new Promise(resolve => frame.addEventListener('load', resolve, {once: true}));
|
|
95
|
+
expect(frame.contentDocument!.querySelector('td')!.getAttribute('style')).to.equal('color:red');
|
|
96
|
+
expect(el.value).to.equal(markup);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
71
99
|
});
|
|
@@ -3,15 +3,18 @@ import type Toolbar from "../toolbar/toolbar";
|
|
|
3
3
|
|
|
4
4
|
interface AttachmentOptions {
|
|
5
5
|
upload: (file: File) => Promise<{ path: any; url: any; filename: any }>;
|
|
6
|
-
|
|
6
|
+
// node is null when the chip was removed (or reset) during the upload
|
|
7
|
+
onFileUploaded?: (node: HTMLElement | null, {url}: { url: string }) => void;
|
|
8
|
+
onFileUploadFailed?: (error: Error) => void;
|
|
7
9
|
attachmentInput?: HTMLInputElement;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
// Mirrors the server's upload body-size limit.
|
|
13
|
+
const MAX_ATTACHMENT_BYTES = 30 * 1024 * 1024;
|
|
14
|
+
|
|
15
|
+
let nextAttachmentId = 0;
|
|
16
|
+
|
|
17
|
+
const generateId = () => `attachment_${++nextAttachmentId}`;
|
|
15
18
|
|
|
16
19
|
export default class Attachment {
|
|
17
20
|
private _quill: Quill;
|
|
@@ -46,38 +49,33 @@ export default class Attachment {
|
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
private _fileChanged() {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
+
const file = this._fileHolder?.files?.[0];
|
|
53
|
+
if (!file) return;
|
|
52
54
|
|
|
53
|
-
const file = this._fileHolder.files[0];
|
|
54
55
|
this.addAttachment(file);
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
public addAttachment(file: File, dataUrl?: string) {
|
|
58
59
|
const attachmentId = generateId();
|
|
59
|
-
|
|
60
|
-
this._insertAttachment({dataUrl: base64, file, id: attachmentId});
|
|
61
|
-
};
|
|
60
|
+
this._insertAttachment({href: dataUrl || URL.createObjectURL(file), file, id: attachmentId});
|
|
62
61
|
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const fileReader = new FileReader();
|
|
67
|
-
fileReader.addEventListener('load', () => {
|
|
68
|
-
const base64Content = fileReader.result as string;
|
|
69
|
-
insertWithDataUrl(base64Content);
|
|
70
|
-
}, false);
|
|
71
|
-
fileReader.readAsDataURL(file);
|
|
62
|
+
if (file.size > MAX_ATTACHMENT_BYTES) {
|
|
63
|
+
this._markAttachmentFailed(attachmentId, 'File too large (max 30 MB)');
|
|
64
|
+
return;
|
|
72
65
|
}
|
|
73
66
|
|
|
74
67
|
if (typeof this._options.upload === 'function') {
|
|
68
|
+
// the rejection handler covers only the upload, so each upload settles exactly once
|
|
75
69
|
this._options.upload(file).then(async ({path, url}: { path: string; url: string }) => {
|
|
76
70
|
await this._uploadAttachment(file, url);
|
|
77
|
-
|
|
78
|
-
}).
|
|
71
|
+
return {path, url};
|
|
72
|
+
}).then(({path, url}) => this._updateAttachment(attachmentId, url, path), (err: Error) => {
|
|
79
73
|
console.warn(err.message);
|
|
74
|
+
this._markAttachmentFailed(attachmentId, 'Upload failed');
|
|
75
|
+
this._options.onFileUploadFailed?.(err);
|
|
80
76
|
});
|
|
77
|
+
} else {
|
|
78
|
+
this._markAttachmentFailed(attachmentId, 'Upload failed');
|
|
81
79
|
}
|
|
82
80
|
}
|
|
83
81
|
|
|
@@ -98,14 +96,36 @@ export default class Attachment {
|
|
|
98
96
|
this._quill.container.appendChild(attachmentContainer);
|
|
99
97
|
}
|
|
100
98
|
|
|
101
|
-
private _insertAttachment({
|
|
102
|
-
this._attachmentContainer.appendChild(this._createAttachment(
|
|
99
|
+
private _insertAttachment({href, file, id}: { href: string; file: File; id: string }) {
|
|
100
|
+
this._attachmentContainer.appendChild(this._createAttachment(href, file, id));
|
|
103
101
|
}
|
|
104
102
|
|
|
105
|
-
private
|
|
106
|
-
const element = this._quill.container.querySelector(`#${id}`);
|
|
103
|
+
private _markAttachmentFailed(id: string, message: string) {
|
|
104
|
+
const element = this._quill.container.querySelector<HTMLElement>(`#${id}`);
|
|
107
105
|
if (!element) return;
|
|
108
106
|
|
|
107
|
+
const attachmentName = element.querySelector('.attachment-name');
|
|
108
|
+
if (attachmentName) {
|
|
109
|
+
attachmentName.textContent = message;
|
|
110
|
+
}
|
|
111
|
+
element.style.borderColor = '#d93025';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private _revokeObjectUrl(element: Element) {
|
|
115
|
+
const href = element.getAttribute('href');
|
|
116
|
+
if (href?.startsWith('blob:')) {
|
|
117
|
+
URL.revokeObjectURL(href);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private _updateAttachment(id: string, url: string, filename: string) {
|
|
122
|
+
const element = this._quill.container.querySelector<HTMLElement>(`#${id}`);
|
|
123
|
+
if (!element) {
|
|
124
|
+
this._options.onFileUploaded?.(null, {url});
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this._revokeObjectUrl(element);
|
|
109
129
|
element.setAttribute('href', url);
|
|
110
130
|
const attachmentName = element.querySelector('.attachment-name');
|
|
111
131
|
if (attachmentName) {
|
|
@@ -117,7 +137,7 @@ export default class Attachment {
|
|
|
117
137
|
}
|
|
118
138
|
|
|
119
139
|
if (this._options.onFileUploaded) {
|
|
120
|
-
this._options.onFileUploaded(element
|
|
140
|
+
this._options.onFileUploaded(element, {url});
|
|
121
141
|
}
|
|
122
142
|
|
|
123
143
|
// add the url to the hidden input
|
|
@@ -131,9 +151,9 @@ export default class Attachment {
|
|
|
131
151
|
}
|
|
132
152
|
}
|
|
133
153
|
|
|
134
|
-
private _createAttachment(
|
|
154
|
+
private _createAttachment(href: string, file: File, id: string) {
|
|
135
155
|
const attachment = document.createElement('a');
|
|
136
|
-
attachment.setAttribute('href',
|
|
156
|
+
attachment.setAttribute('href', href);
|
|
137
157
|
attachment.setAttribute('id', id);
|
|
138
158
|
attachment.setAttribute('download', file.name);
|
|
139
159
|
attachment.style.padding = '10px';
|
|
@@ -165,6 +185,7 @@ export default class Attachment {
|
|
|
165
185
|
|
|
166
186
|
/** Clear every staged attachment preview and reset the backing form input. */
|
|
167
187
|
public reset() {
|
|
188
|
+
Array.from(this._attachmentContainer.children).forEach(chip => this._revokeObjectUrl(chip));
|
|
168
189
|
this._attachmentContainer.replaceChildren();
|
|
169
190
|
const attachments = this._options.attachmentInput;
|
|
170
191
|
if (attachments) {
|
|
@@ -175,6 +196,7 @@ export default class Attachment {
|
|
|
175
196
|
private _removeAttachment(id: string) {
|
|
176
197
|
const attachment = this._quill.container.querySelector(`#${id}`);
|
|
177
198
|
if (attachment) {
|
|
199
|
+
this._revokeObjectUrl(attachment);
|
|
178
200
|
attachment.remove();
|
|
179
201
|
}
|
|
180
202
|
|
|
@@ -29,6 +29,10 @@ class ContextMenu {
|
|
|
29
29
|
private _dismissedIndex = -1;
|
|
30
30
|
private _keydownHandler = (e: KeyboardEvent) => this.onKeydown(e);
|
|
31
31
|
private _docClickHandler = (e: MouseEvent) => this.onDocumentClick(e);
|
|
32
|
+
private _pointerHeld = false;
|
|
33
|
+
private _pointerReleaseHandler = () => {
|
|
34
|
+
this._pointerHeld = false;
|
|
35
|
+
};
|
|
32
36
|
private _featureConfig: EditorFeatureConfig = {};
|
|
33
37
|
private readonly _caretAnchor: VirtualElement = {
|
|
34
38
|
getBoundingClientRect: () => this.caretRect()
|
|
@@ -57,6 +61,10 @@ class ContextMenu {
|
|
|
57
61
|
this._quill.on(Quill.events.EDITOR_CHANGE, () => this.updateFromEditor());
|
|
58
62
|
this._quill.root.addEventListener('keydown', this._keydownHandler);
|
|
59
63
|
this._menu.addEventListener(SLASH_ITEM_SELECT, (e: Event) => this.onItemSelect(e as CustomEvent<{ item: ContextMenuItem }>));
|
|
64
|
+
this._menu.addEventListener('pointerdown', () => {
|
|
65
|
+
this._pointerHeld = true;
|
|
66
|
+
this._quill.container.ownerDocument.addEventListener('pointerup', this._pointerReleaseHandler, {once: true, capture: true});
|
|
67
|
+
});
|
|
60
68
|
this._quill.focus();
|
|
61
69
|
}
|
|
62
70
|
|
|
@@ -202,6 +210,15 @@ class ContextMenu {
|
|
|
202
210
|
return;
|
|
203
211
|
}
|
|
204
212
|
|
|
213
|
+
if (key === 'dialog' && this._pointerHeld) {
|
|
214
|
+
// Items select on mousedown; a modal opened mid-press is light-dismissed by the pointerup landing on its backdrop
|
|
215
|
+
this._quill.container.ownerDocument.addEventListener('pointerup', () => {
|
|
216
|
+
setTimeout(() => this._toolbarModule.callFormat(key, value));
|
|
217
|
+
}, {once: true, capture: true});
|
|
218
|
+
this.hide();
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
205
222
|
this._toolbarModule.callFormat(key, value);
|
|
206
223
|
this._quill.focus();
|
|
207
224
|
this.hide();
|
|
@@ -54,13 +54,35 @@ export default class DialogComponent extends ZincElement {
|
|
|
54
54
|
private handleContentCloserClick = (e: Event) => {
|
|
55
55
|
for (const node of e.composedPath()) {
|
|
56
56
|
if (node === this) return;
|
|
57
|
-
if (node instanceof HTMLElement
|
|
57
|
+
if (!(node instanceof HTMLElement)) continue;
|
|
58
|
+
if (node.hasAttribute('dialog-closer')) {
|
|
58
59
|
this.dialogEl.close();
|
|
59
60
|
return;
|
|
60
61
|
}
|
|
62
|
+
const mode = node.getAttribute('editor-mode');
|
|
63
|
+
if (mode === 'insert' || mode === 'replace') {
|
|
64
|
+
this.insertContent(node, mode);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
61
67
|
}
|
|
62
68
|
};
|
|
63
69
|
|
|
70
|
+
/** Inserts the element named by the control's `editor-content-id`: a `<template>` as HTML, anything else as text.
|
|
71
|
+
* The control's form is submitted first, as inserting closes the dialog and detaches it. */
|
|
72
|
+
private insertContent(control: HTMLElement, mode: 'insert' | 'replace') {
|
|
73
|
+
const id = control.getAttribute('editor-content-id');
|
|
74
|
+
const source = id ? (control.getRootNode() as Document | ShadowRoot).getElementById(id) : null;
|
|
75
|
+
if (!source) return;
|
|
76
|
+
|
|
77
|
+
const content = source instanceof HTMLTemplateElement
|
|
78
|
+
? {html: source.innerHTML.trim()}
|
|
79
|
+
: {text: ((source as HTMLInputElement).value ?? source.textContent ?? '').trim()};
|
|
80
|
+
if (!content.html && !content.text) return;
|
|
81
|
+
|
|
82
|
+
control.closest('form')?.requestSubmit();
|
|
83
|
+
this.emit('zn-editor-insert', {detail: {mode, ...content}});
|
|
84
|
+
}
|
|
85
|
+
|
|
64
86
|
setContent(content: string) {
|
|
65
87
|
this.innerHTML = content;
|
|
66
88
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import '../../../../../dist/zn.min.js';
|
|
2
|
+
import {expect, fixture, html, oneEvent} from '@open-wc/testing';
|
|
3
|
+
import type DialogComponent from './dialog.component';
|
|
4
|
+
import type {ZnEditorInsertEvent} from '../events/zn-editor-insert';
|
|
5
|
+
|
|
6
|
+
describe('<zn-editor-dialog> content inserts', () => {
|
|
7
|
+
const dialog = () => fixture<DialogComponent>(html`
|
|
8
|
+
<zn-editor-dialog>
|
|
9
|
+
<template id="reply"><p>Hi <b>there</b></p></template>
|
|
10
|
+
<span id="plain">Plain reply</span>
|
|
11
|
+
<button id="html" editor-mode="replace" editor-content-id="reply">Replace</button>
|
|
12
|
+
<button id="text" editor-mode="insert" editor-content-id="plain">Insert</button>
|
|
13
|
+
</zn-editor-dialog>`);
|
|
14
|
+
|
|
15
|
+
it('inserts a template as HTML', async () => {
|
|
16
|
+
const el = await dialog();
|
|
17
|
+
setTimeout(() => el.querySelector<HTMLElement>('#html')!.click());
|
|
18
|
+
const event = await oneEvent(el, 'zn-editor-insert') as ZnEditorInsertEvent;
|
|
19
|
+
expect(event.detail).to.deep.equal({mode: 'replace', html: '<p>Hi <b>there</b></p>'});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('inserts any other element as text', async () => {
|
|
23
|
+
const el = await dialog();
|
|
24
|
+
setTimeout(() => el.querySelector<HTMLElement>('#text')!.click());
|
|
25
|
+
const event = await oneEvent(el, 'zn-editor-insert') as ZnEditorInsertEvent;
|
|
26
|
+
expect(event.detail).to.deep.equal({mode: 'insert', text: 'Plain reply'});
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -20,7 +20,7 @@ class Emoji {
|
|
|
20
20
|
const host = this.getHostEditor();
|
|
21
21
|
if (host) {
|
|
22
22
|
this._mo = new MutationObserver(() => this.initPicker());
|
|
23
|
-
this._mo.observe(host, {attributes: true, attributeFilter: ['t']});
|
|
23
|
+
this._mo.observe(host, {attributes: true, attributeFilter: ['t', 'm']});
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -45,11 +45,10 @@ class Emoji {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
// `m` is the rendered mode the editor mirrors from the page; older pages set `t="dark"` instead
|
|
48
49
|
private getTheme(): 'light' | 'dark' {
|
|
49
|
-
const host
|
|
50
|
-
|
|
51
|
-
const t = host?.t ?? host?.getAttribute?.('t');
|
|
52
|
-
return t === 'dark' ? 'dark' : 'light';
|
|
50
|
+
const host = this.getHostEditor();
|
|
51
|
+
return host?.getAttribute('m') === 'dark' || host?.getAttribute('t') === 'dark' ? 'dark' : 'light';
|
|
53
52
|
}
|
|
54
53
|
|
|
55
54
|
public initPicker() {
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import Delta from "quill-delta";
|
|
2
|
+
import Embed from "quill/blots/embed";
|
|
3
|
+
import Quill from "quill";
|
|
4
|
+
import type {Op} from "quill-delta";
|
|
5
|
+
|
|
6
|
+
export interface MergeFieldValue {
|
|
7
|
+
token: string;
|
|
8
|
+
label: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const TOKEN = /\{\{\s*[^{}]+?\s*\}\}/g;
|
|
12
|
+
|
|
13
|
+
/** A `{{token}}` replacement shown as a labelled chip, saved back out as the raw token. */
|
|
14
|
+
export class MergeFieldBlot extends Embed {
|
|
15
|
+
static blotName = 'mergeField';
|
|
16
|
+
static tagName = 'SPAN';
|
|
17
|
+
static className = 'ql-merge-field';
|
|
18
|
+
|
|
19
|
+
static create(value: MergeFieldValue): HTMLElement {
|
|
20
|
+
const node = super.create() as HTMLElement;
|
|
21
|
+
node.dataset.token = value.token;
|
|
22
|
+
node.dataset.label = value.label;
|
|
23
|
+
node.title = value.token;
|
|
24
|
+
node.textContent = value.label;
|
|
25
|
+
return node;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static value(node: HTMLElement): MergeFieldValue {
|
|
29
|
+
return {token: node.dataset.token ?? '', label: node.dataset.label ?? ''};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
html(): string {
|
|
33
|
+
return ((this.domNode as HTMLElement).dataset.token ?? '')
|
|
34
|
+
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const normaliseToken = (token: string) => token.replace(/\s+/g, '');
|
|
39
|
+
|
|
40
|
+
class MergeField {
|
|
41
|
+
constructor(quill: Quill, options: { labels?: Record<string, string> }) {
|
|
42
|
+
const labels = options.labels ?? {};
|
|
43
|
+
|
|
44
|
+
// The browser types a space after an embed into its caret guard and leaves the caret before it
|
|
45
|
+
quill.keyboard.addBinding({key: ' '}, (range: { index: number; length: number }) => {
|
|
46
|
+
if (range.length || !range.index) return true;
|
|
47
|
+
const before = quill.getContents(range.index - 1, 1).ops[0]?.insert;
|
|
48
|
+
if (typeof before !== 'object' || !('mergeField' in before)) return true;
|
|
49
|
+
|
|
50
|
+
quill.insertText(range.index, ' ', Quill.sources.USER);
|
|
51
|
+
quill.setSelection(range.index + 1, 0, Quill.sources.SILENT);
|
|
52
|
+
return false;
|
|
53
|
+
});
|
|
54
|
+
// Ahead of Quill's own space handlers, such as list autofill
|
|
55
|
+
const spaceBindings = (quill.keyboard as unknown as { bindings: Record<string, unknown[]> }).bindings[' '];
|
|
56
|
+
spaceBindings?.unshift(spaceBindings.pop());
|
|
57
|
+
|
|
58
|
+
// Text nodes only, so tokens inside attributes such as href stay as they are
|
|
59
|
+
quill.clipboard.addMatcher(Node.TEXT_NODE, (_node: Node, delta: Delta) => {
|
|
60
|
+
return new Delta(delta.ops.flatMap((op: Op) => {
|
|
61
|
+
if (typeof op.insert !== 'string' || !op.insert.includes('{{')) return [op];
|
|
62
|
+
|
|
63
|
+
const attrs = op.attributes ? {attributes: op.attributes} : {};
|
|
64
|
+
const ops: Op[] = [];
|
|
65
|
+
let last = 0;
|
|
66
|
+
for (const match of op.insert.matchAll(TOKEN)) {
|
|
67
|
+
if (match.index! > last) {
|
|
68
|
+
ops.push({insert: op.insert.slice(last, match.index), ...attrs});
|
|
69
|
+
}
|
|
70
|
+
const token = normaliseToken(match[0]);
|
|
71
|
+
ops.push({insert: {mergeField: {token, label: labels[token] ?? token.slice(2, -2)}}, ...attrs});
|
|
72
|
+
last = match.index! + match[0].length;
|
|
73
|
+
}
|
|
74
|
+
if (last < op.insert.length) {
|
|
75
|
+
ops.push({insert: op.insert.slice(last), ...attrs});
|
|
76
|
+
}
|
|
77
|
+
return ops;
|
|
78
|
+
}));
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export default MergeField;
|