@globalpayments/vega 2.91.0 → 2.93.0
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/cjs/{app-globals-7bb10088.js → app-globals-f7ad9275.js} +17 -10
- package/dist/cjs/{child-nodes-event-prevent-slimmer-76d19d56.js → child-nodes-event-prevent-slimmer-e67ee84c.js} +2 -2
- package/dist/cjs/{code-block-c1039791.js → code-block-2f3d0f38.js} +64 -11
- package/dist/cjs/{component-value-history-controller-slimmer.abstract-1d36641d.js → component-value-history-controller-slimmer.abstract-31d2c7aa.js} +72 -39
- package/dist/cjs/{content-state-a6105949.js → content-state-13daf87a.js} +91 -8
- package/dist/cjs/copy-d021c780.js +7 -0
- package/dist/cjs/{date-required-rule-c4997459.js → date-required-rule-e65ddd7d.js} +1 -1
- package/dist/cjs/{design-token-f65eab4a.js → design-token-47393cbb.js} +3 -3
- package/dist/cjs/{element-appender-slimmer-316668d0.js → element-appender-slimmer-29c4ac60.js} +18 -9
- package/dist/cjs/{event-emit-slimmer-5bab10c2.js → event-emit-slimmer-d234a619.js} +1 -1
- package/dist/cjs/{file-uploader-required-rule-cc51d4ab.js → file-uploader-required-rule-5520bbec.js} +1 -1
- package/dist/cjs/{form-field-controller-slimmer-b0c35330.js → form-field-controller-slimmer-3bfcf88c.js} +1 -1
- package/dist/cjs/{image-annotation-action-e4596726.js → image-annotation-action-c2b163bb.js} +3 -3
- package/dist/cjs/image-extension-4e99b620.js +39 -0
- package/dist/cjs/index-58ea899e.js +4 -0
- package/dist/cjs/index.cjs.js +30 -22
- package/dist/cjs/{inject-keyboard-manager-1f5bbb19.js → inject-keyboard-manager-4b2ebbe4.js} +1 -1
- package/dist/cjs/{internal-calendar-period-factory-9c66cf36.js → internal-calendar-period-factory-7a0ecdb1.js} +1 -1
- package/dist/cjs/{internal-translation-controller-5905469e.js → internal-translation-controller-31f2b057.js} +18 -0
- package/dist/cjs/{internal-vega-event-manager-7196a847.js → internal-vega-event-manager-13c4012b.js} +2 -1
- package/dist/cjs/{keyboard-manager-61e8ce38.js → keyboard-manager-c78b46bd.js} +1 -1
- package/dist/cjs/{keyboard-manager-slimmer-079a7e87.js → keyboard-manager-slimmer-d350c931.js} +1 -1
- package/dist/cjs/{link-extension-a7ac28f3.js → link-extension-455c02b3.js} +3 -3
- package/dist/cjs/loader.cjs.js +17 -15
- package/dist/cjs/{month-view-generator-48215a05.js → month-view-generator-006562dc.js} +1 -1
- package/dist/cjs/{public-rules-66540638.js → public-rules-c59eba88.js} +10 -10
- package/dist/cjs/{range-dea19a90.js → range-bf80fa40.js} +25 -3
- package/dist/cjs/{replace-selected-text-action-6d1ebbf9.js → replace-selected-text-action-a80f5a6a.js} +1 -1
- package/dist/cjs/{required-field-rule-b12a29e1.js → required-field-rule-400690c3.js} +1 -1
- package/dist/cjs/{responsive-format-facade-10ff730e.js → responsive-format-facade-1af54330.js} +2 -2
- package/dist/cjs/{rich-text-editor-required-rule-63492ca3.js → rich-text-editor-required-rule-1085c71a.js} +1 -1
- package/dist/cjs/{split-cell-operation-30e5dd4a.js → split-cell-operation-2fa5c9e4.js} +3 -3
- package/dist/cjs/{state-border-formatter-e7779964.js → state-border-formatter-501f6645.js} +1 -1
- package/dist/cjs/{string-format-strategy.abstract-a22f0cb1.js → string-format-strategy.abstract-a02bf002.js} +1 -1
- package/dist/cjs/{string-input-formatter-slimmer-8179eb40.js → string-input-formatter-slimmer-9cca6151.js} +1 -1
- package/dist/cjs/{string-mask-strategy-8b9bb0c1.js → string-mask-strategy-4c174ebf.js} +2 -2
- package/dist/cjs/{style-formatter-380b9a86.js → style-formatter-2c749500.js} +2 -2
- package/dist/cjs/{time-required-rule-080d975b.js → time-required-rule-04994a5e.js} +1 -1
- package/dist/cjs/{token-extension-619379a3.js → token-extension-d217914d.js} +3622 -742
- package/dist/cjs/{translation-slimmer-2af6f141.js → translation-slimmer-7fb6e94c.js} +1 -1
- package/dist/cjs/{type-guard-0171e779.js → type-guard-1c3770d4.js} +1 -1
- package/dist/cjs/{valid-credit-card-number-rule-fdfca83e.js → valid-credit-card-number-rule-f719a8c6.js} +1 -1
- package/dist/cjs/vega-accordion.cjs.entry.js +21 -13
- package/dist/cjs/vega-app-footer.cjs.entry.js +3 -3
- package/dist/cjs/vega-app-header-button.cjs.entry.js +10 -10
- package/dist/cjs/vega-banner.cjs.entry.js +6 -9
- package/dist/cjs/vega-box.cjs.entry.js +7 -7
- package/dist/cjs/vega-breadcrumb.cjs.entry.js +5 -5
- package/dist/cjs/vega-button-circle.cjs.entry.js +10 -10
- package/dist/cjs/vega-button-group_2.cjs.entry.js +5 -5
- package/dist/cjs/vega-button-link.cjs.entry.js +5 -5
- package/dist/cjs/vega-button.cjs.entry.js +8 -8
- package/dist/cjs/vega-calendar_5.cjs.entry.js +12 -12
- package/dist/cjs/vega-card.cjs.entry.js +6 -6
- package/dist/cjs/vega-carousel.cjs.entry.js +7 -7
- package/dist/cjs/vega-checkbox_2.cjs.entry.js +8 -8
- package/dist/cjs/vega-chip.cjs.entry.js +10 -10
- package/dist/cjs/vega-code-block.cjs.entry.js +15 -18
- package/dist/cjs/vega-color-picker.cjs.entry.js +7 -7
- package/dist/cjs/vega-color-swatch-picker.cjs.entry.js +9 -9
- package/dist/cjs/vega-color-swatch.cjs.entry.js +5 -5
- package/dist/cjs/vega-combo-box.cjs.entry.js +21 -15
- package/dist/cjs/vega-date-picker_2.cjs.entry.js +18 -18
- package/dist/cjs/vega-dialog_2.cjs.entry.js +21 -10
- package/dist/cjs/vega-divider.cjs.entry.js +6 -6
- package/dist/cjs/vega-dropdown_5.cjs.entry.js +694 -196
- package/dist/cjs/vega-env-manager-23b8b23c.js +2 -2
- package/dist/cjs/{vega-event-id-df22d80e.js → vega-event-id-58afed7a.js} +2 -0
- package/dist/cjs/vega-field-label.cjs.entry.js +16 -12
- package/dist/cjs/vega-file-uploader.cjs.entry.js +9 -9
- package/dist/cjs/vega-flag-icon.cjs.entry.js +6 -6
- package/dist/cjs/vega-flex.cjs.entry.js +7 -7
- package/dist/cjs/vega-font.cjs.entry.js +6 -6
- package/dist/cjs/vega-form.cjs.entry.js +8 -8
- package/dist/cjs/vega-grid.cjs.entry.js +6 -6
- package/dist/cjs/vega-icon.cjs.entry.js +6 -6
- package/dist/cjs/vega-image-uploader.cjs.entry.js +11 -11
- package/dist/cjs/vega-input-credit-card.cjs.entry.js +12 -12
- package/dist/cjs/vega-input-numeric.cjs.entry.js +11 -11
- package/dist/cjs/vega-input-passcode.cjs.entry.js +11 -11
- package/dist/cjs/vega-input-phone-number.cjs.entry.js +9 -9
- package/dist/cjs/vega-input-range.cjs.entry.js +8 -8
- package/dist/cjs/vega-input-select.cjs.entry.js +11 -11
- package/dist/cjs/vega-input.cjs.entry.js +12 -12
- package/dist/cjs/vega-item-toggle.cjs.entry.js +3 -3
- package/dist/cjs/vega-left-nav_5.cjs.entry.js +11 -11
- package/dist/cjs/vega-loader-wrapper_2.cjs.entry.js +4 -4
- package/dist/cjs/vega-page-notification_2.cjs.entry.js +1 -1
- package/dist/cjs/vega-pagination-page-selector-mobile.cjs.entry.js +4 -4
- package/dist/cjs/vega-pagination-page-size-selector-mobile.cjs.entry.js +3 -3
- package/dist/cjs/vega-pagination.cjs.entry.js +9 -9
- package/dist/cjs/vega-popover_2.cjs.entry.js +12 -12
- package/dist/cjs/vega-progress-tracker_2.cjs.entry.js +7 -7
- package/dist/cjs/vega-radio_2.cjs.entry.js +11 -11
- package/dist/cjs/vega-rich-text-content.cjs.entry.js +7 -7
- package/dist/cjs/vega-rich-text-editor_4.cjs.entry.js +677 -95
- package/dist/cjs/vega-rich-text-find-replace-panel.cjs.entry.js +233 -0
- package/dist/cjs/vega-rich-text-special-characters-panel.cjs.entry.js +8 -8
- package/dist/cjs/vega-rich-text-table-properties_3.cjs.entry.js +13 -13
- package/dist/cjs/vega-segment-control.cjs.entry.js +3 -3
- package/dist/cjs/vega-selection-chip_2.cjs.entry.js +10 -10
- package/dist/cjs/vega-selection-tile_2.cjs.entry.js +8 -8
- package/dist/cjs/vega-sidenav_3.cjs.entry.js +7 -7
- package/dist/cjs/vega-signature-capture.cjs.entry.js +12 -12
- package/dist/cjs/vega-stepper.cjs.entry.js +7 -7
- package/dist/cjs/vega-tab-group_2.cjs.entry.js +5 -5
- package/dist/cjs/vega-table_11.cjs.entry.js +912 -429
- package/dist/cjs/vega-textarea.cjs.entry.js +9 -11
- package/dist/cjs/vega-time-picker_2.cjs.entry.js +35 -15
- package/dist/cjs/vega-toggle-switch.cjs.entry.js +5 -5
- package/dist/cjs/vega-tooltip_2.cjs.entry.js +7 -7
- package/dist/cjs/vega.cjs.js +17 -15
- package/dist/cjs/xmark-large-faae5a80.js +7 -0
- package/dist/collection/collection-manifest.json +6 -0
- package/dist/collection/components/vega-accordion/slimmers/vega-accordion-renderer.js +11 -3
- package/dist/collection/components/vega-box/vega-box.js +3 -3
- package/dist/collection/components/vega-code-block/slimmers/renderers/vega-code-block-copy-button-renderer.js +1 -1
- package/dist/collection/components/vega-combo-box/slimmers/renderers/vega-combo-box-dropdown-renderer.js +4 -1
- package/dist/collection/components/vega-combo-box/vega-combo-box.js +7 -1
- package/dist/collection/components/vega-dialog/vega-dialog.js +1 -1
- package/dist/collection/components/vega-dropdown/constants.js +1 -0
- package/dist/collection/components/vega-dropdown/slimmers/controllers/vega-dropdown-appender-controller.js +9 -0
- package/dist/collection/components/vega-dropdown/slimmers/controllers/vega-dropdown-children-controller.js +97 -4
- package/dist/collection/components/vega-dropdown/slimmers/controllers/vega-dropdown-select-all-controller.js +219 -0
- package/dist/collection/components/vega-dropdown/slimmers/controllers/vega-dropdown-selected-controller.js +78 -13
- package/dist/collection/components/vega-dropdown/slimmers/controllers/vega-dropdown-source-controller.js +13 -1
- package/dist/collection/components/vega-dropdown/slimmers/renderers/vega-dropdown-list-renderer.js +7 -0
- package/dist/collection/components/vega-dropdown/vega-dropdown-item/slimmers/controllers/vega-dropdown-item-state-controller.js +52 -1
- package/dist/collection/components/vega-dropdown/vega-dropdown-item/slimmers/renderers/vega-dropdown-item-renderer.js +4 -1
- package/dist/collection/components/vega-dropdown/vega-dropdown-item/vega-dropdown-item.js +5 -0
- package/dist/collection/components/vega-dropdown/vega-dropdown.js +126 -1
- package/dist/collection/components/vega-field-label/slimmers/renderers/vega-field-label-suffix-element-renderer.js +5 -4
- package/dist/collection/components/vega-field-label/vega-field-label.css +20 -0
- package/dist/collection/components/vega-field-label/vega-field-label.js +7 -4
- package/dist/collection/components/vega-hint/vega-hint.js +1 -1
- package/dist/collection/components/vega-input/vega-input.css +1 -0
- package/dist/collection/components/vega-input/vega-input.js +1 -1
- package/dist/collection/components/vega-input-select/vega-input-select.js +1 -1
- package/dist/collection/components/vega-modal/slimmers/renderers/vega-modal-renderer.js +4 -1
- package/dist/collection/components/vega-modal/vega-modal.css +0 -1
- package/dist/collection/components/vega-modal/vega-modal.js +38 -0
- package/dist/collection/components/vega-rich-text-editor/assets/cut.js +3 -0
- package/dist/collection/components/vega-rich-text-editor/assets/redo.js +1 -1
- package/dist/collection/components/vega-rich-text-editor/assets/undo.js +1 -1
- package/dist/collection/components/vega-rich-text-editor/dto/action-handle-strategies/modify-content-strategies/block-delete-text-or-decorator-node-strategy.js +16 -1
- package/dist/collection/components/vega-rich-text-editor/dto/action-handle-strategies/modify-content-strategies/block-insert-line-break-strategy.js +11 -5
- package/dist/collection/components/vega-rich-text-editor/dto/action-handle-strategies/modify-content-strategies/block-merge-nodes-strategy.js +25 -0
- package/dist/collection/components/vega-rich-text-editor/dto/action-handle-strategies/modify-content-strategies/block-split-with-text-node-strategy.js +9 -4
- package/dist/collection/components/vega-rich-text-editor/dto/action-handle-strategies/modify-content-strategies/delete-selected-content-strategy.js +16 -0
- package/dist/collection/components/vega-rich-text-editor/dto/action-handle-strategies/modify-content-strategies/paste-content-strategy.js +61 -5
- package/dist/collection/components/vega-rich-text-editor/dto/actions/delete-selected-content-action.js +17 -0
- package/dist/collection/components/vega-rich-text-editor/dto/actions/modify-content-action.abstract.js +1 -0
- package/dist/collection/components/vega-rich-text-editor/dto/actions/paste-content-action.js +1 -0
- package/dist/collection/components/vega-rich-text-editor/dto/content-state.js +2 -0
- package/dist/collection/components/vega-rich-text-editor/dto/nodes/text-node.js +6 -1
- package/dist/collection/components/vega-rich-text-editor/dto/range.js +24 -2
- package/dist/collection/components/vega-rich-text-editor/extensions/blockquote/action-handler-interceptors/blockquote-exit-on-enter-interceptor.js +61 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/blockquote/action-handler-strategies/block-transform-to-blockquote-strategy.js +40 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/blockquote/action-handler-strategies/remove-blockquote-strategy.js +20 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/blockquote/actions/remove-blockquote-action.js +17 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/blockquote/actions/transform-to-blockquote-action.js +19 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/blockquote/assets/quote-left.js +4 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/blockquote/blockquote-extension.js +47 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/blockquote/dto/blockquote-annotation.js +76 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/blockquote/dto/rte-blockquote-block.js +70 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/blockquote/element-to-dto-strategies/blockquote-to-blockquote-block-strategy.js +53 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/blockquote/filter-styles-strategies/blockquote-block/blockquote-filter-styles-strategy.js +41 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/blockquote/renderers/blockquote-block-renderer.js +21 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/blockquote/renderers/blockquote-toolbar-button-renderer.js +260 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/copy/action-handler-strategies/copy-selected-content-strategy.js +226 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/copy/actions/copy-selected-content-action.js +21 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/copy/copy-extension.js +20 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/copy/copy-toolbar-button-renderer.js +58 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/cut/cut-extension.js +15 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/cut/cut-toolbar-button-renderer.js +44 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/find-replace/assets/find-and-replace.js +3 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/find-replace/find-replace-extension.js +18 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/find-replace/slimmer/controllers/find-controller.js +105 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/find-replace/slimmer/controllers/find-replace-controller.abstract.js +68 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/find-replace/slimmer/controllers/helper/block-search-strategies/block-search-strategy.abstract.js +8 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/find-replace/slimmer/controllers/helper/block-search-strategies/code-block-search-strategy.js +19 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/find-replace/slimmer/controllers/helper/block-search-strategies/container-block-search-strategy.js +23 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/find-replace/slimmer/controllers/helper/block-search-strategies/list-item-block-search-strategy.js +30 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/find-replace/slimmer/controllers/helper/block-search-strategies/text-block-search-strategy.js +20 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/find-replace/slimmer/controllers/helper/find-replace-highlight-manager.js +266 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/find-replace/slimmer/controllers/helper/find-replace-state.js +346 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/find-replace/slimmer/controllers/replace-controller.js +113 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/find-replace/slimmer/renderers/find-replace-toolbar-button-renderer.js +194 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/find-replace/vega-rich-text-find-replace-panel/slimmers/renderers/find-replace-panel-renderer.js +153 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/find-replace/vega-rich-text-find-replace-panel/vega-rich-text-find-replace-panel.css +50 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/find-replace/vega-rich-text-find-replace-panel/vega-rich-text-find-replace-panel.js +156 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/functions/function-extension.js +2 -1
- package/dist/collection/components/vega-rich-text-editor/extensions/horizontal-line/horizontal-line-extension.js +2 -1
- package/dist/collection/components/vega-rich-text-editor/extensions/image/image-extension.js +34 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/image/type.js +1 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/languages/language-extension.js +2 -1
- package/dist/collection/components/vega-rich-text-editor/extensions/line-height/line-height-extension.js +2 -1
- package/dist/collection/components/vega-rich-text-editor/extensions/paste/assets/paste.js +3 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/paste/paste-chooser-ui.js +144 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/paste/paste-constants.js +6 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/paste/paste-extension.js +442 -0
- package/dist/collection/components/vega-rich-text-editor/extensions/special-characters/special-characters-extension.js +2 -1
- package/dist/collection/components/vega-rich-text-editor/extensions/table/renderers/table-renderer.js +1 -1
- package/dist/collection/components/vega-rich-text-editor/extensions/table/table-extension.js +6 -4
- package/dist/collection/components/vega-rich-text-editor/extensions/table/vega-rich-text-table-selection-widget/slimmers/controllers/selection-widget-keyboard-controller.js +1 -1
- package/dist/collection/components/vega-rich-text-editor/extensions/tokens/token-extension.js +2 -1
- package/dist/collection/components/vega-rich-text-editor/helpers/utils.js +14 -0
- package/dist/collection/components/vega-rich-text-editor/public-api.js +7 -0
- package/dist/collection/components/vega-rich-text-editor/slimmers/controllers/helper/input-event-handler/abstract-delete-content-handler.js +133 -0
- package/dist/collection/components/vega-rich-text-editor/slimmers/controllers/helper/input-event-handler/backward-delete-content-handler.js +35 -0
- package/dist/collection/components/vega-rich-text-editor/slimmers/controllers/helper/input-event-handler/base-handler.js +1 -1
- package/dist/collection/components/vega-rich-text-editor/slimmers/controllers/helper/input-event-handler/forward-delete-content-handler.js +93 -0
- package/dist/collection/components/vega-rich-text-editor/slimmers/controllers/helper/input-event-handler/insert-line-break-handler.js +1 -1
- package/dist/collection/components/vega-rich-text-editor/slimmers/controllers/helper/input-event-handler/insert-paste-handler.js +385 -25
- package/dist/collection/components/vega-rich-text-editor/slimmers/controllers/selection-controller.js +69 -36
- package/dist/collection/components/vega-rich-text-editor/slimmers/controllers/user-input-controller.js +9 -3
- package/dist/collection/components/vega-rich-text-editor/slimmers/renderers/tools/image-toolbar-button-slimmer.js +59 -14
- package/dist/collection/components/vega-rich-text-editor/vega-rich-text-editor-toolbar-button/slimmers/renderers/vega-rich-text-editor-toolbar-button-renderer.js +6 -1
- package/dist/collection/components/vega-rich-text-editor/vega-rich-text-editor-toolbar-button/vega-rich-text-editor-toolbar-button.js +28 -1
- package/dist/collection/components/vega-rich-text-editor/vega-rich-text-editor.js +1 -1
- package/dist/collection/components/vega-rich-text-editor/vega-rich-text-image-editor/vega-rich-text-image-editor.js +1 -1
- package/dist/collection/components/vega-rich-text-editor/vega-rich-text-link-editor/vega-rich-text-link-editor.js +1 -1
- package/dist/collection/components/vega-table/slimmers/controllers/dto/column-render-input-model.js +32 -0
- package/dist/collection/components/vega-table/slimmers/controllers/vega-table-body-col-span-controller.js +77 -0
- package/dist/collection/components/vega-table/slimmers/renderers/vega-table-body-col-span-resolver.js +121 -0
- package/dist/collection/components/vega-table/slimmers/renderers/vega-table-cell-renderer.js +39 -14
- package/dist/collection/components/vega-table/slimmers/renderers/vega-table-pinned-renderer.js +207 -7
- package/dist/collection/components/vega-table/slimmers/renderers/vega-table-row-renderer.js +16 -3
- package/dist/collection/components/vega-table/vega-table-cell/vega-table-cell.js +24 -4
- package/dist/collection/components/vega-table/vega-table-expand-row/slimmers/controllers/vega-table-expand-row-col-span-controller.js +11 -2
- package/dist/collection/components/vega-table/vega-table-foot-cell/slimmers/renderers/vega-table-foot-cell-renderer.js +5 -1
- package/dist/collection/components/vega-table/vega-table-foot-cell/vega-table-foot-cell.js +22 -1
- package/dist/collection/components/vega-table/vega-table.css +5 -0
- package/dist/collection/components/vega-table/vega-table.js +5 -0
- package/dist/collection/components/vega-text/vega-text.js +1 -1
- package/dist/collection/components/vega-textarea/vega-textarea.js +2 -4
- package/dist/collection/components/vega-time-picker/slimmers/controllers/vega-time-picker-range-dropdown-controller.js +4 -0
- package/dist/collection/components/vega-time-picker/slimmers/controllers/vega-time-picker-single-dropdown-controller.js +4 -0
- package/dist/collection/components/vega-time-picker/vega-time-picker.js +39 -0
- package/dist/collection/constants/ui.js +1 -0
- package/dist/collection/global/scripts/before-vega-load.js +12 -0
- package/dist/collection/helpers/event-manager/event-id/component-event-id-map.js +2 -1
- package/dist/collection/helpers/event-manager/event-id/vega-event-id.js +1 -0
- package/dist/collection/helpers/slimmers/element-appender/element-appender-slimmer.js +11 -4
- package/dist/collection/helpers/translation/locales/en.js +18 -0
- package/dist/collection/helpers/ui/element-appender.js +5 -2
- package/dist/esm/{app-globals-6041f52f.js → app-globals-9950d055.js} +17 -10
- package/dist/esm/{aria-dialog-focusable-trap-slimmer-1332c071.js → aria-dialog-focusable-trap-slimmer-9eae86c6.js} +1 -1
- package/dist/esm/{child-nodes-event-prevent-slimmer-50022584.js → child-nodes-event-prevent-slimmer-96c14f5a.js} +2 -2
- package/dist/esm/{code-block-37294847.js → code-block-ae2337c2.js} +65 -13
- package/dist/esm/{component-value-history-controller-slimmer.abstract-45ccddc9.js → component-value-history-controller-slimmer.abstract-b1ee1b8e.js} +73 -40
- package/dist/esm/{content-state-67950b80.js → content-state-c3b85ae6.js} +92 -9
- package/dist/esm/copy-54e45e2d.js +5 -0
- package/dist/esm/{date-required-rule-958cf380.js → date-required-rule-e06115f8.js} +1 -1
- package/dist/esm/{design-token-e7e78f18.js → design-token-4e682056.js} +3 -3
- package/dist/esm/{element-appender-slimmer-ebda526d.js → element-appender-slimmer-a4485ef6.js} +19 -10
- package/dist/esm/{event-emit-slimmer-b94cec83.js → event-emit-slimmer-db4a82e0.js} +1 -1
- package/dist/esm/{file-uploader-required-rule-82188a61.js → file-uploader-required-rule-96952e1a.js} +1 -1
- package/dist/esm/{form-field-controller-slimmer-0a2ba886.js → form-field-controller-slimmer-a1c7b28c.js} +3 -3
- package/dist/esm/{image-annotation-action-7f7a5678.js → image-annotation-action-4c77d314.js} +3 -3
- package/dist/esm/image-extension-7108c27d.js +37 -0
- package/dist/esm/index-090d31ca.js +4 -0
- package/dist/esm/index.js +24 -23
- package/dist/esm/{inject-keyboard-manager-2b19c121.js → inject-keyboard-manager-e8ddb78d.js} +1 -1
- package/dist/esm/{internal-calendar-period-factory-fff8aaee.js → internal-calendar-period-factory-6f19d767.js} +1 -1
- package/dist/esm/{internal-translation-controller-aa09ca87.js → internal-translation-controller-09b8bd13.js} +18 -0
- package/dist/esm/{internal-vega-event-manager-956cf128.js → internal-vega-event-manager-48690e5b.js} +2 -1
- package/dist/esm/{keyboard-manager-3e5b157f.js → keyboard-manager-ec18721b.js} +1 -1
- package/dist/esm/{keyboard-manager-slimmer-ba5cc767.js → keyboard-manager-slimmer-531ddb0e.js} +1 -1
- package/dist/esm/{link-extension-afb404fb.js → link-extension-74eaa826.js} +3 -3
- package/dist/esm/loader.js +18 -16
- package/dist/esm/{month-view-generator-7a0c869c.js → month-view-generator-afd78ff8.js} +1 -1
- package/dist/esm/{public-rules-f64b0b78.js → public-rules-5fce32a5.js} +11 -11
- package/dist/esm/{range-4d09a305.js → range-8f877dbf.js} +25 -3
- package/dist/esm/{replace-selected-text-action-12bc8b82.js → replace-selected-text-action-c9278a0f.js} +1 -1
- package/dist/esm/{required-field-rule-a4634842.js → required-field-rule-897fe8d1.js} +1 -1
- package/dist/esm/{responsive-format-facade-844c5e09.js → responsive-format-facade-299e6304.js} +2 -2
- package/dist/esm/{rich-text-editor-required-rule-d62d418e.js → rich-text-editor-required-rule-3047bab0.js} +1 -1
- package/dist/esm/{split-cell-operation-c515669e.js → split-cell-operation-98d58410.js} +3 -3
- package/dist/esm/{state-border-formatter-f53847a5.js → state-border-formatter-da52b626.js} +1 -1
- package/dist/esm/{string-format-strategy.abstract-a0327405.js → string-format-strategy.abstract-a45b5ff7.js} +1 -1
- package/dist/esm/{string-input-formatter-slimmer-12de4a25.js → string-input-formatter-slimmer-4a032fed.js} +3 -3
- package/dist/esm/{string-mask-strategy-3bb03155.js → string-mask-strategy-37e53e48.js} +2 -2
- package/dist/esm/{style-formatter-df7bf8ec.js → style-formatter-3d5ddfb1.js} +2 -2
- package/dist/esm/{sub-state-notify-slimmer-7ef94141.js → sub-state-notify-slimmer-0f72ce74.js} +1 -1
- package/dist/esm/{sub-state-observer-slimmer-d9ad2bdd.js → sub-state-observer-slimmer-64532d94.js} +1 -1
- package/dist/esm/{time-required-rule-db0dee53.js → time-required-rule-355b73d2.js} +1 -1
- package/dist/esm/{token-extension-8ff92d14.js → token-extension-ecfafe94.js} +3618 -745
- package/dist/esm/{translation-slimmer-4904e7fe.js → translation-slimmer-3ea56354.js} +1 -1
- package/dist/esm/{type-guard-a2cbaae9.js → type-guard-2bfb2c7b.js} +1 -1
- package/dist/esm/{valid-credit-card-number-rule-a3f9858d.js → valid-credit-card-number-rule-52c6b029.js} +1 -1
- package/dist/esm/vega-accordion.entry.js +21 -13
- package/dist/esm/vega-app-footer.entry.js +3 -3
- package/dist/esm/vega-app-header-button.entry.js +10 -10
- package/dist/esm/vega-banner.entry.js +5 -8
- package/dist/esm/vega-box.entry.js +7 -7
- package/dist/esm/vega-breadcrumb.entry.js +5 -5
- package/dist/esm/vega-button-circle.entry.js +11 -11
- package/dist/esm/vega-button-group_2.entry.js +8 -8
- package/dist/esm/vega-button-link.entry.js +5 -5
- package/dist/esm/vega-button.entry.js +9 -9
- package/dist/esm/vega-calendar_5.entry.js +15 -15
- package/dist/esm/vega-card.entry.js +6 -6
- package/dist/esm/vega-carousel.entry.js +7 -7
- package/dist/esm/vega-checkbox_2.entry.js +10 -10
- package/dist/esm/vega-chip.entry.js +10 -10
- package/dist/esm/vega-code-block.entry.js +15 -18
- package/dist/esm/vega-color-picker.entry.js +9 -9
- package/dist/esm/vega-color-swatch-picker.entry.js +11 -11
- package/dist/esm/vega-color-swatch.entry.js +5 -5
- package/dist/esm/vega-combo-box.entry.js +23 -17
- package/dist/esm/vega-date-picker_2.entry.js +22 -22
- package/dist/esm/vega-dialog_2.entry.js +23 -12
- package/dist/esm/vega-divider.entry.js +6 -6
- package/dist/esm/vega-dropdown_5.entry.js +700 -202
- package/dist/esm/vega-env-manager-8f8dc473.js +2 -2
- package/dist/esm/{vega-event-id-268f8def.js → vega-event-id-270fe47e.js} +2 -1
- package/dist/esm/vega-field-label.entry.js +16 -12
- package/dist/esm/vega-file-uploader.entry.js +11 -11
- package/dist/esm/vega-flag-icon.entry.js +6 -6
- package/dist/esm/vega-flex.entry.js +7 -7
- package/dist/esm/vega-font.entry.js +6 -6
- package/dist/esm/vega-form.entry.js +11 -11
- package/dist/esm/vega-grid.entry.js +6 -6
- package/dist/esm/vega-icon.entry.js +6 -6
- package/dist/esm/vega-image-uploader.entry.js +13 -13
- package/dist/esm/vega-input-credit-card.entry.js +14 -14
- package/dist/esm/vega-input-numeric.entry.js +14 -14
- package/dist/esm/vega-input-passcode.entry.js +13 -13
- package/dist/esm/vega-input-phone-number.entry.js +11 -11
- package/dist/esm/vega-input-range.entry.js +10 -10
- package/dist/esm/vega-input-select.entry.js +13 -13
- package/dist/esm/vega-input.entry.js +15 -15
- package/dist/esm/{vega-internal-event-id-22151a4d.js → vega-internal-event-id-827eb1ce.js} +1 -1
- package/dist/esm/vega-item-toggle.entry.js +3 -3
- package/dist/esm/vega-left-nav_5.entry.js +14 -14
- package/dist/esm/vega-loader-wrapper_2.entry.js +4 -4
- package/dist/esm/vega-page-notification_2.entry.js +1 -1
- package/dist/esm/vega-pagination-page-selector-mobile.entry.js +5 -5
- package/dist/esm/vega-pagination-page-size-selector-mobile.entry.js +4 -4
- package/dist/esm/vega-pagination.entry.js +10 -10
- package/dist/esm/vega-popover_2.entry.js +14 -14
- package/dist/esm/vega-progress-tracker_2.entry.js +10 -10
- package/dist/esm/vega-radio_2.entry.js +13 -13
- package/dist/esm/vega-rich-text-content.entry.js +10 -10
- package/dist/esm/vega-rich-text-editor_4.entry.js +681 -99
- package/dist/esm/vega-rich-text-find-replace-panel.entry.js +229 -0
- package/dist/esm/vega-rich-text-special-characters-panel.entry.js +8 -8
- package/dist/esm/vega-rich-text-table-properties_3.entry.js +14 -14
- package/dist/esm/vega-segment-control.entry.js +3 -3
- package/dist/esm/vega-selection-chip_2.entry.js +13 -13
- package/dist/esm/vega-selection-tile_2.entry.js +11 -11
- package/dist/esm/vega-sidenav_3.entry.js +10 -10
- package/dist/esm/vega-signature-capture.entry.js +14 -14
- package/dist/esm/vega-stepper.entry.js +9 -9
- package/dist/esm/vega-tab-group_2.entry.js +5 -5
- package/dist/esm/vega-table_11.entry.js +915 -432
- package/dist/esm/vega-textarea.entry.js +11 -13
- package/dist/esm/vega-time-picker_2.entry.js +37 -17
- package/dist/esm/vega-toggle-switch.entry.js +7 -7
- package/dist/esm/vega-tooltip_2.entry.js +8 -8
- package/dist/esm/vega.js +18 -16
- package/dist/esm/{wait-for-component-did-render-0488922a.js → wait-for-component-did-render-0c237cb8.js} +1 -1
- package/dist/esm/xmark-large-c5ae7944.js +5 -0
- package/dist/sri/vega-sri-manifest.json +392 -376
- package/dist/types/components/vega-accordion/slimmers/vega-accordion-renderer.d.ts +2 -0
- package/dist/types/components/vega-combo-box/types.d.ts +6 -1
- package/dist/types/components/vega-combo-box/vega-combo-box.d.ts +1 -0
- package/dist/types/components/vega-dropdown/constants.d.ts +1 -0
- package/dist/types/components/vega-dropdown/slimmers/controllers/vega-dropdown-appender-controller.d.ts +2 -0
- package/dist/types/components/vega-dropdown/slimmers/controllers/vega-dropdown-children-controller.d.ts +36 -0
- package/dist/types/components/vega-dropdown/slimmers/controllers/vega-dropdown-select-all-controller.d.ts +107 -0
- package/dist/types/components/vega-dropdown/slimmers/controllers/vega-dropdown-selected-controller.d.ts +17 -0
- package/dist/types/components/vega-dropdown/slimmers/controllers/vega-dropdown-source-controller.d.ts +5 -0
- package/dist/types/components/vega-dropdown/slimmers/renderers/vega-dropdown-list-renderer.d.ts +2 -0
- package/dist/types/components/vega-dropdown/types.d.ts +8 -0
- package/dist/types/components/vega-dropdown/vega-dropdown-item/slimmers/controllers/vega-dropdown-item-state-controller.d.ts +25 -0
- package/dist/types/components/vega-dropdown/vega-dropdown-item/slimmers/renderers/vega-dropdown-item-renderer.d.ts +1 -0
- package/dist/types/components/vega-dropdown/vega-dropdown-item/vega-dropdown-item.d.ts +4 -0
- package/dist/types/components/vega-dropdown/vega-dropdown.d.ts +48 -0
- package/dist/types/components/vega-field-label/slimmers/renderers/vega-field-label-suffix-element-renderer.d.ts +1 -0
- package/dist/types/components/vega-field-label/types.d.ts +12 -0
- package/dist/types/components/vega-modal/slimmers/renderers/vega-modal-renderer.d.ts +2 -1
- package/dist/types/components/vega-modal/vega-modal.d.ts +9 -1
- package/dist/types/components/vega-rich-text-editor/dto/action-handle-strategies/modify-content-strategies/block-merge-nodes-strategy.d.ts +9 -0
- package/dist/types/components/vega-rich-text-editor/dto/action-handle-strategies/modify-content-strategies/delete-selected-content-strategy.d.ts +13 -0
- package/dist/types/components/vega-rich-text-editor/dto/action-handle-strategies/modify-content-strategies/paste-content-strategy.d.ts +14 -4
- package/dist/types/components/vega-rich-text-editor/dto/actions/delete-selected-content-action.d.ts +12 -0
- package/dist/types/components/vega-rich-text-editor/dto/actions/modify-content-action.abstract.d.ts +2 -1
- package/dist/types/components/vega-rich-text-editor/dto/actions/paste-content-action.d.ts +7 -0
- package/dist/types/components/vega-rich-text-editor/dto/range.d.ts +16 -0
- package/dist/types/components/vega-rich-text-editor/extensions/blockquote/action-handler-interceptors/blockquote-exit-on-enter-interceptor.d.ts +34 -0
- package/dist/types/components/vega-rich-text-editor/extensions/blockquote/action-handler-strategies/block-transform-to-blockquote-strategy.d.ts +13 -0
- package/dist/types/components/vega-rich-text-editor/extensions/blockquote/action-handler-strategies/remove-blockquote-strategy.d.ts +13 -0
- package/dist/types/components/vega-rich-text-editor/extensions/blockquote/actions/remove-blockquote-action.d.ts +14 -0
- package/dist/types/components/vega-rich-text-editor/extensions/blockquote/actions/transform-to-blockquote-action.d.ts +18 -0
- package/dist/types/components/vega-rich-text-editor/extensions/blockquote/assets/quote-left.d.ts +3 -0
- package/dist/types/components/vega-rich-text-editor/extensions/blockquote/blockquote-extension.d.ts +9 -0
- package/dist/types/components/vega-rich-text-editor/extensions/blockquote/dto/blockquote-annotation.d.ts +48 -0
- package/dist/types/components/vega-rich-text-editor/extensions/blockquote/dto/rte-blockquote-block.d.ts +56 -0
- package/dist/types/components/vega-rich-text-editor/extensions/blockquote/element-to-dto-strategies/blockquote-to-blockquote-block-strategy.d.ts +35 -0
- package/dist/types/components/vega-rich-text-editor/extensions/blockquote/filter-styles-strategies/blockquote-block/blockquote-filter-styles-strategy.d.ts +31 -0
- package/dist/types/components/vega-rich-text-editor/extensions/blockquote/renderers/blockquote-block-renderer.d.ts +18 -0
- package/dist/types/components/vega-rich-text-editor/extensions/blockquote/renderers/blockquote-toolbar-button-renderer.d.ts +113 -0
- package/dist/types/components/vega-rich-text-editor/extensions/copy/action-handler-strategies/copy-selected-content-strategy.d.ts +79 -0
- package/dist/types/components/vega-rich-text-editor/extensions/copy/actions/copy-selected-content-action.d.ts +18 -0
- package/dist/types/components/vega-rich-text-editor/extensions/copy/copy-extension.d.ts +10 -0
- package/dist/types/components/vega-rich-text-editor/extensions/copy/copy-toolbar-button-renderer.d.ts +36 -0
- package/dist/types/components/vega-rich-text-editor/extensions/cut/cut-extension.d.ts +11 -0
- package/dist/types/components/vega-rich-text-editor/extensions/cut/cut-toolbar-button-renderer.d.ts +24 -0
- package/dist/types/components/vega-rich-text-editor/extensions/find-replace/assets/find-and-replace.d.ts +3 -0
- package/dist/types/components/vega-rich-text-editor/extensions/find-replace/find-replace-extension.d.ts +15 -0
- package/dist/types/components/vega-rich-text-editor/extensions/find-replace/slimmer/controllers/find-controller.d.ts +65 -0
- package/dist/types/components/vega-rich-text-editor/extensions/find-replace/slimmer/controllers/find-replace-controller.abstract.d.ts +65 -0
- package/dist/types/components/vega-rich-text-editor/extensions/find-replace/slimmer/controllers/helper/block-search-strategies/block-search-strategy.abstract.d.ts +29 -0
- package/dist/types/components/vega-rich-text-editor/extensions/find-replace/slimmer/controllers/helper/block-search-strategies/code-block-search-strategy.d.ts +17 -0
- package/dist/types/components/vega-rich-text-editor/extensions/find-replace/slimmer/controllers/helper/block-search-strategies/container-block-search-strategy.d.ts +21 -0
- package/dist/types/components/vega-rich-text-editor/extensions/find-replace/slimmer/controllers/helper/block-search-strategies/list-item-block-search-strategy.d.ts +21 -0
- package/dist/types/components/vega-rich-text-editor/extensions/find-replace/slimmer/controllers/helper/block-search-strategies/text-block-search-strategy.d.ts +18 -0
- package/dist/types/components/vega-rich-text-editor/extensions/find-replace/slimmer/controllers/helper/find-replace-highlight-manager.d.ts +88 -0
- package/dist/types/components/vega-rich-text-editor/extensions/find-replace/slimmer/controllers/helper/find-replace-state.d.ts +200 -0
- package/dist/types/components/vega-rich-text-editor/extensions/find-replace/slimmer/controllers/replace-controller.d.ts +73 -0
- package/dist/types/components/vega-rich-text-editor/extensions/find-replace/slimmer/renderers/find-replace-toolbar-button-renderer.d.ts +64 -0
- package/dist/types/components/vega-rich-text-editor/extensions/find-replace/vega-rich-text-find-replace-panel/slimmers/renderers/find-replace-panel-renderer.d.ts +28 -0
- package/dist/types/components/vega-rich-text-editor/extensions/find-replace/vega-rich-text-find-replace-panel/vega-rich-text-find-replace-panel.d.ts +56 -0
- package/dist/types/components/vega-rich-text-editor/extensions/functions/function-extension.d.ts +1 -0
- package/dist/types/components/vega-rich-text-editor/extensions/horizontal-line/horizontal-line-extension.d.ts +1 -0
- package/dist/types/components/vega-rich-text-editor/extensions/image/image-extension.d.ts +27 -0
- package/dist/types/components/vega-rich-text-editor/extensions/image/type.d.ts +13 -0
- package/dist/types/components/vega-rich-text-editor/extensions/languages/language-extension.d.ts +1 -0
- package/dist/types/components/vega-rich-text-editor/extensions/line-height/line-height-extension.d.ts +1 -0
- package/dist/types/components/vega-rich-text-editor/extensions/paste/assets/paste.d.ts +3 -0
- package/dist/types/components/vega-rich-text-editor/extensions/paste/paste-chooser-ui.d.ts +58 -0
- package/dist/types/components/vega-rich-text-editor/extensions/paste/paste-constants.d.ts +6 -0
- package/dist/types/components/vega-rich-text-editor/extensions/paste/paste-extension.d.ts +150 -0
- package/dist/types/components/vega-rich-text-editor/extensions/special-characters/special-characters-extension.d.ts +1 -0
- package/dist/types/components/vega-rich-text-editor/extensions/table/table-extension.d.ts +1 -0
- package/dist/types/components/vega-rich-text-editor/extensions/tokens/token-extension.d.ts +1 -0
- package/dist/types/components/vega-rich-text-editor/helpers/utils.d.ts +7 -0
- package/dist/types/components/vega-rich-text-editor/public-api.d.ts +7 -0
- package/dist/types/components/vega-rich-text-editor/slimmers/controllers/helper/input-event-handler/abstract-delete-content-handler.d.ts +60 -0
- package/dist/types/components/vega-rich-text-editor/slimmers/controllers/helper/input-event-handler/backward-delete-content-handler.d.ts +8 -0
- package/dist/types/components/vega-rich-text-editor/slimmers/controllers/helper/input-event-handler/forward-delete-content-handler.d.ts +8 -0
- package/dist/types/components/vega-rich-text-editor/slimmers/controllers/helper/input-event-handler/insert-paste-handler.d.ts +114 -3
- package/dist/types/components/vega-rich-text-editor/slimmers/controllers/selection-controller.d.ts +22 -13
- package/dist/types/components/vega-rich-text-editor/slimmers/controllers/user-input-controller.d.ts +2 -1
- package/dist/types/components/vega-rich-text-editor/slimmers/renderers/tools/image-toolbar-button-slimmer.d.ts +10 -2
- package/dist/types/components/vega-rich-text-editor/vega-rich-text-editor-toolbar-button/slimmers/renderers/vega-rich-text-editor-toolbar-button-renderer.d.ts +1 -0
- package/dist/types/components/vega-rich-text-editor/vega-rich-text-editor-toolbar-button/vega-rich-text-editor-toolbar-button.d.ts +6 -0
- package/dist/types/components/vega-rich-text-editor/vega-rich-text-editor.d.ts +4 -4
- package/dist/types/components/vega-rich-text-editor/vega-rich-text-image-editor/vega-rich-text-image-editor.d.ts +1 -1
- package/dist/types/components/vega-rich-text-editor/vega-rich-text-link-editor/vega-rich-text-link-editor.d.ts +1 -1
- package/dist/types/components/vega-table/interface.d.ts +18 -0
- package/dist/types/components/vega-table/slimmers/controllers/dto/column-render-input-model.d.ts +13 -0
- package/dist/types/components/vega-table/slimmers/controllers/vega-table-body-col-span-controller.d.ts +43 -0
- package/dist/types/components/vega-table/slimmers/renderers/vega-table-body-col-span-resolver.d.ts +38 -0
- package/dist/types/components/vega-table/slimmers/renderers/vega-table-cell-renderer.d.ts +11 -2
- package/dist/types/components/vega-table/slimmers/renderers/vega-table-pinned-renderer.d.ts +4 -0
- package/dist/types/components/vega-table/slimmers/renderers/vega-table-row-renderer.d.ts +1 -0
- package/dist/types/components/vega-table/vega-table-cell/slimmers/controllers/vega-table-row-state-subscriber.d.ts +1 -1
- package/dist/types/components/vega-table/vega-table-cell/vega-table-cell.d.ts +9 -0
- package/dist/types/components/vega-table/vega-table-foot-cell/slimmers/renderers/vega-table-foot-cell-renderer.d.ts +1 -0
- package/dist/types/components/vega-table/vega-table-foot-cell/vega-table-foot-cell.d.ts +9 -0
- package/dist/types/components/vega-table/vega-table.d.ts +2 -0
- package/dist/types/components/vega-time-picker/slimmers/controllers/vega-time-picker-range-dropdown-controller.d.ts +1 -0
- package/dist/types/components/vega-time-picker/slimmers/controllers/vega-time-picker-single-dropdown-controller.d.ts +1 -0
- package/dist/types/components/vega-time-picker/types.d.ts +7 -0
- package/dist/types/components/vega-time-picker/vega-time-picker.d.ts +11 -1
- package/dist/types/components.d.ts +114 -3
- package/dist/types/constants/ui.d.ts +1 -1
- package/dist/types/global/icons/copy.d.ts +3 -0
- package/dist/types/helpers/event-manager/event-id/vega-event-id.d.ts +1 -0
- package/dist/types/helpers/translation/interface.d.ts +20 -1
- package/dist/types/helpers/ui/element-appender.d.ts +4 -1
- package/dist/types/types/components.type.d.ts +5 -1
- package/dist/types/types/ui.type.d.ts +1 -1
- package/dist/vega/index.esm.js +1 -1
- package/dist/vega/{p-94845302.entry.js → p-05f937c5.entry.js} +1 -1
- package/dist/vega/p-07562b9c.js +1 -0
- package/dist/vega/p-09d50f59.entry.js +1 -0
- package/dist/vega/{p-cc8bf6e7.entry.js → p-0cb78838.entry.js} +1 -1
- package/dist/vega/p-0e607b44.js +1 -0
- package/dist/vega/{p-1acd5f04.entry.js → p-0f1d6ceb.entry.js} +1 -1
- package/dist/vega/{p-ef44d516.js → p-140616fe.js} +1 -1
- package/dist/vega/{p-e75e75c9.entry.js → p-1818f399.entry.js} +1 -1
- package/dist/vega/{p-4037126d.js → p-18a5e8bc.js} +1 -1
- package/dist/vega/{p-2b22091e.js → p-19c42e30.js} +1 -1
- package/dist/vega/{p-c87c7ef4.js → p-1efab41b.js} +1 -1
- package/dist/vega/{p-cd2a1ffb.js → p-21e21f51.js} +1 -1
- package/dist/vega/{p-4644ccf9.entry.js → p-26c7fe8b.entry.js} +1 -1
- package/dist/vega/p-282b37cb.js +1 -0
- package/dist/vega/p-29e3eb12.js +1 -0
- package/dist/vega/{p-6ecb3c7d.entry.js → p-2dc5edd0.entry.js} +1 -1
- package/dist/vega/{p-014e2cf9.js → p-2de61f1b.js} +1 -1
- package/dist/vega/{p-b77e85e0.entry.js → p-2de88b4a.entry.js} +1 -1
- package/dist/vega/{p-12b5c5e3.entry.js → p-2fcc604c.entry.js} +1 -1
- package/dist/vega/{p-73b9868f.js → p-32a67ba1.js} +1 -1
- package/dist/vega/p-392e049c.entry.js +1 -0
- package/dist/vega/{p-70352240.entry.js → p-3932f964.entry.js} +1 -1
- package/dist/vega/{p-5ffc7cb8.js → p-3a68b729.js} +1 -1
- package/dist/vega/{p-9e327397.entry.js → p-3c9da3cd.entry.js} +1 -1
- package/dist/vega/{p-4cef4812.js → p-3f183b52.js} +1 -1
- package/dist/vega/{p-216ef41e.entry.js → p-3f7b10b9.entry.js} +1 -1
- package/dist/vega/{p-64b5fe2a.entry.js → p-422d88ae.entry.js} +1 -1
- package/dist/vega/p-4256252c.js +3 -0
- package/dist/vega/{p-329c808e.entry.js → p-44439ecc.entry.js} +1 -1
- package/dist/vega/p-45e8424a.entry.js +1 -0
- package/dist/vega/p-4c49b2e3.js +1 -0
- package/dist/vega/{p-a14d8767.js → p-4d9562bc.js} +1 -1
- package/dist/vega/p-4e1aa86f.js +1 -0
- package/dist/vega/p-4ef5c4a7.entry.js +1 -0
- package/dist/vega/{p-7f086e54.entry.js → p-4f327808.entry.js} +1 -1
- package/dist/vega/{p-8019c156.js → p-5139e516.js} +1 -1
- package/dist/vega/{p-5bed3fd2.entry.js → p-56c4e349.entry.js} +1 -1
- package/dist/vega/{p-fea33425.js → p-5dbfffcd.js} +1 -1
- package/dist/vega/p-5f377954.js +1 -1
- package/dist/vega/p-615a2b8c.entry.js +1 -0
- package/dist/vega/{p-aaa6e976.entry.js → p-63101cb4.entry.js} +1 -1
- package/dist/vega/{p-a4826f3f.entry.js → p-653a6dd8.entry.js} +1 -1
- package/dist/vega/{p-044496e0.entry.js → p-66b7d1c7.entry.js} +1 -1
- package/dist/vega/{p-42a1d300.entry.js → p-6712a9c7.entry.js} +1 -1
- package/dist/vega/p-698fbd73.js +1 -0
- package/dist/vega/p-6ad7144a.entry.js +1 -0
- package/dist/vega/{p-6e7c1b19.entry.js → p-6b1893f6.entry.js} +1 -1
- package/dist/vega/{p-cec8ffdc.js → p-6d40635e.js} +1 -1
- package/dist/vega/{p-81591449.entry.js → p-73fd18d9.entry.js} +1 -1
- package/dist/vega/p-751a5cd6.entry.js +1 -0
- package/dist/vega/{p-42f3c0b1.entry.js → p-7b93a756.entry.js} +1 -1
- package/dist/vega/p-7c8e579e.entry.js +1 -0
- package/dist/vega/{p-0707aa21.js → p-7d54e9c1.js} +1 -1
- package/dist/vega/p-832d1a72.entry.js +1 -0
- package/dist/vega/p-846b1ab9.entry.js +1 -0
- package/dist/vega/p-88863658.js +1 -0
- package/dist/vega/p-88a6c89a.js +1 -0
- package/dist/vega/p-890a5254.js +1 -0
- package/dist/vega/{p-14bfc113.js → p-8bc15ed0.js} +1 -1
- package/dist/vega/{p-50d7a1c4.entry.js → p-8c1a5fa4.entry.js} +1 -1
- package/dist/vega/p-8d27b9cc.js +1 -0
- package/dist/vega/{p-3863187f.entry.js → p-944b43ce.entry.js} +1 -1
- package/dist/vega/{p-aea912ac.js → p-95d6c85f.js} +1 -1
- package/dist/vega/{p-da067259.entry.js → p-99b699be.entry.js} +1 -1
- package/dist/vega/{p-d00b3910.js → p-9a01be93.js} +1 -1
- package/dist/vega/p-9a472a04.entry.js +1 -0
- package/dist/vega/{p-e9a82603.entry.js → p-9cafbc26.entry.js} +1 -1
- package/dist/vega/{p-cd21adb8.js → p-a2bd621e.js} +1 -1
- package/dist/vega/{p-5a4e598f.js → p-a359e53c.js} +1 -1
- package/dist/vega/p-a9a87b9a.js +1 -0
- package/dist/vega/p-acbba6da.entry.js +1 -0
- package/dist/vega/{p-c75872a4.entry.js → p-ad35dea4.entry.js} +1 -1
- package/dist/vega/{p-8bc23bcf.entry.js → p-af98539f.entry.js} +1 -1
- package/dist/vega/{p-141f74ff.js → p-afb96938.js} +1 -1
- package/dist/vega/{p-6820b488.entry.js → p-b042e3b4.entry.js} +1 -1
- package/dist/vega/{p-822f70b0.js → p-b1ff70bf.js} +1 -1
- package/dist/vega/{p-c65a5778.entry.js → p-b39d4a88.entry.js} +1 -1
- package/dist/vega/{p-5df53125.entry.js → p-b5bee669.entry.js} +1 -1
- package/dist/vega/p-b5f308ee.entry.js +1 -0
- package/dist/vega/p-b6225d01.js +1 -0
- package/dist/vega/{p-8982796b.entry.js → p-bbabb1c8.entry.js} +1 -1
- package/dist/vega/p-bbf1f988.entry.js +1 -0
- package/dist/vega/{p-5fcb825c.entry.js → p-c2d28797.entry.js} +1 -1
- package/dist/vega/{p-42ed76ee.entry.js → p-c3c0e415.entry.js} +1 -1
- package/dist/vega/{p-5e282e41.js → p-c553d37e.js} +1 -1
- package/dist/vega/{p-2f48162f.entry.js → p-c5ecbc53.entry.js} +1 -1
- package/dist/vega/p-c9c6daa6.entry.js +1 -0
- package/dist/vega/p-cb81fc2a.entry.js +1 -0
- package/dist/vega/p-ccb9bf86.js +1 -0
- package/dist/vega/{p-49b4ab2b.js → p-ccf7518b.js} +1 -1
- package/dist/vega/{p-da2b1fd0.entry.js → p-cf8d5b3f.entry.js} +1 -1
- package/dist/vega/{p-bb0040d4.entry.js → p-d3a36d19.entry.js} +1 -1
- package/dist/vega/{p-ce27c273.entry.js → p-d44943e2.entry.js} +1 -1
- package/dist/vega/{p-ae728041.entry.js → p-d4bb6f93.entry.js} +1 -1
- package/dist/vega/p-d62b85ff.entry.js +1 -0
- package/dist/vega/p-d7f32ccf.entry.js +1 -0
- package/dist/vega/{p-d0060bdb.js → p-dca3278f.js} +1 -1
- package/dist/vega/{p-15da9d58.entry.js → p-ddd28a8e.entry.js} +1 -1
- package/dist/vega/p-ddf03de1.entry.js +1 -0
- package/dist/vega/{p-21775cc7.js → p-e0ecafca.js} +1 -1
- package/dist/vega/p-e33e1e90.js +1 -0
- package/dist/vega/{p-316a1e5c.entry.js → p-e410c4ad.entry.js} +1 -1
- package/dist/vega/p-e4309574.js +1 -0
- package/dist/vega/p-e5187a86.entry.js +1 -0
- package/dist/vega/{p-335e3237.entry.js → p-e86c6802.entry.js} +1 -1
- package/dist/vega/{p-4979fd7d.js → p-ef605271.js} +1 -1
- package/dist/vega/{p-0e03daa6.js → p-f0d4c78c.js} +1 -1
- package/dist/vega/p-f2f22b5d.entry.js +1 -0
- package/dist/vega/{p-58a00869.entry.js → p-f8a29127.entry.js} +1 -1
- package/dist/vega/{p-a04b95aa.entry.js → p-fbd3be00.entry.js} +1 -1
- package/dist/vega/p-fd70418f.js +1 -0
- package/dist/vega/{p-9756c6b9.entry.js → p-fda6e2cc.entry.js} +1 -1
- package/dist/vega/p-ff8dd200.js +1 -0
- package/dist/vega/{p-522dd15f.entry.js → p-ffdacaf9.entry.js} +1 -1
- package/dist/vega/vega.esm.js +1 -1
- package/package.json +2 -16
- package/readme.md +0 -5
- package/dist/collection/components/vega-rich-text-editor/slimmers/controllers/helper/input-event-handler/delete-content-handler.js +0 -92
- package/dist/types/components/vega-rich-text-editor/slimmers/controllers/helper/input-event-handler/delete-content-handler.d.ts +0 -26
- package/dist/vega/p-0605c656.entry.js +0 -1
- package/dist/vega/p-144d4b30.js +0 -1
- package/dist/vega/p-2944c76e.entry.js +0 -1
- package/dist/vega/p-3372f34a.entry.js +0 -1
- package/dist/vega/p-3cf298a4.entry.js +0 -1
- package/dist/vega/p-3fe43f7a.entry.js +0 -1
- package/dist/vega/p-507612f6.entry.js +0 -1
- package/dist/vega/p-53ed3b1f.js +0 -1
- package/dist/vega/p-57f7c257.js +0 -1
- package/dist/vega/p-5e1cd1cc.js +0 -1
- package/dist/vega/p-5e1ec2b8.entry.js +0 -1
- package/dist/vega/p-61d42e9b.entry.js +0 -1
- package/dist/vega/p-63ae5ada.entry.js +0 -1
- package/dist/vega/p-66905cbf.js +0 -1
- package/dist/vega/p-689070f9.entry.js +0 -1
- package/dist/vega/p-74fa0f5b.entry.js +0 -1
- package/dist/vega/p-7bb39b97.js +0 -1
- package/dist/vega/p-7f139b74.js +0 -1
- package/dist/vega/p-85d78ac1.js +0 -1
- package/dist/vega/p-92a144bc.js +0 -1
- package/dist/vega/p-9f608b97.js +0 -1
- package/dist/vega/p-aca134a3.entry.js +0 -1
- package/dist/vega/p-b2f89dbd.js +0 -1
- package/dist/vega/p-b2f9698a.entry.js +0 -1
- package/dist/vega/p-b504a8b4.entry.js +0 -1
- package/dist/vega/p-b7b0930a.entry.js +0 -1
- package/dist/vega/p-bd94b904.entry.js +0 -1
- package/dist/vega/p-c0c2a917.js +0 -3
- package/dist/vega/p-c40555c4.entry.js +0 -1
- package/dist/vega/p-cb4033ef.js +0 -1
- package/dist/vega/p-d14ddf80.js +0 -1
- package/dist/vega/p-dc78e8d6.js +0 -1
- package/dist/vega/p-ddc2d219.js +0 -1
- package/dist/vega/p-f362c07b.entry.js +0 -1
- package/dist/vega/p-fb1ef3e6.entry.js +0 -1
- package/dist/vega/p-fc103347.entry.js +0 -1
- /package/dist/cjs/{feature-flag-controller-438a1510.js → feature-flag-controller-c19d0b58.js} +0 -0
- /package/dist/collection/{components/vega-code-block/assets → global/icons}/copy.js +0 -0
- /package/dist/esm/{feature-flag-controller-2a89f20c.js → feature-flag-controller-3fd04ea9.js} +0 -0
- /package/dist/types/components/{vega-code-block/assets/copy.d.ts → vega-rich-text-editor/assets/cut.d.ts} +0 -0
- /package/dist/vega/{p-bc73ce30.js → p-87a4d75f.js} +0 -0
|
@@ -5,27 +5,29 @@ import { I as InjectVegaTelemetrySlimmer, c as createPublicAPIRuntimeMetricsSlim
|
|
|
5
5
|
import { I as InternalVegaZIndexManager } from './internal-vega-z-index-manager-7d2b54c3.js';
|
|
6
6
|
import { c as createDynamicSlimmer } from './dynamic-slimmer-90b8af32.js';
|
|
7
7
|
import { P as PageResizeObserverSlimmer } from './page-resize-observer-slimmer-e24d36c5.js';
|
|
8
|
-
import { i as internalVegaEventManager } from './internal-vega-event-manager-
|
|
9
|
-
import { V as VegaInternalTranslation } from './internal-translation-controller-
|
|
10
|
-
import { I as InternalCalendarPeriodConverter } from './internal-calendar-period-factory-
|
|
11
|
-
import {
|
|
12
|
-
import { R as
|
|
13
|
-
import { M as ModifyContentAction, A as ActionHandleStrategyRegistry, R as RTETextNode, a as RTEBlock, b as RTETextBlock, c as RTEAnnotationStyle, d as ActionHandleStrategy, I as InsertChildrenAfterAction, e as AppendChildrenAction, f as RTENode, s as stateEntityRenderingRegistry, g as InsertChildrenBeforeAction, h as InternalAnnotationTypeEnum, i as ModifyContentActionType, B as BlockAnnotation, j as AnnotationAction, k as RemoveChildrenAction, C as CommonAnnotationTypeEnum, l as CustomAttributeAnnotation, m as RTEDTOClassManager, n as RTEFilterStylesStrategy, o as RTEDecoratorNode, S as SplitBlockWithNodeAction, U as UpdateTextAction,
|
|
14
|
-
import { i as isNonNullable } from './type-guard-
|
|
8
|
+
import { i as internalVegaEventManager } from './internal-vega-event-manager-48690e5b.js';
|
|
9
|
+
import { V as VegaInternalTranslation } from './internal-translation-controller-09b8bd13.js';
|
|
10
|
+
import { I as InternalCalendarPeriodConverter } from './internal-calendar-period-factory-6f19d767.js';
|
|
11
|
+
import { R as RTEExtensionContextManager, a as VegaRTEExtensionRenderer, b as VegaRTEExtension } from './link-extension-74eaa826.js';
|
|
12
|
+
import { U as UpdateCursorPositionAction, V as VegaRTEContent, B as BlockToRTEBlockStrategyAbstract, E as ElementToDTOStrategy, h as htmlElementToAnnotationGenerator, I as InsertNewParagraphAction, R as RTEImageBlock, a as InsertChildrenBeforeStrategy, P as PasteContentStrategy, A as AnnotationGeneratorStrategyAbstract, b as RTEListItemBlock } from './content-state-c3b85ae6.js';
|
|
13
|
+
import { M as ModifyContentAction, A as ActionHandleStrategyRegistry, R as RTETextNode, a as RTEBlock, b as RTETextBlock, c as RTEAnnotationStyle, d as ActionHandleStrategy, I as InsertChildrenAfterAction, e as AppendChildrenAction, f as RTENode, s as stateEntityRenderingRegistry, g as InsertChildrenBeforeAction, h as InternalAnnotationTypeEnum, i as ModifyContentActionType, B as BlockAnnotation, j as AnnotationAction, k as RemoveChildrenAction, C as CommonAnnotationTypeEnum, l as CustomAttributeAnnotation, m as RTEDTOClassManager, n as RTEFilterStylesStrategy, o as RTEDecoratorNode, S as SplitBlockWithNodeAction, U as UpdateTextAction, p as RTECodeBlock, q as RTECodeBlockNode, N as NodeAnnotationTypeEnum, r as CustomStyleAnnotation, t as RTEFilterStylesStrategyRegistry } from './code-block-ae2337c2.js';
|
|
14
|
+
import { i as isNonNullable } from './type-guard-2bfb2c7b.js';
|
|
15
15
|
import { V as VegaInternalIconManager } from './internal-icon-manager-e7c57096.js';
|
|
16
16
|
import { v as vegaNonceManager } from './vega-nonce-manager-497e5eb5.js';
|
|
17
17
|
import { D as DomNodeSubjectObserverFactory, d as domNodeSubjectFactory } from './dom-node-subject-observer-factory-ddff8662.js';
|
|
18
|
-
import { V as VegaDropdownClick, a as VegaClick, b as VegaPopoverHide, c as VegaPopoverShow } from './vega-event-id-
|
|
19
|
-
import { R as RTETableExtensionAnnotationTypeEnum, a as RTE_TABLE_HEAD_DEFAULT_BACKGROUND_COLOR, S as SettingEventHandler, T as TableSettingType, b as RTETableAnnotation, c as RTETableCellAnnotation, d as RTETableHeadCellBlock, e as RowOperationEventHandler, C as ColumnOperationEventHandler, M as MergeCellsOperationEventHandler, f as SplitCellOperationEventHandler, g as RTETableColorManager, h as RTETableBlock, i as generateTableBody, j as RTETableCellBlock, k as RTETableHeadBlock, l as RTETableBodyBlock, m as getParentBlockByType, n as RTETableHeadCellAnnotation, o as TableColumnStrategy, p as getAllTableRows, q as buildTableGrid, s as setSpanAttributes, r as findCellBlockById, t as getDefaultCellBlockTemplate, I as InsertTableColumnDirection, u as buildCellStartColumnMap, v as getTableColumnCount, w as getFirstNode, x as getCellSpan, y as createNewCell, z as MergeCellsOperationType, A as RTETableRowBlock, B as getActualColumnPosition, D as InsertTableRowDirection, E as RTE_TABLE_DEFAULT_COLORS, F as INSERT_TABLE_ROW_INTO_DIFFERENT_SECTION, G as INSERT_TABLE_ROW_INTO_SAME_SECTION, H as DELETE_TABLE_ROW, J as TOGGLE_HEADER_ROW, K as INSERT_TABLE_COLUMN, L as DELETE_TABLE_COLUMN, N as TOGGLE_HEADER_TABLE_COLUMN, O as ToggleHeaderTableColumnStrategy, P as SPLIT_TABLE_CELL, Q as REPLACE_TABLE_CELL, U as SPLIT_CELL_HORIZONTALLY, W as SPLIT_CELL_VERTICALLY, X as SELECT_TABLE_COLUMN, Y as MERGE_CELLS } from './split-cell-operation-
|
|
18
|
+
import { V as VegaDropdownClick, a as VegaClick, b as VegaPopoverHide, c as VegaPopoverShow, d as VegaChange } from './vega-event-id-270fe47e.js';
|
|
19
|
+
import { R as RTETableExtensionAnnotationTypeEnum, a as RTE_TABLE_HEAD_DEFAULT_BACKGROUND_COLOR, S as SettingEventHandler, T as TableSettingType, b as RTETableAnnotation, c as RTETableCellAnnotation, d as RTETableHeadCellBlock, e as RowOperationEventHandler, C as ColumnOperationEventHandler, M as MergeCellsOperationEventHandler, f as SplitCellOperationEventHandler, g as RTETableColorManager, h as RTETableBlock, i as generateTableBody, j as RTETableCellBlock, k as RTETableHeadBlock, l as RTETableBodyBlock, m as getParentBlockByType, n as RTETableHeadCellAnnotation, o as TableColumnStrategy, p as getAllTableRows, q as buildTableGrid, s as setSpanAttributes, r as findCellBlockById, t as getDefaultCellBlockTemplate, I as InsertTableColumnDirection, u as buildCellStartColumnMap, v as getTableColumnCount, w as getFirstNode, x as getCellSpan, y as createNewCell, z as MergeCellsOperationType, A as RTETableRowBlock, B as getActualColumnPosition, D as InsertTableRowDirection, E as RTE_TABLE_DEFAULT_COLORS, F as INSERT_TABLE_ROW_INTO_DIFFERENT_SECTION, G as INSERT_TABLE_ROW_INTO_SAME_SECTION, H as DELETE_TABLE_ROW, J as TOGGLE_HEADER_ROW, K as INSERT_TABLE_COLUMN, L as DELETE_TABLE_COLUMN, N as TOGGLE_HEADER_TABLE_COLUMN, O as ToggleHeaderTableColumnStrategy, P as SPLIT_TABLE_CELL, Q as REPLACE_TABLE_CELL, U as SPLIT_CELL_HORIZONTALLY, W as SPLIT_CELL_VERTICALLY, X as SELECT_TABLE_COLUMN, Y as MERGE_CELLS } from './split-cell-operation-98d58410.js';
|
|
20
20
|
import { C as ChangeManager } from './change-manager-6a7eb88c.js';
|
|
21
|
-
import { V as VegaInternalRTEExtensionTableCellsSelectionAnchor, a as VegaInternalPopoverUpdateTarget, b as VegaInternalRTEExtensionTableCellSelectionChange } from './vega-internal-event-id-
|
|
21
|
+
import { V as VegaInternalRTEExtensionTableCellsSelectionAnchor, a as VegaInternalPopoverUpdateTarget, b as VegaInternalRTEExtensionTableCellSelectionChange } from './vega-internal-event-id-827eb1ce.js';
|
|
22
22
|
import { r as remoteInvocationRegistry } from './remote-invocation-registry-d69245c2.js';
|
|
23
|
-
import { V as VegaInternalThemeManager } from './dark-mode-style-controller-ca5afd2d.js';
|
|
23
|
+
import { V as VegaInternalThemeManager, a as VegaDarkModeStyleController } from './dark-mode-style-controller-ca5afd2d.js';
|
|
24
24
|
import { t as tryGetDocument } from './try-get-document-bef0f526.js';
|
|
25
25
|
import { p as parseToPixelString } from './pixel-f32c07ce.js';
|
|
26
26
|
import { c as cleanObject } from './object-0c277f57.js';
|
|
27
27
|
import { d as debounce } from './timer-9321173b.js';
|
|
28
|
-
import {
|
|
28
|
+
import { c as camelToDashCase } from './string-a953eafc.js';
|
|
29
|
+
import { R as ReplaceSelectedTextAction } from './replace-selected-text-action-c9278a0f.js';
|
|
30
|
+
import { c as copy } from './copy-54e45e2d.js';
|
|
29
31
|
|
|
30
32
|
const VegaLoaderRuntimeMetricsPayloadDefinition = [
|
|
31
33
|
{
|
|
@@ -1767,7 +1769,7 @@ class VegaRTELanguageExtension extends VegaRTEExtension {
|
|
|
1767
1769
|
*/
|
|
1768
1770
|
prepareBeforeLoad(host) {
|
|
1769
1771
|
this.languageToolbarButtonRenderer = new RTELanguageToolbarButtonRenderer(this.languageList, this.languageChangeCallBack, this.languageState.selectedLanguage);
|
|
1770
|
-
this.registerToolbarButtonRenderer(
|
|
1772
|
+
this.registerToolbarButtonRenderer(VegaRTELanguageExtension.TOOLBAR_BUTTON_KEY, this.languageToolbarButtonRenderer, host);
|
|
1771
1773
|
}
|
|
1772
1774
|
/**
|
|
1773
1775
|
* Initializes the language extension.
|
|
@@ -1859,6 +1861,7 @@ class VegaRTELanguageExtension extends VegaRTEExtension {
|
|
|
1859
1861
|
return result;
|
|
1860
1862
|
}
|
|
1861
1863
|
}
|
|
1864
|
+
VegaRTELanguageExtension.TOOLBAR_BUTTON_KEY = 'languages';
|
|
1862
1865
|
|
|
1863
1866
|
const INSERT_FUNCTION_BLOCK = 'INSERT_FUNCTION_BLOCK';
|
|
1864
1867
|
/**
|
|
@@ -2651,7 +2654,7 @@ class VegaRTEFunctionExtension extends VegaRTEExtension {
|
|
|
2651
2654
|
}
|
|
2652
2655
|
/** @inheritDoc */
|
|
2653
2656
|
prepareBeforeLoad(host) {
|
|
2654
|
-
this.registerToolbarButtonRenderer(
|
|
2657
|
+
this.registerToolbarButtonRenderer(VegaRTEFunctionExtension.TOOLBAR_BUTTON_KEY, new FunctionToolbarButtonRenderer(), host);
|
|
2655
2658
|
}
|
|
2656
2659
|
/**
|
|
2657
2660
|
* Remove the appen children strategies to prevent default insert new paragraph logic in function block
|
|
@@ -2675,6 +2678,7 @@ class VegaRTEFunctionExtension extends VegaRTEExtension {
|
|
|
2675
2678
|
VegaRTEExtension.registerActionHandlerInterceptor(RTETextBlock.name, ModifyContentActionType.INSERT_NEW_PARAGRAPH, new PreventNewParagraphInterceptor());
|
|
2676
2679
|
VegaRTEExtension.registerActionHandlerInterceptor(RTETextBlock.name, ModifyContentActionType.PASTE_CONTENT, new PreventPasteContentInterceptor());
|
|
2677
2680
|
})();
|
|
2681
|
+
VegaRTEFunctionExtension.TOOLBAR_BUTTON_KEY = 'functions';
|
|
2678
2682
|
|
|
2679
2683
|
const tableBlockStylesCss = ".v-rte--extension-table-container{display:table}table.v-rte--extension-table{border-collapse:collapse}table.v-rte--extension-table th,table.v-rte--extension-table td{border:1px solid #ddd;padding:12px;text-align:left;vertical-align:middle;min-width:55px;box-sizing:border-box;position:relative}table.v-rte--extension-table th{background-color:#f2f2f2}.rte-table-setting-popover,.rte-table-dynamic-popover{display:block}table.v-rte--extension-table .v-rte-table-cell-focused::after,table.v-rte--extension-table .v-rte-table-cell-selected::after{content:'';position:absolute;top:0;left:0;right:0;bottom:0;pointer-events:none;background-color:rgba(158, 207, 250, 0.3)}table.v-rte--extension-table:has(.v-rte-table-cell-selected){caret-color:transparent}table.v-rte--extension-table:has(.v-rte-table-cell-selected) ::selection{background-color:transparent}table.v-rte--extension-table:has(.v-rte-table-cell-selected) ::-moz-selection{background-color:transparent}.v-rte--extension-table-caption{padding:8px;border:1px solid #ddd;border-bottom:none;background-color:rgba(var(--v-bg-table-header, 240, 243, 247, 1));position:relative;display:table-caption;caption-side:top;min-height:22px;text-align:center;word-break:break-word;overflow-wrap:break-word}.v-rte--extension-table-caption.showPlaceholder::before{position:absolute;left:0;right:0;content:attr(data-placeholder);font-family:'Inter', sans-serif;font-size:16px;font-weight:400;line-height:24px;letter-spacing:0;color:rgba(var(--v-text-input-placeholder, 176, 180, 181, 1));text-align:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}";
|
|
2680
2684
|
|
|
@@ -3801,7 +3805,7 @@ class RTETableBlockRenderer extends VegaRTEExtensionRenderer {
|
|
|
3801
3805
|
* @param {HTMLElement} tableRef - The element that can triggered the table popover open.
|
|
3802
3806
|
*/
|
|
3803
3807
|
handleTableRemove(renderData, tableRef) {
|
|
3804
|
-
renderData.
|
|
3808
|
+
renderData.apply(new DeleteTableBlockAction(renderData));
|
|
3805
3809
|
this.destroySelectionAnchor(tableRef);
|
|
3806
3810
|
}
|
|
3807
3811
|
/**
|
|
@@ -7311,7 +7315,7 @@ class VegaRTETableExtension extends VegaRTEExtension {
|
|
|
7311
7315
|
this.tableHeadCellRenderer = new RTETableHeadCellRenderer();
|
|
7312
7316
|
this.tableSelection = new RTETableSelection();
|
|
7313
7317
|
RTETableColorManager.setTableColors(colors);
|
|
7314
|
-
this.registerToolbarButtonRenderer(
|
|
7318
|
+
this.registerToolbarButtonRenderer(VegaRTETableExtension.TOOLBAR_BUTTON_KEY, this.toolbarButtonRenderer);
|
|
7315
7319
|
this.registerBlockBasicStrategies(RTETableBlock.name);
|
|
7316
7320
|
this.registerBlockBasicStrategies(RTETableHeadBlock.name);
|
|
7317
7321
|
this.registerBlockBasicStrategies(RTETableHeadCellBlock.name);
|
|
@@ -7365,9 +7369,10 @@ class VegaRTETableExtension extends VegaRTEExtension {
|
|
|
7365
7369
|
const insertTableBlockStrategy = new InsertTableBlockStrategy();
|
|
7366
7370
|
const deleteTableBlockStrategy = new DeleteTableBlockStrategy();
|
|
7367
7371
|
VegaRTEExtension.registerActionHandleStrategy(VegaRTEContent.name, INSERT_TABLE_BLOCK, insertTableBlockStrategy);
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
7372
|
+
// Registered on the table block itself (not its parent) so a table can always be
|
|
7373
|
+
// deleted regardless of which block type it is nested in (root content, table cell,
|
|
7374
|
+
// blockquote, or any future container).
|
|
7375
|
+
VegaRTEExtension.registerActionHandleStrategy(RTETableBlock.name, DELETE_TABLE_BLOCK, deleteTableBlockStrategy);
|
|
7371
7376
|
VegaRTEExtension.registerActionHandleStrategy(RTETextBlock.name, INSERT_TABLE_BLOCK, insertTableBlockStrategy);
|
|
7372
7377
|
VegaRTEExtension.registerActionHandleStrategy(RTEImageBlock.name, INSERT_TABLE_BLOCK, insertTableBlockStrategy);
|
|
7373
7378
|
VegaRTEExtension.registerActionHandleStrategy(RTETableBlock.name, INSERT_TABLE_ROW_INTO_DIFFERENT_SECTION, new InsertTableRowIntoDifferentSectionStrategy());
|
|
@@ -7399,6 +7404,7 @@ class VegaRTETableExtension extends VegaRTEExtension {
|
|
|
7399
7404
|
VegaRTEExtension.registerFilterStylesStrategy(RTETableCellBlock.dtoName, new TableCellFilterStylesStrategy());
|
|
7400
7405
|
VegaRTEExtension.registerFilterStylesStrategy(RTETableHeadCellBlock.dtoName, new TableCellFilterStylesStrategy('th'));
|
|
7401
7406
|
})();
|
|
7407
|
+
VegaRTETableExtension.TOOLBAR_BUTTON_KEY = 'table';
|
|
7402
7408
|
|
|
7403
7409
|
/**
|
|
7404
7410
|
* Horizontal line node — a decorator node representing `<hr>` in the editor.
|
|
@@ -7896,7 +7902,7 @@ class VegaRTEHorizontalLineExtension extends VegaRTEExtension {
|
|
|
7896
7902
|
// Register the renderer
|
|
7897
7903
|
this.registerRenderer('horizontal-line', this.blockRenderer);
|
|
7898
7904
|
// Register the toolbar button
|
|
7899
|
-
this.registerToolbarButtonRenderer(
|
|
7905
|
+
this.registerToolbarButtonRenderer(VegaRTEHorizontalLineExtension.TOOLBAR_BUTTON_KEY, this.toolbarButtonRenderer);
|
|
7900
7906
|
// Register HTML → DTO conversion strategy
|
|
7901
7907
|
this.registerElementToBlockDTOClassStrategy(new HrToHorizontalLineBlockStrategy());
|
|
7902
7908
|
// Register filter styles strategy for source edit round-trip
|
|
@@ -7911,6 +7917,695 @@ class VegaRTEHorizontalLineExtension extends VegaRTEExtension {
|
|
|
7911
7917
|
VegaRTEExtension.registerActionHandleStrategy(RTEImageBlock.name, INSERT_HORIZONTAL_LINE_BLOCK, insertStrategy);
|
|
7912
7918
|
VegaRTEExtension.registerActionHandleStrategy(RTEHorizontalLineBlock.name, INSERT_HORIZONTAL_LINE_BLOCK, insertStrategy);
|
|
7913
7919
|
})();
|
|
7920
|
+
VegaRTEHorizontalLineExtension.TOOLBAR_BUTTON_KEY = 'horizontalLine';
|
|
7921
|
+
|
|
7922
|
+
var RTEBlockquoteExtensionAnnotationTypeEnum;
|
|
7923
|
+
(function (RTEBlockquoteExtensionAnnotationTypeEnum) {
|
|
7924
|
+
RTEBlockquoteExtensionAnnotationTypeEnum["BLOCKQUOTE"] = "BLOCKQUOTE";
|
|
7925
|
+
})(RTEBlockquoteExtensionAnnotationTypeEnum || (RTEBlockquoteExtensionAnnotationTypeEnum = {}));
|
|
7926
|
+
/**
|
|
7927
|
+
* CSS class applied to `<blockquote>` elements rendered inside the editor.
|
|
7928
|
+
*/
|
|
7929
|
+
const BLOCKQUOTE_CLASS_NAME = 'v-rte-blockquote';
|
|
7930
|
+
/**
|
|
7931
|
+
* Default visual styles for the `<blockquote>` element in the RTE.
|
|
7932
|
+
*/
|
|
7933
|
+
const BLOCKQUOTE_DEFAULT_STYLES = {
|
|
7934
|
+
overflow: 'hidden',
|
|
7935
|
+
marginInline: '0',
|
|
7936
|
+
paddingInline: '24px',
|
|
7937
|
+
borderInlineStart: 'solid 5px hsl(0, 0%, 80%)',
|
|
7938
|
+
};
|
|
7939
|
+
/**
|
|
7940
|
+
* Blockquote annotation.
|
|
7941
|
+
*/
|
|
7942
|
+
class BlockquoteAnnotation extends BlockAnnotation {
|
|
7943
|
+
constructor() {
|
|
7944
|
+
super(...arguments);
|
|
7945
|
+
this.type = RTEBlockquoteExtensionAnnotationTypeEnum.BLOCKQUOTE;
|
|
7946
|
+
}
|
|
7947
|
+
/**
|
|
7948
|
+
* Generate the map item for the annotation.
|
|
7949
|
+
*
|
|
7950
|
+
* @returns {[RTEBlockquoteExtensionAnnotationTypeEnum.BLOCKQUOTE, BlockquoteAnnotation]} Map item.
|
|
7951
|
+
*/
|
|
7952
|
+
static from() {
|
|
7953
|
+
return [RTEBlockquoteExtensionAnnotationTypeEnum.BLOCKQUOTE, new BlockquoteAnnotation()];
|
|
7954
|
+
}
|
|
7955
|
+
/**
|
|
7956
|
+
* Get the default CSS rule for the blockquote element.
|
|
7957
|
+
*
|
|
7958
|
+
* @returns {string} The default CSS rule.
|
|
7959
|
+
*/
|
|
7960
|
+
static getDefaultCssText() {
|
|
7961
|
+
const declarations = Object.entries(BLOCKQUOTE_DEFAULT_STYLES)
|
|
7962
|
+
.map(([key, value]) => `\t${camelToDashCase(key)}: ${value};`)
|
|
7963
|
+
.join('\n');
|
|
7964
|
+
return `.${BLOCKQUOTE_CLASS_NAME} {\n${declarations}\n}`;
|
|
7965
|
+
}
|
|
7966
|
+
/**
|
|
7967
|
+
* @inheritDoc
|
|
7968
|
+
*/
|
|
7969
|
+
toJSON() {
|
|
7970
|
+
// Blockquote identity is captured by the block type; nothing to serialize.
|
|
7971
|
+
return null;
|
|
7972
|
+
}
|
|
7973
|
+
/**
|
|
7974
|
+
* @inheritDoc
|
|
7975
|
+
*/
|
|
7976
|
+
clone() {
|
|
7977
|
+
return new BlockquoteAnnotation();
|
|
7978
|
+
}
|
|
7979
|
+
/**
|
|
7980
|
+
* @inheritDoc
|
|
7981
|
+
*/
|
|
7982
|
+
renderClass(options) {
|
|
7983
|
+
if (options === null || options === void 0 ? void 0 : options.standalone)
|
|
7984
|
+
return null;
|
|
7985
|
+
return BLOCKQUOTE_CLASS_NAME;
|
|
7986
|
+
}
|
|
7987
|
+
/**
|
|
7988
|
+
* @inheritDoc
|
|
7989
|
+
*/
|
|
7990
|
+
renderStyle(options) {
|
|
7991
|
+
if (!(options === null || options === void 0 ? void 0 : options.standalone))
|
|
7992
|
+
return null;
|
|
7993
|
+
return BLOCKQUOTE_DEFAULT_STYLES;
|
|
7994
|
+
}
|
|
7995
|
+
}
|
|
7996
|
+
|
|
7997
|
+
/**
|
|
7998
|
+
* DTO representing a `<blockquote>` block.
|
|
7999
|
+
*/
|
|
8000
|
+
class RTEBlockquoteBlock extends RTEBlock {
|
|
8001
|
+
constructor(id, options) {
|
|
8002
|
+
super(id);
|
|
8003
|
+
this.dtoName = RTEBlockquoteBlock.dtoName;
|
|
8004
|
+
this.type = 'blockquote';
|
|
8005
|
+
this.children = [];
|
|
8006
|
+
const { autoMatchFormat = true } = options !== null && options !== void 0 ? options : {};
|
|
8007
|
+
if (autoMatchFormat) {
|
|
8008
|
+
this.annotationMap = new Map([BlockquoteAnnotation.from()]);
|
|
8009
|
+
}
|
|
8010
|
+
}
|
|
8011
|
+
/**
|
|
8012
|
+
* Convert a JSON block payload into an `RTEBlockquoteBlock` instance.
|
|
8013
|
+
*
|
|
8014
|
+
* @param {VegaRTEBlockquoteBlock} block - JSON payload.
|
|
8015
|
+
* @param {VegaRTETransformOptions} [options] - Transform options.
|
|
8016
|
+
* @returns {RTEBlockquoteBlock} The created blockquote block.
|
|
8017
|
+
*/
|
|
8018
|
+
static from(block, options) {
|
|
8019
|
+
const blockquote = new RTEBlockquoteBlock(block.id, options);
|
|
8020
|
+
if (block.annotations) {
|
|
8021
|
+
RTEBlock.convertAnnotationsToMap(blockquote.annotationMap, block.annotations);
|
|
8022
|
+
}
|
|
8023
|
+
blockquote.children = RTEBlock.generateBlockChildren(block.children, options, blockquote);
|
|
8024
|
+
return blockquote;
|
|
8025
|
+
}
|
|
8026
|
+
/**
|
|
8027
|
+
* @inheritDoc
|
|
8028
|
+
*/
|
|
8029
|
+
clone(parent) {
|
|
8030
|
+
const cloned = new RTEBlockquoteBlock(this.id);
|
|
8031
|
+
const clonedAsChildParent = cloned;
|
|
8032
|
+
cloned.children = this.children.map((child) => child.clone(clonedAsChildParent));
|
|
8033
|
+
cloned.annotationMap = this.cloneAnnotations();
|
|
8034
|
+
cloned.parent = parent;
|
|
8035
|
+
return cloned;
|
|
8036
|
+
}
|
|
8037
|
+
/**
|
|
8038
|
+
* @inheritDoc
|
|
8039
|
+
*/
|
|
8040
|
+
getLastNode() {
|
|
8041
|
+
return this.children[this.children.length - 1].getLastNode();
|
|
8042
|
+
}
|
|
8043
|
+
/**
|
|
8044
|
+
* @inheritDoc
|
|
8045
|
+
*/
|
|
8046
|
+
isNotEmpty() {
|
|
8047
|
+
return this.children.length > 0;
|
|
8048
|
+
}
|
|
8049
|
+
/**
|
|
8050
|
+
* @inheritDoc
|
|
8051
|
+
*/
|
|
8052
|
+
toHtml(options) {
|
|
8053
|
+
const attrStr = this.generateAttributeString(options);
|
|
8054
|
+
const inner = this.children.map((child) => child.toHtml(options)).join('');
|
|
8055
|
+
return `<blockquote${attrStr}>${inner}</blockquote>`;
|
|
8056
|
+
}
|
|
8057
|
+
/**
|
|
8058
|
+
* @inheritDoc
|
|
8059
|
+
*/
|
|
8060
|
+
toJSON() {
|
|
8061
|
+
return Object.assign(Object.assign({}, super.toJSON()), { id: this.id, type: 'blockquote', children: this.children.map((child) => child.toJSON()) });
|
|
8062
|
+
}
|
|
8063
|
+
}
|
|
8064
|
+
RTEBlockquoteBlock.dtoName = 'RTEBlockquoteBlock';
|
|
8065
|
+
|
|
8066
|
+
/**
|
|
8067
|
+
* Wrap the selected blocks into a new `<blockquote>` block while preserving
|
|
8068
|
+
* their order.
|
|
8069
|
+
*/
|
|
8070
|
+
class BlockTransformToBlockquoteStrategy extends ActionHandleStrategy {
|
|
8071
|
+
/**
|
|
8072
|
+
* @inheritDoc
|
|
8073
|
+
*/
|
|
8074
|
+
handleAction(action, target) {
|
|
8075
|
+
const selectedBlocks = action.selectedBlocks;
|
|
8076
|
+
const children = target.children;
|
|
8077
|
+
const selectedSet = new Set(selectedBlocks);
|
|
8078
|
+
const firstIndex = children.indexOf(selectedBlocks[0]);
|
|
8079
|
+
// The selected blocks may not be contiguous in `children` (e.g. a table
|
|
8080
|
+
// sitting between two selected paragraphs must stay untouched), so the
|
|
8081
|
+
// insert position is the first selected block's index, adjusted for any
|
|
8082
|
+
// non-selected siblings that come before it.
|
|
8083
|
+
const insertIndex = children
|
|
8084
|
+
.slice(0, firstIndex)
|
|
8085
|
+
.filter((block) => !selectedSet.has(block)).length;
|
|
8086
|
+
const blockquote = new RTEBlockquoteBlock(generateUUID());
|
|
8087
|
+
blockquote.parent = target;
|
|
8088
|
+
blockquote.children = selectedBlocks;
|
|
8089
|
+
selectedBlocks.forEach((block) => {
|
|
8090
|
+
block.parent = blockquote;
|
|
8091
|
+
});
|
|
8092
|
+
// Remove selected blocks individually rather than assuming a single
|
|
8093
|
+
// contiguous run, so non-selected siblings interleaved with the
|
|
8094
|
+
// selection (e.g. a table) are preserved instead of being spliced away.
|
|
8095
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
8096
|
+
if (selectedSet.has(children[i])) {
|
|
8097
|
+
children.splice(i, 1);
|
|
8098
|
+
}
|
|
8099
|
+
}
|
|
8100
|
+
children.splice(insertIndex, 0, blockquote);
|
|
8101
|
+
}
|
|
8102
|
+
}
|
|
8103
|
+
|
|
8104
|
+
/**
|
|
8105
|
+
* Unwrap a `<blockquote>` — lift its children to the parent in their original
|
|
8106
|
+
* order and remove the blockquote.
|
|
8107
|
+
*/
|
|
8108
|
+
class RemoveBlockquoteStrategy extends ActionHandleStrategy {
|
|
8109
|
+
/**
|
|
8110
|
+
* @inheritDoc
|
|
8111
|
+
*/
|
|
8112
|
+
handleAction(_action, target) {
|
|
8113
|
+
const parent = target.parent;
|
|
8114
|
+
const childrenToLift = [...target.children];
|
|
8115
|
+
if (childrenToLift.length > 0) {
|
|
8116
|
+
parent.apply(new InsertChildrenBeforeAction(target, ...childrenToLift));
|
|
8117
|
+
}
|
|
8118
|
+
parent.apply(new RemoveChildrenAction(target));
|
|
8119
|
+
}
|
|
8120
|
+
}
|
|
8121
|
+
|
|
8122
|
+
/**
|
|
8123
|
+
* Lets the caret exit the blockquote via the keyboard. Pressing Enter on an empty
|
|
8124
|
+
* trailing paragraph inside a blockquote (i.e. the second Enter press at the end of
|
|
8125
|
+
* the quote) moves that empty paragraph out to a new paragraph after the blockquote,
|
|
8126
|
+
* instead of stacking another empty line inside the quote.
|
|
8127
|
+
*/
|
|
8128
|
+
class BlockquoteExitOnEnterInterceptor extends ActionHandlerInterceptor {
|
|
8129
|
+
/**
|
|
8130
|
+
* Intercept only when the caret sits in an empty paragraph that is the last child of a blockquote.
|
|
8131
|
+
*
|
|
8132
|
+
* @param {InsertNewParagraphAction} _action - the action to be intercepted
|
|
8133
|
+
* @param {RTETextBlock} target - the paragraph block receiving the Enter key press
|
|
8134
|
+
* @returns {boolean} - whether the action should be intercepted
|
|
8135
|
+
*/
|
|
8136
|
+
canIntercept(_action, target) {
|
|
8137
|
+
const parent = target.parent;
|
|
8138
|
+
return (parent instanceof RTEBlockquoteBlock &&
|
|
8139
|
+
!target.isNotEmpty() &&
|
|
8140
|
+
parent.children[parent.children.length - 1] === target);
|
|
8141
|
+
}
|
|
8142
|
+
/**
|
|
8143
|
+
* Move the empty paragraph out of the blockquote and place a new paragraph after it.
|
|
8144
|
+
*
|
|
8145
|
+
* @param {ActionHandlerExecution} _execution - the original execution function
|
|
8146
|
+
* @param {InsertNewParagraphAction} action - the action containing insert metadata
|
|
8147
|
+
* @param {RTETextBlock} target - the empty paragraph block to move out of the blockquote
|
|
8148
|
+
*/
|
|
8149
|
+
intercept(_execution, action, target) {
|
|
8150
|
+
const blockquote = target.parent;
|
|
8151
|
+
const newParagraph = this.createNewParagraph('');
|
|
8152
|
+
if (newParagraph) {
|
|
8153
|
+
// Insert the replacement paragraph after the blockquote before removing the
|
|
8154
|
+
// empty one so the blockquote is still present as an anchor for the insert.
|
|
8155
|
+
blockquote.parent.apply(new InsertChildrenAfterAction(blockquote, newParagraph));
|
|
8156
|
+
blockquote.apply(new RemoveChildrenAction(target));
|
|
8157
|
+
action.newBlock = newParagraph;
|
|
8158
|
+
}
|
|
8159
|
+
}
|
|
8160
|
+
/**
|
|
8161
|
+
* Create a new empty paragraph block.
|
|
8162
|
+
*
|
|
8163
|
+
* @param {string} text - paragraph text
|
|
8164
|
+
* @returns {Nullable<RTETextBlock>} - paragraph block when paragraph dto class is available
|
|
8165
|
+
*/
|
|
8166
|
+
createNewParagraph(text) {
|
|
8167
|
+
const dtoClass = RTEDTOClassManager.getRTEBlockClass('paragraph');
|
|
8168
|
+
if (dtoClass) {
|
|
8169
|
+
return dtoClass.from({
|
|
8170
|
+
id: generateUUID(),
|
|
8171
|
+
type: 'paragraph',
|
|
8172
|
+
nodes: [{ id: generateUUID(), type: 'text', text }],
|
|
8173
|
+
});
|
|
8174
|
+
}
|
|
8175
|
+
}
|
|
8176
|
+
}
|
|
8177
|
+
|
|
8178
|
+
/**
|
|
8179
|
+
* Action type discriminator. Plain string constant — intentionally NOT added
|
|
8180
|
+
* to {@link ModifyContentActionType} enum to keep the extension self-contained.
|
|
8181
|
+
*/
|
|
8182
|
+
const TRANSFORM_TO_BLOCKQUOTE = 'TRANSFORM_TO_BLOCKQUOTE';
|
|
8183
|
+
/**
|
|
8184
|
+
* Wrap the supplied blocks into a new `<blockquote>` block while preserving
|
|
8185
|
+
* their order. Strategy reads `selectedBlocks` and replaces them in-place
|
|
8186
|
+
* with a single `RTEBlockquoteBlock` that adopts them as children.
|
|
8187
|
+
*/
|
|
8188
|
+
class TransformToBlockquoteAction extends ModifyContentAction {
|
|
8189
|
+
constructor(selectedBlocks) {
|
|
8190
|
+
super();
|
|
8191
|
+
this.isFlushable = true;
|
|
8192
|
+
this.type = TRANSFORM_TO_BLOCKQUOTE;
|
|
8193
|
+
this.selectedBlocks = selectedBlocks;
|
|
8194
|
+
}
|
|
8195
|
+
}
|
|
8196
|
+
|
|
8197
|
+
/**
|
|
8198
|
+
* Action type discriminator. Plain string constant — intentionally NOT added
|
|
8199
|
+
* to {@link ModifyContentActionType} enum to keep the extension self-contained.
|
|
8200
|
+
*/
|
|
8201
|
+
const REMOVE_BLOCKQUOTE = 'REMOVE_BLOCKQUOTE';
|
|
8202
|
+
/**
|
|
8203
|
+
* Unwrap a `<blockquote>` block — lift its children into the parent in place
|
|
8204
|
+
* and remove the blockquote wrapper. Targets the blockquote block itself.
|
|
8205
|
+
*/
|
|
8206
|
+
class RemoveBlockquoteAction extends ModifyContentAction {
|
|
8207
|
+
constructor() {
|
|
8208
|
+
super(...arguments);
|
|
8209
|
+
this.isFlushable = true;
|
|
8210
|
+
this.type = REMOVE_BLOCKQUOTE;
|
|
8211
|
+
}
|
|
8212
|
+
}
|
|
8213
|
+
|
|
8214
|
+
/**
|
|
8215
|
+
* Claim every `<blockquote>` element encountered during HTML → DTO conversion
|
|
8216
|
+
* and produce an {@link RTEBlockquoteBlock}.
|
|
8217
|
+
*
|
|
8218
|
+
* Nested `<blockquote>` elements are flattened to a single level in
|
|
8219
|
+
* {@link appendChildBlocks}: any direct child that arrives as another
|
|
8220
|
+
* `<blockquote>` is replaced by its own children, lifting them one level up.
|
|
8221
|
+
*/
|
|
8222
|
+
class BlockquoteToBlockquoteBlockStrategy extends ElementToDTOStrategy {
|
|
8223
|
+
/**
|
|
8224
|
+
* @inheritDoc
|
|
8225
|
+
*/
|
|
8226
|
+
canHandle(elementsArray, currentIndex) {
|
|
8227
|
+
return elementsArray[currentIndex].tagName === 'BLOCKQUOTE' ? 1 : 0;
|
|
8228
|
+
}
|
|
8229
|
+
/**
|
|
8230
|
+
* @inheritDoc
|
|
8231
|
+
*/
|
|
8232
|
+
handle(elementsArray, options) {
|
|
8233
|
+
const element = elementsArray[0];
|
|
8234
|
+
const annotations = htmlElementToAnnotationGenerator.generate(RTEBlockquoteBlock, element, options);
|
|
8235
|
+
return RTEBlockquoteBlock.from({
|
|
8236
|
+
id: generateUUID(),
|
|
8237
|
+
type: 'blockquote',
|
|
8238
|
+
annotations,
|
|
8239
|
+
children: [],
|
|
8240
|
+
}, options);
|
|
8241
|
+
}
|
|
8242
|
+
/**
|
|
8243
|
+
* Flatten nested `<blockquote>` elements: any direct child that is itself
|
|
8244
|
+
* an {@link RTEBlockquoteBlock} is replaced by its own children, lifting
|
|
8245
|
+
* them one level up.
|
|
8246
|
+
*
|
|
8247
|
+
* @inheritDoc
|
|
8248
|
+
*/
|
|
8249
|
+
appendChildBlocks(currentBlock, childBlocks) {
|
|
8250
|
+
const flattened = childBlocks.flatMap((child) => child instanceof RTEBlockquoteBlock ? child.children : [child]);
|
|
8251
|
+
currentBlock.children = flattened.map((block) => {
|
|
8252
|
+
block.parent = currentBlock;
|
|
8253
|
+
return block;
|
|
8254
|
+
});
|
|
8255
|
+
}
|
|
8256
|
+
/**
|
|
8257
|
+
* @inheritDoc
|
|
8258
|
+
*/
|
|
8259
|
+
shouldProceedToElementChildren() {
|
|
8260
|
+
return true;
|
|
8261
|
+
}
|
|
8262
|
+
}
|
|
8263
|
+
|
|
8264
|
+
/**
|
|
8265
|
+
* Renderer for blockquote blocks in the rich text editor.
|
|
8266
|
+
*/
|
|
8267
|
+
class RTEBlockquoteBlockRenderer extends VegaRTEExtensionRenderer {
|
|
8268
|
+
/**
|
|
8269
|
+
* @inheritDoc
|
|
8270
|
+
*/
|
|
8271
|
+
doRender(createElement, block, rteContext) {
|
|
8272
|
+
var _a;
|
|
8273
|
+
return createElement('blockquote', Object.assign({ key: block.id, class: block.getClasses(rteContext), style: block.getStyles(rteContext), ref: this.useRef(block) }, (_a = block.getCustomAttributesAnnotation()) === null || _a === void 0 ? void 0 : _a.render()));
|
|
8274
|
+
}
|
|
8275
|
+
/**
|
|
8276
|
+
* @inheritDoc
|
|
8277
|
+
*/
|
|
8278
|
+
injectRTEScopeStyles() {
|
|
8279
|
+
return BlockquoteAnnotation.getDefaultCssText();
|
|
8280
|
+
}
|
|
8281
|
+
}
|
|
8282
|
+
const RTEBlockquoteBlockRenderer$1 = new RTEBlockquoteBlockRenderer();
|
|
8283
|
+
|
|
8284
|
+
// FontAwesome 7.2.0 — `quote-left` (solid).
|
|
8285
|
+
const QuoteLeftIcon = {
|
|
8286
|
+
icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M0 216C0 149.7 53.7 96 120 96l8 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-8 0c-30.9 0-56 25.1-56 56l0 8 64 0c35.3 0 64 28.7 64 64l0 64c0 35.3-28.7 64-64 64l-64 0c-35.3 0-64-28.7-64-64L0 216zm256 0c0-66.3 53.7-120 120-120l8 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-8 0c-30.9 0-56 25.1-56 56l0 8 64 0c35.3 0 64 28.7 64 64l0 64c0 35.3-28.7 64-64 64l-64 0c-35.3 0-64-28.7-64-64l0-136z"/></svg>`,
|
|
8287
|
+
};
|
|
8288
|
+
|
|
8289
|
+
/**
|
|
8290
|
+
* Toolbar button renderer for the blockquote extension.
|
|
8291
|
+
*
|
|
8292
|
+
* Behaviors implemented:
|
|
8293
|
+
* - Active (`selected`) when the caret/selection is inside any blockquote
|
|
8294
|
+
* ancestor.
|
|
8295
|
+
* - Click wraps the current block(s) into a new `<blockquote>`, or unwraps
|
|
8296
|
+
* the nearest blockquote ancestor when active. Inside a list, the entire
|
|
8297
|
+
* list is wrapped (escalating to the outermost list block). With no
|
|
8298
|
+
* selection, a new empty blockquote is appended at the end and focused.
|
|
8299
|
+
* - Disabled when the wrap container does not support the wrap strategy
|
|
8300
|
+
* (e.g. inside a code block or function block); the button remains
|
|
8301
|
+
* enabled inside a blockquote so it can be toggled off, and with no
|
|
8302
|
+
* selection so it can append a new blockquote.
|
|
8303
|
+
* - Hidden in source-edit mode (inherited from {@link VegaRTEToolbarButtonRenderer}).
|
|
8304
|
+
*/
|
|
8305
|
+
class RTEBlockquoteToolbarButtonRenderer extends VegaRTEToolbarButtonRenderer {
|
|
8306
|
+
/**
|
|
8307
|
+
* @inheritDoc
|
|
8308
|
+
*/
|
|
8309
|
+
render(createElement, editorContext) {
|
|
8310
|
+
return this.renderButton(createElement, editorContext, {
|
|
8311
|
+
icon: 'quote-left',
|
|
8312
|
+
tooltip: { text: 'Block quote' },
|
|
8313
|
+
});
|
|
8314
|
+
}
|
|
8315
|
+
/**
|
|
8316
|
+
* @inheritDoc
|
|
8317
|
+
*/
|
|
8318
|
+
isSelected(editorContext) {
|
|
8319
|
+
const startNode = this.getStartNode(editorContext);
|
|
8320
|
+
return startNode ? this.findBlockquoteAncestor(startNode) !== null : false;
|
|
8321
|
+
}
|
|
8322
|
+
/**
|
|
8323
|
+
* @inheritDoc
|
|
8324
|
+
*/
|
|
8325
|
+
isDisabled(editorContext) {
|
|
8326
|
+
if (editorContext.host.disabled) {
|
|
8327
|
+
return true;
|
|
8328
|
+
}
|
|
8329
|
+
const startNode = this.getStartNode(editorContext);
|
|
8330
|
+
// No selection — click appends a new blockquote at the end.
|
|
8331
|
+
// Inside a blockquote — click toggles it off.
|
|
8332
|
+
// Either way, the button stays enabled.
|
|
8333
|
+
if (!startNode || this.findBlockquoteAncestor(startNode) !== null) {
|
|
8334
|
+
return false;
|
|
8335
|
+
}
|
|
8336
|
+
const { container } = this.resolveWrapAnchor(startNode.parent);
|
|
8337
|
+
return !ActionHandleStrategyRegistry.canHandle(TRANSFORM_TO_BLOCKQUOTE, container);
|
|
8338
|
+
}
|
|
8339
|
+
/**
|
|
8340
|
+
* @inheritDoc
|
|
8341
|
+
*/
|
|
8342
|
+
handleButtonClick(editorContext) {
|
|
8343
|
+
const context = this.getExtensionContext(editorContext.host);
|
|
8344
|
+
if (context) {
|
|
8345
|
+
const { host, getSelectedNodes } = context;
|
|
8346
|
+
const selectedNodes = getSelectedNodes();
|
|
8347
|
+
const startNode = selectedNodes[0];
|
|
8348
|
+
if (!startNode) {
|
|
8349
|
+
// No active selection → append a new empty blockquote at the end (mirrors text-style toolbar).
|
|
8350
|
+
this.appendEmptyBlockquote(host.value);
|
|
8351
|
+
return;
|
|
8352
|
+
}
|
|
8353
|
+
// Direction is decided by the first selected block only.
|
|
8354
|
+
if (this.findBlockquoteAncestor(startNode)) {
|
|
8355
|
+
const blockquoteAncestors = this.collectBlockquoteAncestors(selectedNodes);
|
|
8356
|
+
blockquoteAncestors.forEach((blockquote) => {
|
|
8357
|
+
blockquote.apply(new RemoveBlockquoteAction());
|
|
8358
|
+
});
|
|
8359
|
+
}
|
|
8360
|
+
else {
|
|
8361
|
+
this.wrapSelectedBlocks(selectedNodes);
|
|
8362
|
+
}
|
|
8363
|
+
host.value.apply(new UpdateCursorPositionAction(startNode, editorContext.rteRange.startOffset));
|
|
8364
|
+
}
|
|
8365
|
+
}
|
|
8366
|
+
/* istanbul ignore next */
|
|
8367
|
+
/**
|
|
8368
|
+
* @inheritDoc
|
|
8369
|
+
*/
|
|
8370
|
+
handleDropdownClick() {
|
|
8371
|
+
// No dropdown — single toggle button.
|
|
8372
|
+
}
|
|
8373
|
+
/**
|
|
8374
|
+
* Append an empty paragraph wrapped in a new blockquote to the end of the
|
|
8375
|
+
* content and place the caret inside it.
|
|
8376
|
+
*
|
|
8377
|
+
* @param {VegaRTEContent} content - Editor content to append to.
|
|
8378
|
+
*/
|
|
8379
|
+
appendEmptyBlockquote(content) {
|
|
8380
|
+
if (content.children.length > 0) {
|
|
8381
|
+
const lastBlock = content.children[content.children.length - 1];
|
|
8382
|
+
const newBlock = RTETextBlock.from({
|
|
8383
|
+
id: generateUUID(),
|
|
8384
|
+
type: 'paragraph',
|
|
8385
|
+
nodes: [{ id: generateUUID(), type: 'text', text: '' }],
|
|
8386
|
+
});
|
|
8387
|
+
content.apply(new InsertChildrenAfterAction(lastBlock, newBlock));
|
|
8388
|
+
content.apply(new TransformToBlockquoteAction([newBlock]));
|
|
8389
|
+
content.apply(new UpdateCursorPositionAction(newBlock.getLastNode(), 0));
|
|
8390
|
+
}
|
|
8391
|
+
}
|
|
8392
|
+
/**
|
|
8393
|
+
* Wrap the selection's unique parent blocks into a new blockquote. Blocks
|
|
8394
|
+
* inside a list or other nested container escalate to the top-level block.
|
|
8395
|
+
* When the selection spans multiple top-level (root) blocks, any scoped
|
|
8396
|
+
* container touched along the way (e.g. a table cell) is treated as an
|
|
8397
|
+
* opaque block instead of an independent wrap target — the whole
|
|
8398
|
+
* selection wraps as one, and any blockquote nested deeper than the
|
|
8399
|
+
* top-level block (e.g. inside a table cell) is left untouched. Existing
|
|
8400
|
+
* top-level blockquote blocks in the selection are merged (flattened)
|
|
8401
|
+
* into the new one rather than nested.
|
|
8402
|
+
*
|
|
8403
|
+
* @param {RTENode[]} selectedNodes - Currently selected nodes.
|
|
8404
|
+
*/
|
|
8405
|
+
wrapSelectedBlocks(selectedNodes) {
|
|
8406
|
+
var _a, _b;
|
|
8407
|
+
const uniqueBlocks = this.getUniqueBlocks(selectedNodes);
|
|
8408
|
+
// If the selection spans more than one top-level block, force every
|
|
8409
|
+
// anchor resolution to escalate all the way to the root, ignoring any
|
|
8410
|
+
// scoped container (e.g. a table cell) it passes through.
|
|
8411
|
+
const rootAncestors = new Set(uniqueBlocks.map((block) => { var _a; return (_a = this.resolveWrapAnchor(block, true).wrapBlock) !== null && _a !== void 0 ? _a : block; }));
|
|
8412
|
+
const forceRootScope = rootAncestors.size > 1;
|
|
8413
|
+
// Pass 1: unwrap any existing blockquote reachable from the selection so it merges instead of nesting.
|
|
8414
|
+
const alreadyUnwrapped = new Set();
|
|
8415
|
+
for (const block of uniqueBlocks) {
|
|
8416
|
+
const { wrapBlock } = this.resolveWrapAnchor(block, forceRootScope);
|
|
8417
|
+
if (wrapBlock instanceof RTEBlockquoteBlock && !alreadyUnwrapped.has(wrapBlock)) {
|
|
8418
|
+
alreadyUnwrapped.add(wrapBlock);
|
|
8419
|
+
wrapBlock.apply(new RemoveBlockquoteAction());
|
|
8420
|
+
}
|
|
8421
|
+
}
|
|
8422
|
+
// Pass 2: group targets by their resolved container so each container is wrapped independently.
|
|
8423
|
+
const containerMap = new Map();
|
|
8424
|
+
for (const block of uniqueBlocks) {
|
|
8425
|
+
const anchor = this.resolveWrapAnchor(block, forceRootScope);
|
|
8426
|
+
const { container } = anchor;
|
|
8427
|
+
if (container) {
|
|
8428
|
+
const target = (_a = anchor.wrapBlock) !== null && _a !== void 0 ? _a : block;
|
|
8429
|
+
const targets = (_b = containerMap.get(container)) !== null && _b !== void 0 ? _b : new Set();
|
|
8430
|
+
targets.add(target);
|
|
8431
|
+
containerMap.set(container, targets);
|
|
8432
|
+
}
|
|
8433
|
+
}
|
|
8434
|
+
for (const [container, targets] of containerMap) {
|
|
8435
|
+
container.apply(new TransformToBlockquoteAction([...targets]));
|
|
8436
|
+
}
|
|
8437
|
+
}
|
|
8438
|
+
/**
|
|
8439
|
+
* Resolve the starting node of the current toolbar selection, if any.
|
|
8440
|
+
*
|
|
8441
|
+
* @param {VegaRTEToolbarRenderContext} editorContext - Render context.
|
|
8442
|
+
* @returns {Nullable<RTENode>} The first selected node, or null.
|
|
8443
|
+
*/
|
|
8444
|
+
getStartNode(editorContext) {
|
|
8445
|
+
const context = this.getExtensionContext(editorContext.host);
|
|
8446
|
+
return context === null || context === void 0 ? void 0 : context.getSelectedNodes()[0];
|
|
8447
|
+
}
|
|
8448
|
+
/**
|
|
8449
|
+
* Collect the unique closest blockquote ancestors of every selected node.
|
|
8450
|
+
* Multiple selected nodes that share the same blockquote ancestor collapse
|
|
8451
|
+
* into a single entry, so applying {@link RemoveBlockquoteAction} to each
|
|
8452
|
+
* result unwraps every distinct blockquote in the selection exactly once.
|
|
8453
|
+
*
|
|
8454
|
+
* @param {RTENode[]} nodes - Selected nodes.
|
|
8455
|
+
* @returns {Set<RTEBlockquoteBlock>} Unique blockquote ancestors, preserving insertion order.
|
|
8456
|
+
*/
|
|
8457
|
+
collectBlockquoteAncestors(nodes) {
|
|
8458
|
+
const ancestors = new Set();
|
|
8459
|
+
nodes.forEach((node) => {
|
|
8460
|
+
const ancestor = this.findBlockquoteAncestor(node);
|
|
8461
|
+
if (ancestor) {
|
|
8462
|
+
ancestors.add(ancestor);
|
|
8463
|
+
}
|
|
8464
|
+
});
|
|
8465
|
+
return ancestors;
|
|
8466
|
+
}
|
|
8467
|
+
/**
|
|
8468
|
+
* Walk up the block ancestors of the supplied node looking for an
|
|
8469
|
+
* {@link RTEBlockquoteBlock}.
|
|
8470
|
+
*
|
|
8471
|
+
* @param {RTENode} node - Starting node.
|
|
8472
|
+
* @returns {Nullable<RTEBlockquoteBlock>} The closest blockquote ancestor, or null.
|
|
8473
|
+
*/
|
|
8474
|
+
findBlockquoteAncestor(node) {
|
|
8475
|
+
let current = node.parent;
|
|
8476
|
+
while (current && !(current instanceof VegaRTEContent)) {
|
|
8477
|
+
if (current instanceof RTEBlockquoteBlock) {
|
|
8478
|
+
return current;
|
|
8479
|
+
}
|
|
8480
|
+
current = current.parent;
|
|
8481
|
+
}
|
|
8482
|
+
return null;
|
|
8483
|
+
}
|
|
8484
|
+
/**
|
|
8485
|
+
* Collect unique block ancestors (immediate parent blocks) from a list of
|
|
8486
|
+
* selected nodes, preserving order of first appearance.
|
|
8487
|
+
*
|
|
8488
|
+
* @param {RTENode[]} nodes - Selected nodes.
|
|
8489
|
+
* @returns {RTEBlock[]} Unique parent blocks.
|
|
8490
|
+
*/
|
|
8491
|
+
getUniqueBlocks(nodes) {
|
|
8492
|
+
const seen = new Set();
|
|
8493
|
+
const result = [];
|
|
8494
|
+
nodes.forEach((node) => {
|
|
8495
|
+
const block = node.parent;
|
|
8496
|
+
if (block && !seen.has(block)) {
|
|
8497
|
+
seen.add(block);
|
|
8498
|
+
result.push(block);
|
|
8499
|
+
}
|
|
8500
|
+
});
|
|
8501
|
+
return result;
|
|
8502
|
+
}
|
|
8503
|
+
/**
|
|
8504
|
+
* Walk up from `block` to find the nearest ancestor that is a direct child
|
|
8505
|
+
* of a container registered to handle {@link TRANSFORM_TO_BLOCKQUOTE}
|
|
8506
|
+
* (e.g. {@link VegaRTEContent}, {@link RTETableCellBlock}).
|
|
8507
|
+
*
|
|
8508
|
+
* Stops as soon as the parent can handle the wrap action, so that blocks
|
|
8509
|
+
* inside a table cell are scoped to the cell (not escalated to the root)
|
|
8510
|
+
* while blocks inside a list are still escalated to the top-level list.
|
|
8511
|
+
* Pass `forceRootScope` to ignore scoped containers and always escalate
|
|
8512
|
+
* to the root — used when the overall selection spans multiple top-level
|
|
8513
|
+
* blocks, so a table (or any other scoped container) is wrapped as a
|
|
8514
|
+
* single opaque block alongside its siblings instead of independently.
|
|
8515
|
+
*
|
|
8516
|
+
* @param {RTEBlock} block - Immediate parent block of the selected node.
|
|
8517
|
+
* @param {boolean} [forceRootScope] - Ignore scoped containers and escalate to the root.
|
|
8518
|
+
* @returns {WrapAnchor} `wrapBlock` is the top-level ancestor when the block
|
|
8519
|
+
* is nested (otherwise null); `container` is the nearest wrap-capable parent.
|
|
8520
|
+
*/
|
|
8521
|
+
resolveWrapAnchor(block, forceRootScope = false) {
|
|
8522
|
+
let current = block;
|
|
8523
|
+
while (current.parent &&
|
|
8524
|
+
!(current.parent instanceof VegaRTEContent) &&
|
|
8525
|
+
(forceRootScope ||
|
|
8526
|
+
!ActionHandleStrategyRegistry.canHandle(TRANSFORM_TO_BLOCKQUOTE, current.parent))) {
|
|
8527
|
+
current = current.parent;
|
|
8528
|
+
}
|
|
8529
|
+
const wrapBlock = current !== block ? current : null;
|
|
8530
|
+
const container = current.parent;
|
|
8531
|
+
return { wrapBlock, container };
|
|
8532
|
+
}
|
|
8533
|
+
}
|
|
8534
|
+
(() => {
|
|
8535
|
+
VegaInternalIconManager.register({ 'quote-left': QuoteLeftIcon });
|
|
8536
|
+
})();
|
|
8537
|
+
|
|
8538
|
+
/**
|
|
8539
|
+
* Filter styles strategy for blockquote blocks.
|
|
8540
|
+
* Strips default blockquote visual styles during output and refills them during input,
|
|
8541
|
+
* ensuring source edit round-trips do not lose information.
|
|
8542
|
+
*/
|
|
8543
|
+
class BlockquoteFilterStylesStrategy extends RTEFilterStylesStrategy {
|
|
8544
|
+
constructor() {
|
|
8545
|
+
super(...arguments);
|
|
8546
|
+
this.stylesToBeFiltered = BLOCKQUOTE_DEFAULT_STYLES;
|
|
8547
|
+
this.elementTagName = 'blockquote';
|
|
8548
|
+
}
|
|
8549
|
+
/**
|
|
8550
|
+
* Checks if the given block dto class name corresponds to a blockquote block.
|
|
8551
|
+
*
|
|
8552
|
+
* @param {string} className The class name to check.
|
|
8553
|
+
* @returns {boolean} True if the class name corresponds to a blockquote block, false otherwise.
|
|
8554
|
+
*/
|
|
8555
|
+
static isBlockquoteBlock(className) {
|
|
8556
|
+
return className === 'RTEBlockquoteBlock';
|
|
8557
|
+
}
|
|
8558
|
+
/**
|
|
8559
|
+
* @inheritDoc
|
|
8560
|
+
*/
|
|
8561
|
+
canFilter(blockOrNodeInstance) {
|
|
8562
|
+
return BlockquoteFilterStylesStrategy.isBlockquoteBlock(blockOrNodeInstance.dtoName);
|
|
8563
|
+
}
|
|
8564
|
+
/**
|
|
8565
|
+
* @inheritDoc
|
|
8566
|
+
*/
|
|
8567
|
+
canRefill(_blockOrNodeClassName, element) {
|
|
8568
|
+
return element.tagName.toLowerCase() === this.elementTagName;
|
|
8569
|
+
}
|
|
8570
|
+
/**
|
|
8571
|
+
* @inheritDoc
|
|
8572
|
+
*/
|
|
8573
|
+
toCssText() {
|
|
8574
|
+
return this.buildCssTextForStyles(this.elementTagName, this.classNameToBeAdded);
|
|
8575
|
+
}
|
|
8576
|
+
}
|
|
8577
|
+
|
|
8578
|
+
/**
|
|
8579
|
+
* Adds `<blockquote>` support to the Vega Rich Text Editor.
|
|
8580
|
+
*/
|
|
8581
|
+
class VegaRTEBlockquoteExtension extends VegaRTEExtension {
|
|
8582
|
+
constructor() {
|
|
8583
|
+
super();
|
|
8584
|
+
this.toolbarButtonRenderer = new RTEBlockquoteToolbarButtonRenderer();
|
|
8585
|
+
this.registerBlockBasicStrategies(RTEBlockquoteBlock.name);
|
|
8586
|
+
this.registerNode('blockquote', RTEBlockquoteBlock);
|
|
8587
|
+
this.registerRenderer('blockquote', RTEBlockquoteBlockRenderer$1);
|
|
8588
|
+
this.registerToolbarButtonRenderer(VegaRTEBlockquoteExtension.TOOLBAR_BUTTON_KEY, this.toolbarButtonRenderer);
|
|
8589
|
+
this.registerElementToBlockDTOClassStrategy(new BlockquoteToBlockquoteBlockStrategy());
|
|
8590
|
+
// Register filter styles strategy for source edit round-trip.
|
|
8591
|
+
VegaRTEExtension.registerFilterStylesStrategy(RTEBlockquoteBlock.dtoName, new BlockquoteFilterStylesStrategy());
|
|
8592
|
+
}
|
|
8593
|
+
}
|
|
8594
|
+
(() => {
|
|
8595
|
+
const wrapStrategy = new BlockTransformToBlockquoteStrategy();
|
|
8596
|
+
// Root container — wrap top-level blocks.
|
|
8597
|
+
VegaRTEExtension.registerActionHandleStrategy(VegaRTEContent.name, TRANSFORM_TO_BLOCKQUOTE, wrapStrategy);
|
|
8598
|
+
// Table cells (body + header) — wrap is scoped to the closest cell.
|
|
8599
|
+
// Other container blocks (code, list item, function) are intentionally
|
|
8600
|
+
// not registered, so the toolbar stays disabled inside them.
|
|
8601
|
+
VegaRTEExtension.registerActionHandleStrategy(RTETableCellBlock.name, TRANSFORM_TO_BLOCKQUOTE, wrapStrategy);
|
|
8602
|
+
VegaRTEExtension.registerActionHandleStrategy(RTETableHeadCellBlock.name, TRANSFORM_TO_BLOCKQUOTE, wrapStrategy);
|
|
8603
|
+
VegaRTEExtension.registerActionHandleStrategy(RTEBlockquoteBlock.name, REMOVE_BLOCKQUOTE, new RemoveBlockquoteStrategy());
|
|
8604
|
+
// Enables exiting the blockquote via the keyboard: pressing Enter on an empty
|
|
8605
|
+
// trailing paragraph breaks out to a new paragraph after the blockquote.
|
|
8606
|
+
VegaRTEExtension.registerActionHandlerInterceptor(RTETextBlock.name, ModifyContentActionType.INSERT_NEW_PARAGRAPH, new BlockquoteExitOnEnterInterceptor());
|
|
8607
|
+
})();
|
|
8608
|
+
VegaRTEBlockquoteExtension.TOOLBAR_BUTTON_KEY = 'blockquote';
|
|
7914
8609
|
|
|
7915
8610
|
const ARROW_CHARACTERS = [
|
|
7916
8611
|
{ character: '←', title: 'leftwards simple arrow' },
|
|
@@ -7937,907 +8632,3084 @@ const ARROW_CHARACTERS = [
|
|
|
7937
8632
|
{ character: '🔝', title: 'top with upwards arrow above' },
|
|
7938
8633
|
];
|
|
7939
8634
|
|
|
7940
|
-
const CURRENCY_CHARACTERS = [
|
|
7941
|
-
{ character: '$', title: 'Dollar sign' },
|
|
7942
|
-
{ character: '€', title: 'Euro sign' },
|
|
7943
|
-
{ character: '¥', title: 'Yen sign' },
|
|
7944
|
-
{ character: '£', title: 'Pound sign' },
|
|
7945
|
-
{ character: '¢', title: 'Cent sign' },
|
|
7946
|
-
{ character: '₠', title: 'Euro-currency sign' },
|
|
7947
|
-
{ character: '₡', title: 'Colon sign' },
|
|
7948
|
-
{ character: '₢', title: 'Cruzeiro sign' },
|
|
7949
|
-
{ character: '₣', title: 'French franc sign' },
|
|
7950
|
-
{ character: '₤', title: 'Lira sign' },
|
|
7951
|
-
{ character: '¤', title: 'Currency sign' },
|
|
7952
|
-
{ character: '₿', title: 'Bitcoin sign' },
|
|
7953
|
-
{ character: '₥', title: 'Mill sign' },
|
|
7954
|
-
{ character: '₦', title: 'Naira sign' },
|
|
7955
|
-
{ character: '₧', title: 'Peseta sign' },
|
|
7956
|
-
{ character: '₨', title: 'Rupee sign' },
|
|
7957
|
-
{ character: '₩', title: 'Won sign' },
|
|
7958
|
-
{ character: '₪', title: 'New sheqel sign' },
|
|
7959
|
-
{ character: '₫', title: 'Dong sign' },
|
|
7960
|
-
{ character: '₭', title: 'Kip sign' },
|
|
7961
|
-
{ character: '₮', title: 'Tugrik sign' },
|
|
7962
|
-
{ character: '₯', title: 'Drachma sign' },
|
|
7963
|
-
{ character: '₰', title: 'German penny sign' },
|
|
7964
|
-
{ character: '₱', title: 'Peso sign' },
|
|
7965
|
-
{ character: '₲', title: 'Guarani sign' },
|
|
7966
|
-
{ character: '₳', title: 'Austral sign' },
|
|
7967
|
-
{ character: '₴', title: 'Hryvnia sign' },
|
|
7968
|
-
{ character: '₵', title: 'Cedi sign' },
|
|
7969
|
-
{ character: '₶', title: 'Livre tournois sign' },
|
|
7970
|
-
{ character: '₷', title: 'Spesmilo sign' },
|
|
7971
|
-
{ character: '₸', title: 'Tenge sign' },
|
|
7972
|
-
{ character: '₹', title: 'Indian rupee sign' },
|
|
7973
|
-
{ character: '₺', title: 'Turkish lira sign' },
|
|
7974
|
-
{ character: '₻', title: 'Nordic mark sign' },
|
|
7975
|
-
{ character: '₼', title: 'Manat sign' },
|
|
7976
|
-
{ character: '₽', title: 'Ruble sign' },
|
|
7977
|
-
];
|
|
8635
|
+
const CURRENCY_CHARACTERS = [
|
|
8636
|
+
{ character: '$', title: 'Dollar sign' },
|
|
8637
|
+
{ character: '€', title: 'Euro sign' },
|
|
8638
|
+
{ character: '¥', title: 'Yen sign' },
|
|
8639
|
+
{ character: '£', title: 'Pound sign' },
|
|
8640
|
+
{ character: '¢', title: 'Cent sign' },
|
|
8641
|
+
{ character: '₠', title: 'Euro-currency sign' },
|
|
8642
|
+
{ character: '₡', title: 'Colon sign' },
|
|
8643
|
+
{ character: '₢', title: 'Cruzeiro sign' },
|
|
8644
|
+
{ character: '₣', title: 'French franc sign' },
|
|
8645
|
+
{ character: '₤', title: 'Lira sign' },
|
|
8646
|
+
{ character: '¤', title: 'Currency sign' },
|
|
8647
|
+
{ character: '₿', title: 'Bitcoin sign' },
|
|
8648
|
+
{ character: '₥', title: 'Mill sign' },
|
|
8649
|
+
{ character: '₦', title: 'Naira sign' },
|
|
8650
|
+
{ character: '₧', title: 'Peseta sign' },
|
|
8651
|
+
{ character: '₨', title: 'Rupee sign' },
|
|
8652
|
+
{ character: '₩', title: 'Won sign' },
|
|
8653
|
+
{ character: '₪', title: 'New sheqel sign' },
|
|
8654
|
+
{ character: '₫', title: 'Dong sign' },
|
|
8655
|
+
{ character: '₭', title: 'Kip sign' },
|
|
8656
|
+
{ character: '₮', title: 'Tugrik sign' },
|
|
8657
|
+
{ character: '₯', title: 'Drachma sign' },
|
|
8658
|
+
{ character: '₰', title: 'German penny sign' },
|
|
8659
|
+
{ character: '₱', title: 'Peso sign' },
|
|
8660
|
+
{ character: '₲', title: 'Guarani sign' },
|
|
8661
|
+
{ character: '₳', title: 'Austral sign' },
|
|
8662
|
+
{ character: '₴', title: 'Hryvnia sign' },
|
|
8663
|
+
{ character: '₵', title: 'Cedi sign' },
|
|
8664
|
+
{ character: '₶', title: 'Livre tournois sign' },
|
|
8665
|
+
{ character: '₷', title: 'Spesmilo sign' },
|
|
8666
|
+
{ character: '₸', title: 'Tenge sign' },
|
|
8667
|
+
{ character: '₹', title: 'Indian rupee sign' },
|
|
8668
|
+
{ character: '₺', title: 'Turkish lira sign' },
|
|
8669
|
+
{ character: '₻', title: 'Nordic mark sign' },
|
|
8670
|
+
{ character: '₼', title: 'Manat sign' },
|
|
8671
|
+
{ character: '₽', title: 'Ruble sign' },
|
|
8672
|
+
];
|
|
8673
|
+
|
|
8674
|
+
const LATIN_CHARACTERS = [
|
|
8675
|
+
{ character: 'Ā', title: 'Latin capital letter a with macron' },
|
|
8676
|
+
{ character: 'ā', title: 'Latin small letter a with macron' },
|
|
8677
|
+
{ character: 'Ă', title: 'Latin capital letter a with breve' },
|
|
8678
|
+
{ character: 'ă', title: 'Latin small letter a with breve' },
|
|
8679
|
+
{ character: 'Ą', title: 'Latin capital letter a with ogonek' },
|
|
8680
|
+
{ character: 'ą', title: 'Latin small letter a with ogonek' },
|
|
8681
|
+
{ character: 'Ć', title: 'Latin capital letter c with acute' },
|
|
8682
|
+
{ character: 'ć', title: 'Latin small letter c with acute' },
|
|
8683
|
+
{ character: 'Ĉ', title: 'Latin capital letter c with circumflex' },
|
|
8684
|
+
{ character: 'ĉ', title: 'Latin small letter c with circumflex' },
|
|
8685
|
+
{ character: 'Ċ', title: 'Latin capital letter c with dot above' },
|
|
8686
|
+
{ character: 'ċ', title: 'Latin small letter c with dot above' },
|
|
8687
|
+
{ character: 'Č', title: 'Latin capital letter c with caron' },
|
|
8688
|
+
{ character: 'č', title: 'Latin small letter c with caron' },
|
|
8689
|
+
{ character: 'Ď', title: 'Latin capital letter d with caron' },
|
|
8690
|
+
{ character: 'ď', title: 'Latin small letter d with caron' },
|
|
8691
|
+
{ character: 'Đ', title: 'Latin capital letter d with stroke' },
|
|
8692
|
+
{ character: 'đ', title: 'Latin small letter d with stroke' },
|
|
8693
|
+
{ character: 'Ē', title: 'Latin capital letter e with macron' },
|
|
8694
|
+
{ character: 'ē', title: 'Latin small letter e with macron' },
|
|
8695
|
+
{ character: 'Ĕ', title: 'Latin capital letter e with breve' },
|
|
8696
|
+
{ character: 'ĕ', title: 'Latin small letter e with breve' },
|
|
8697
|
+
{ character: 'Ė', title: 'Latin capital letter e with dot above' },
|
|
8698
|
+
{ character: 'ė', title: 'Latin small letter e with dot above' },
|
|
8699
|
+
{ character: 'Ę', title: 'Latin capital letter e with ogonek' },
|
|
8700
|
+
{ character: 'ę', title: 'Latin small letter e with ogonek' },
|
|
8701
|
+
{ character: 'Ě', title: 'Latin capital letter e with caron' },
|
|
8702
|
+
{ character: 'ě', title: 'Latin small letter e with caron' },
|
|
8703
|
+
{ character: 'Ĝ', title: 'Latin capital letter g with circumflex' },
|
|
8704
|
+
{ character: 'ĝ', title: 'Latin small letter g with circumflex' },
|
|
8705
|
+
{ character: 'Ğ', title: 'Latin capital letter g with breve' },
|
|
8706
|
+
{ character: 'ğ', title: 'Latin small letter g with breve' },
|
|
8707
|
+
{ character: 'Ġ', title: 'Latin capital letter g with dot above' },
|
|
8708
|
+
{ character: 'ġ', title: 'Latin small letter g with dot above' },
|
|
8709
|
+
{ character: 'Ģ', title: 'Latin capital letter g with cedilla' },
|
|
8710
|
+
{ character: 'ģ', title: 'Latin small letter g with cedilla' },
|
|
8711
|
+
{ character: 'Ĥ', title: 'Latin capital letter h with circumflex' },
|
|
8712
|
+
{ character: 'ĥ', title: 'Latin small letter h with circumflex' },
|
|
8713
|
+
{ character: 'Ħ', title: 'Latin capital letter h with stroke' },
|
|
8714
|
+
{ character: 'ħ', title: 'Latin small letter h with stroke' },
|
|
8715
|
+
{ character: 'Ĩ', title: 'Latin capital letter i with tilde' },
|
|
8716
|
+
{ character: 'ĩ', title: 'Latin small letter i with tilde' },
|
|
8717
|
+
{ character: 'Ī', title: 'Latin capital letter i with macron' },
|
|
8718
|
+
{ character: 'ī', title: 'Latin small letter i with macron' },
|
|
8719
|
+
{ character: 'Ĭ', title: 'Latin capital letter i with breve' },
|
|
8720
|
+
{ character: 'ĭ', title: 'Latin small letter i with breve' },
|
|
8721
|
+
{ character: 'Į', title: 'Latin capital letter i with ogonek' },
|
|
8722
|
+
{ character: 'į', title: 'Latin small letter i with ogonek' },
|
|
8723
|
+
{ character: 'İ', title: 'Latin capital letter i with dot above' },
|
|
8724
|
+
{ character: 'ı', title: 'Latin small letter dotless i' },
|
|
8725
|
+
{ character: 'IJ', title: 'Latin capital ligature ij' },
|
|
8726
|
+
{ character: 'ij', title: 'Latin small ligature ij' },
|
|
8727
|
+
{ character: 'Ĵ', title: 'Latin capital letter j with circumflex' },
|
|
8728
|
+
{ character: 'ĵ', title: 'Latin small letter j with circumflex' },
|
|
8729
|
+
{ character: 'Ķ', title: 'Latin capital letter k with cedilla' },
|
|
8730
|
+
{ character: 'ķ', title: 'Latin small letter k with cedilla' },
|
|
8731
|
+
{ character: 'ĸ', title: 'Latin small letter kra' },
|
|
8732
|
+
{ character: 'Ĺ', title: 'Latin capital letter l with acute' },
|
|
8733
|
+
{ character: 'ĺ', title: 'Latin small letter l with acute' },
|
|
8734
|
+
{ character: 'Ļ', title: 'Latin capital letter l with cedilla' },
|
|
8735
|
+
{ character: 'ļ', title: 'Latin small letter l with cedilla' },
|
|
8736
|
+
{ character: 'Ľ', title: 'Latin capital letter l with caron' },
|
|
8737
|
+
{ character: 'ľ', title: 'Latin small letter l with caron' },
|
|
8738
|
+
{ character: 'Ŀ', title: 'Latin capital letter l with middle dot' },
|
|
8739
|
+
{ character: 'ŀ', title: 'Latin small letter l with middle dot' },
|
|
8740
|
+
{ character: 'Ł', title: 'Latin capital letter l with stroke' },
|
|
8741
|
+
{ character: 'ł', title: 'Latin small letter l with stroke' },
|
|
8742
|
+
{ character: 'Ń', title: 'Latin capital letter n with acute' },
|
|
8743
|
+
{ character: 'ń', title: 'Latin small letter n with acute' },
|
|
8744
|
+
{ character: 'Ņ', title: 'Latin capital letter n with cedilla' },
|
|
8745
|
+
{ character: 'ņ', title: 'Latin small letter n with cedilla' },
|
|
8746
|
+
{ character: 'Ň', title: 'Latin capital letter n with caron' },
|
|
8747
|
+
{ character: 'ň', title: 'Latin small letter n with caron' },
|
|
8748
|
+
{ character: 'ʼn', title: 'Latin small letter n preceded by apostrophe' },
|
|
8749
|
+
{ character: 'Ŋ', title: 'Latin capital letter eng' },
|
|
8750
|
+
{ character: 'ŋ', title: 'Latin small letter eng' },
|
|
8751
|
+
{ character: 'Ō', title: 'Latin capital letter o with macron' },
|
|
8752
|
+
{ character: 'ō', title: 'Latin small letter o with macron' },
|
|
8753
|
+
{ character: 'Ŏ', title: 'Latin capital letter o with breve' },
|
|
8754
|
+
{ character: 'ŏ', title: 'Latin small letter o with breve' },
|
|
8755
|
+
{ character: 'Ő', title: 'Latin capital letter o with double acute' },
|
|
8756
|
+
{ character: 'ő', title: 'Latin small letter o with double acute' },
|
|
8757
|
+
{ character: 'Œ', title: 'Latin capital ligature oe' },
|
|
8758
|
+
{ character: 'œ', title: 'Latin small ligature oe' },
|
|
8759
|
+
{ character: 'Ŕ', title: 'Latin capital letter r with acute' },
|
|
8760
|
+
{ character: 'ŕ', title: 'Latin small letter r with acute' },
|
|
8761
|
+
{ character: 'Ŗ', title: 'Latin capital letter r with cedilla' },
|
|
8762
|
+
{ character: 'ŗ', title: 'Latin small letter r with cedilla' },
|
|
8763
|
+
{ character: 'Ř', title: 'Latin capital letter r with caron' },
|
|
8764
|
+
{ character: 'ř', title: 'Latin small letter r with caron' },
|
|
8765
|
+
{ character: 'Ś', title: 'Latin capital letter s with acute' },
|
|
8766
|
+
{ character: 'ś', title: 'Latin small letter s with acute' },
|
|
8767
|
+
{ character: 'Ŝ', title: 'Latin capital letter s with circumflex' },
|
|
8768
|
+
{ character: 'ŝ', title: 'Latin small letter s with circumflex' },
|
|
8769
|
+
{ character: 'Ş', title: 'Latin capital letter s with cedilla' },
|
|
8770
|
+
{ character: 'ş', title: 'Latin small letter s with cedilla' },
|
|
8771
|
+
{ character: 'Š', title: 'Latin capital letter s with caron' },
|
|
8772
|
+
{ character: 'š', title: 'Latin small letter s with caron' },
|
|
8773
|
+
{ character: 'Ţ', title: 'Latin capital letter t with cedilla' },
|
|
8774
|
+
{ character: 'ţ', title: 'Latin small letter t with cedilla' },
|
|
8775
|
+
{ character: 'Ť', title: 'Latin capital letter t with caron' },
|
|
8776
|
+
{ character: 'ť', title: 'Latin small letter t with caron' },
|
|
8777
|
+
{ character: 'Ŧ', title: 'Latin capital letter t with stroke' },
|
|
8778
|
+
{ character: 'ŧ', title: 'Latin small letter t with stroke' },
|
|
8779
|
+
{ character: 'Ũ', title: 'Latin capital letter u with tilde' },
|
|
8780
|
+
{ character: 'ũ', title: 'Latin small letter u with tilde' },
|
|
8781
|
+
{ character: 'Ū', title: 'Latin capital letter u with macron' },
|
|
8782
|
+
{ character: 'ū', title: 'Latin small letter u with macron' },
|
|
8783
|
+
{ character: 'Ŭ', title: 'Latin capital letter u with breve' },
|
|
8784
|
+
{ character: 'ŭ', title: 'Latin small letter u with breve' },
|
|
8785
|
+
{ character: 'Ů', title: 'Latin capital letter u with ring above' },
|
|
8786
|
+
{ character: 'ů', title: 'Latin small letter u with ring above' },
|
|
8787
|
+
{ character: 'Ű', title: 'Latin capital letter u with double acute' },
|
|
8788
|
+
{ character: 'ű', title: 'Latin small letter u with double acute' },
|
|
8789
|
+
{ character: 'Ų', title: 'Latin capital letter u with ogonek' },
|
|
8790
|
+
{ character: 'ų', title: 'Latin small letter u with ogonek' },
|
|
8791
|
+
{ character: 'Ŵ', title: 'Latin capital letter w with circumflex' },
|
|
8792
|
+
{ character: 'ŵ', title: 'Latin small letter w with circumflex' },
|
|
8793
|
+
{ character: 'Ŷ', title: 'Latin capital letter y with circumflex' },
|
|
8794
|
+
{ character: 'ŷ', title: 'Latin small letter y with circumflex' },
|
|
8795
|
+
{ character: 'Ÿ', title: 'Latin capital letter y with diaeresis' },
|
|
8796
|
+
{ character: 'Ź', title: 'Latin capital letter z with acute' },
|
|
8797
|
+
{ character: 'ź', title: 'Latin small letter z with acute' },
|
|
8798
|
+
{ character: 'Ż', title: 'Latin capital letter z with dot above' },
|
|
8799
|
+
{ character: 'ż', title: 'Latin small letter z with dot above' },
|
|
8800
|
+
{ character: 'Ž', title: 'Latin capital letter z with caron' },
|
|
8801
|
+
{ character: 'ž', title: 'Latin small letter z with caron' },
|
|
8802
|
+
{ character: 'ſ', title: 'Latin small letter long s' },
|
|
8803
|
+
];
|
|
8804
|
+
|
|
8805
|
+
const MATHEMATICAL_CHARACTERS = [
|
|
8806
|
+
{ character: '<', title: 'Less-than sign' },
|
|
8807
|
+
{ character: '>', title: 'Greater-than sign' },
|
|
8808
|
+
{ character: '≤', title: 'Less-than or equal to' },
|
|
8809
|
+
{ character: '≥', title: 'Greater-than or equal to' },
|
|
8810
|
+
{ character: '–', title: 'En dash' },
|
|
8811
|
+
{ character: '—', title: 'Em dash' },
|
|
8812
|
+
{ character: '¯', title: 'Macron' },
|
|
8813
|
+
{ character: '‾', title: 'Overline' },
|
|
8814
|
+
{ character: '°', title: 'Degree sign' },
|
|
8815
|
+
{ character: '−', title: 'Minus sign' },
|
|
8816
|
+
{ character: '±', title: 'Plus-minus sign' },
|
|
8817
|
+
{ character: '÷', title: 'Division sign' },
|
|
8818
|
+
{ character: '⁄', title: 'Fraction slash' },
|
|
8819
|
+
{ character: '×', title: 'Multiplication sign' },
|
|
8820
|
+
{ character: 'ƒ', title: 'Latin small letter f with hook' },
|
|
8821
|
+
{ character: '∫', title: 'Integral' },
|
|
8822
|
+
{ character: '∑', title: 'N-ary summation' },
|
|
8823
|
+
{ character: '∞', title: 'Infinity' },
|
|
8824
|
+
{ character: '√', title: 'Square root' },
|
|
8825
|
+
{ character: '∼', title: 'Tilde operator' },
|
|
8826
|
+
{ character: '≅', title: 'Approximately equal to' },
|
|
8827
|
+
{ character: '≈', title: 'Almost equal to' },
|
|
8828
|
+
{ character: '≠', title: 'Not equal to' },
|
|
8829
|
+
{ character: '≡', title: 'Identical to' },
|
|
8830
|
+
{ character: '∈', title: 'Element of' },
|
|
8831
|
+
{ character: '∉', title: 'Not an element of' },
|
|
8832
|
+
{ character: '∋', title: 'Contains as member' },
|
|
8833
|
+
{ character: '∏', title: 'N-ary product' },
|
|
8834
|
+
{ character: '∧', title: 'Logical and' },
|
|
8835
|
+
{ character: '∨', title: 'Logical or' },
|
|
8836
|
+
{ character: '¬', title: 'Not sign' },
|
|
8837
|
+
{ character: '∩', title: 'Intersection' },
|
|
8838
|
+
{ character: '∪', title: 'Union' },
|
|
8839
|
+
{ character: '∂', title: 'Partial differential' },
|
|
8840
|
+
{ character: '∀', title: 'For all' },
|
|
8841
|
+
{ character: '∃', title: 'There exists' },
|
|
8842
|
+
{ character: '∅', title: 'Empty set' },
|
|
8843
|
+
{ character: '∇', title: 'Nabla' },
|
|
8844
|
+
{ character: '∗', title: 'Asterisk operator' },
|
|
8845
|
+
{ character: '∝', title: 'Proportional to' },
|
|
8846
|
+
{ character: '∠', title: 'Angle' },
|
|
8847
|
+
{ character: '¼', title: 'Vulgar fraction one quarter' },
|
|
8848
|
+
{ character: '½', title: 'Vulgar fraction one half' },
|
|
8849
|
+
{ character: '¾', title: 'Vulgar fraction three quarters' },
|
|
8850
|
+
];
|
|
8851
|
+
|
|
8852
|
+
const TEXT_CHARACTERS = [
|
|
8853
|
+
{ character: '‹', title: 'Single left-pointing angle quotation mark' },
|
|
8854
|
+
{ character: '›', title: 'Single right-pointing angle quotation mark' },
|
|
8855
|
+
{ character: '«', title: 'Left-pointing double angle quotation mark' },
|
|
8856
|
+
{ character: '»', title: 'Right-pointing double angle quotation mark' },
|
|
8857
|
+
{ character: '‘', title: 'Left single quotation mark' },
|
|
8858
|
+
{ character: '’', title: 'Right single quotation mark' },
|
|
8859
|
+
{ character: '“', title: 'Left double quotation mark' },
|
|
8860
|
+
{ character: '”', title: 'Right double quotation mark' },
|
|
8861
|
+
{ character: '‚', title: 'Single low-9 quotation mark' },
|
|
8862
|
+
{ character: '„', title: 'Double low-9 quotation mark' },
|
|
8863
|
+
{ character: '¡', title: 'Inverted exclamation mark' },
|
|
8864
|
+
{ character: '¿', title: 'Inverted question mark' },
|
|
8865
|
+
{ character: '‥', title: 'Two dot leader' },
|
|
8866
|
+
{ character: '…', title: 'Horizontal ellipsis' },
|
|
8867
|
+
{ character: '‡', title: 'Double dagger' },
|
|
8868
|
+
{ character: '‰', title: 'Per mille sign' },
|
|
8869
|
+
{ character: '‱', title: 'Per ten thousand sign' },
|
|
8870
|
+
{ character: '‼', title: 'Double exclamation mark' },
|
|
8871
|
+
{ character: '⁈', title: 'Question exclamation mark' },
|
|
8872
|
+
{ character: '⁉', title: 'Exclamation question mark' },
|
|
8873
|
+
{ character: '⁇', title: 'Double question mark' },
|
|
8874
|
+
{ character: '©', title: 'Copyright sign' },
|
|
8875
|
+
{ character: '®', title: 'Registered sign' },
|
|
8876
|
+
{ character: '™', title: 'Trade mark sign' },
|
|
8877
|
+
{ character: '§', title: 'Section sign' },
|
|
8878
|
+
{ character: '¶', title: 'Paragraph sign' },
|
|
8879
|
+
{ character: '⁋', title: 'Reversed paragraph sign' },
|
|
8880
|
+
];
|
|
8881
|
+
|
|
8882
|
+
const VegaRTESpecialCharactersLatin = {
|
|
8883
|
+
key: 'latin',
|
|
8884
|
+
label: 'Latin',
|
|
8885
|
+
characters: LATIN_CHARACTERS,
|
|
8886
|
+
};
|
|
8887
|
+
const VegaRTESpecialCharactersMathematical = {
|
|
8888
|
+
key: 'mathematical',
|
|
8889
|
+
label: 'Mathematical',
|
|
8890
|
+
characters: MATHEMATICAL_CHARACTERS,
|
|
8891
|
+
};
|
|
8892
|
+
const VegaRTESpecialCharactersArrows = {
|
|
8893
|
+
key: 'arrows',
|
|
8894
|
+
label: 'Arrows',
|
|
8895
|
+
characters: ARROW_CHARACTERS,
|
|
8896
|
+
};
|
|
8897
|
+
const VegaRTESpecialCharactersCurrency = {
|
|
8898
|
+
key: 'currency',
|
|
8899
|
+
label: 'Currency',
|
|
8900
|
+
characters: CURRENCY_CHARACTERS,
|
|
8901
|
+
};
|
|
8902
|
+
const VegaRTESpecialCharactersText = {
|
|
8903
|
+
key: 'text',
|
|
8904
|
+
label: 'Text',
|
|
8905
|
+
characters: TEXT_CHARACTERS,
|
|
8906
|
+
};
|
|
8907
|
+
const VegaRTESpecialCharactersEssentials = [
|
|
8908
|
+
VegaRTESpecialCharactersCurrency,
|
|
8909
|
+
VegaRTESpecialCharactersText,
|
|
8910
|
+
VegaRTESpecialCharactersMathematical,
|
|
8911
|
+
VegaRTESpecialCharactersArrows,
|
|
8912
|
+
VegaRTESpecialCharactersLatin,
|
|
8913
|
+
];
|
|
8914
|
+
|
|
8915
|
+
const specialCharactersIcon = {
|
|
8916
|
+
icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path fill="currentColor" d="M224 80C126.8 80 48 158.8 48 256c0 63.7 33.8 119.5 84.6 150.4c11.4 7 19.4 19.6 19.4 34.2c0 21.8-17.6 39.4-39.4 39.4H24c-13.3 0-24-10.7-24-24s10.7-24 24-24H85.4C33.4 391 0 327.4 0 256C0 132.3 100.3 32 224 32s224 100.3 224 224c0 71.4-33.4 135-85.4 176H424c13.3 0 24 10.7 24 24s-10.7 24-24 24H335.4c-21.8 0-39.4-17.6-39.4-39.4c0-14.6 8-27.2 19.4-34.2C366.2 375.5 400 319.7 400 256c0-97.2-78.8-176-176-176z"/></svg>`,
|
|
8917
|
+
};
|
|
8918
|
+
|
|
8919
|
+
/**
|
|
8920
|
+
* Toolbar button renderer for the Special Characters extension.
|
|
8921
|
+
* Renders a toolbar button with an Ω icon that opens a popover panel
|
|
8922
|
+
* with categorized character grids.
|
|
8923
|
+
*/
|
|
8924
|
+
class RTESpecialCharactersToolbarButtonRenderer extends VegaRTEToolbarButtonRenderer {
|
|
8925
|
+
constructor(categories) {
|
|
8926
|
+
super();
|
|
8927
|
+
this.panelRef = null;
|
|
8928
|
+
this.categories = categories;
|
|
8929
|
+
}
|
|
8930
|
+
/**
|
|
8931
|
+
* Renders the special characters toolbar button wrapped in a popover.
|
|
8932
|
+
*
|
|
8933
|
+
* @param {VegaRTECreateElementFunction} h - Create element function.
|
|
8934
|
+
* @param {VegaRTEToolbarRenderContext} editorContext - The render context for the editor.
|
|
8935
|
+
* @returns {VegaRTERenderResult} - The toolbar button render result.
|
|
8936
|
+
*/
|
|
8937
|
+
render(h, editorContext) {
|
|
8938
|
+
return h('vega-popover', {
|
|
8939
|
+
trigger: 'click',
|
|
8940
|
+
alignment: 'start',
|
|
8941
|
+
placement: 'bottom',
|
|
8942
|
+
showArrow: false,
|
|
8943
|
+
isScreenPosition: true,
|
|
8944
|
+
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
8945
|
+
ref: (ref) => {
|
|
8946
|
+
if (ref) {
|
|
8947
|
+
DomNodeSubjectObserverFactory.addUniqueObserverToNode(ref, VegaPopoverShow, () => {
|
|
8948
|
+
this.handlePopoverShow();
|
|
8949
|
+
});
|
|
8950
|
+
}
|
|
8951
|
+
},
|
|
8952
|
+
}, [
|
|
8953
|
+
h('div', { slot: 'popover' }, [
|
|
8954
|
+
h('vega-rich-text-special-characters-panel', {
|
|
8955
|
+
categories: this.categories,
|
|
8956
|
+
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
8957
|
+
ref: (ref) => {
|
|
8958
|
+
if (ref) {
|
|
8959
|
+
this.panelRef = ref;
|
|
8960
|
+
DomNodeSubjectObserverFactory.addUniqueObserverToNode(ref, VegaClick,
|
|
8961
|
+
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
8962
|
+
(payload) => {
|
|
8963
|
+
this.insertCharacter(editorContext, payload.detail);
|
|
8964
|
+
});
|
|
8965
|
+
}
|
|
8966
|
+
},
|
|
8967
|
+
}),
|
|
8968
|
+
]),
|
|
8969
|
+
h('div', { slot: 'popover-content' }, this.renderButton(h, editorContext, {
|
|
8970
|
+
icon: 'rte-special-characters',
|
|
8971
|
+
tooltip: { text: 'Special Characters' },
|
|
8972
|
+
})),
|
|
8973
|
+
]);
|
|
8974
|
+
}
|
|
8975
|
+
/**
|
|
8976
|
+
* @inheritDoc
|
|
8977
|
+
*/
|
|
8978
|
+
handleButtonClick() {
|
|
8979
|
+
// Popover handles open/close via trigger='click'
|
|
8980
|
+
}
|
|
8981
|
+
/* istanbul ignore next */
|
|
8982
|
+
/**
|
|
8983
|
+
* @inheritDoc
|
|
8984
|
+
*/
|
|
8985
|
+
handleDropdownClick() {
|
|
8986
|
+
// No dropdown used
|
|
8987
|
+
}
|
|
8988
|
+
/**
|
|
8989
|
+
* Resets the panel and moves focus to the category select when the
|
|
8990
|
+
* popover opens.
|
|
8991
|
+
*/
|
|
8992
|
+
handlePopoverShow() {
|
|
8993
|
+
if (this.panelRef) {
|
|
8994
|
+
void this.panelRef.resetPanel();
|
|
8995
|
+
}
|
|
8996
|
+
}
|
|
8997
|
+
/**
|
|
8998
|
+
* Inserts a special character at the current cursor position using ReplaceSelectedTextAction,
|
|
8999
|
+
* then restores the cursor position after flushing.
|
|
9000
|
+
*
|
|
9001
|
+
* @param {VegaRTEToolbarRenderContext} editorContext - The render context for the editor.
|
|
9002
|
+
* @param {string} char - The Unicode character to insert.
|
|
9003
|
+
*/
|
|
9004
|
+
insertCharacter(editorContext, char) {
|
|
9005
|
+
const currentRange = editorContext.rteRange;
|
|
9006
|
+
const cursorNode = currentRange.startNode;
|
|
9007
|
+
if (!cursorNode)
|
|
9008
|
+
return;
|
|
9009
|
+
const target = this.isCaretOnLineBreak(cursorNode, currentRange)
|
|
9010
|
+
? this.insertAtLineBreak(cursorNode, char, currentRange)
|
|
9011
|
+
: this.insertAtTextNode(cursorNode, char, currentRange);
|
|
9012
|
+
editorContext.host.value.apply(new UpdateCursorPositionAction(target.node, target.offset));
|
|
9013
|
+
}
|
|
9014
|
+
/**
|
|
9015
|
+
* Checks whether the caret is collapsed on a Shift+Enter line-break text node.
|
|
9016
|
+
*
|
|
9017
|
+
* @param {RTENode} cursorNode - The current cursor node.
|
|
9018
|
+
* @param {RTERange} range - The current selection range.
|
|
9019
|
+
* @returns {boolean} True if the caret is collapsed on a '\n' text node.
|
|
9020
|
+
*/
|
|
9021
|
+
isCaretOnLineBreak(cursorNode, range) {
|
|
9022
|
+
return (cursorNode instanceof RTETextNode &&
|
|
9023
|
+
cursorNode.text === '\n' &&
|
|
9024
|
+
range.startOffset === range.endOffset);
|
|
9025
|
+
}
|
|
9026
|
+
/**
|
|
9027
|
+
* Default insert path: replace the selected range on a regular text node.
|
|
9028
|
+
*
|
|
9029
|
+
* @param {RTENode} cursorNode - The current cursor node.
|
|
9030
|
+
* @param {string} char - The Unicode character to insert.
|
|
9031
|
+
* @param {RTERange} range - The current selection range.
|
|
9032
|
+
* @returns {CaretTarget} The node and offset for the resulting caret position.
|
|
9033
|
+
*/
|
|
9034
|
+
insertAtTextNode(cursorNode, char, range) {
|
|
9035
|
+
const action = new ReplaceSelectedTextAction(char, range);
|
|
9036
|
+
action.isFlushable = true;
|
|
9037
|
+
cursorNode.apply(action);
|
|
9038
|
+
// Clamp to node length: empty paragraphs contain a ZWS in the DOM (startOffset=1)
|
|
9039
|
+
// but the DTO text node is empty, so `startOffset + char.length` would overflow.
|
|
9040
|
+
return {
|
|
9041
|
+
node: cursorNode,
|
|
9042
|
+
offset: Math.min(range.startOffset + char.length, cursorNode.getRangeEndOffset()),
|
|
9043
|
+
};
|
|
9044
|
+
}
|
|
9045
|
+
/**
|
|
9046
|
+
* Line-break insert path: delegate to insertTextToLineBreakNode and place
|
|
9047
|
+
* the caret at the end of the inserted text.
|
|
9048
|
+
*
|
|
9049
|
+
* @param {RTETextNode} lineBreakNode - The '\n' text node at the caret.
|
|
9050
|
+
* @param {string} char - The Unicode character to insert.
|
|
9051
|
+
* @param {RTERange} range - The current selection range.
|
|
9052
|
+
* @returns {CaretTarget} Caret target.
|
|
9053
|
+
*/
|
|
9054
|
+
insertAtLineBreak(lineBreakNode, char, range) {
|
|
9055
|
+
return {
|
|
9056
|
+
node: this.insertTextToLineBreakNode(lineBreakNode, char, range),
|
|
9057
|
+
offset: char.length,
|
|
9058
|
+
};
|
|
9059
|
+
}
|
|
9060
|
+
/**
|
|
9061
|
+
* Mirror of InsertTextHandler.insertTextToLineBreakNode for the toolbar
|
|
9062
|
+
* insert path: caret on a Shift+Enter line-break text node needs either a
|
|
9063
|
+
* new sibling text node inserted before the break, or the break's text
|
|
9064
|
+
* replaced. The default ReplaceSelectedTextAction + getRangeEndOffset()
|
|
9065
|
+
* path returns 0 for '\n' text nodes and drops the caret in front of the
|
|
9066
|
+
* inserted character.
|
|
9067
|
+
*
|
|
9068
|
+
* @param {RTETextNode} lineBreakNode - The '\n' text node at the caret.
|
|
9069
|
+
* @param {string} text - The text to insert.
|
|
9070
|
+
* @param {RTERange} currentRange - The current selection range.
|
|
9071
|
+
* @returns {RTETextNode} The node the caret should land in.
|
|
9072
|
+
*/
|
|
9073
|
+
insertTextToLineBreakNode(lineBreakNode, text, currentRange) {
|
|
9074
|
+
// '\n' text nodes always live inside a text block in the RTE structure.
|
|
9075
|
+
const textBlock = lineBreakNode.parent;
|
|
9076
|
+
const index = textBlock.children.indexOf(lineBreakNode);
|
|
9077
|
+
if (currentRange.startOffset === 0 && textBlock.children[index + 1]) {
|
|
9078
|
+
const newTextNode = lineBreakNode.cloneWithText(text);
|
|
9079
|
+
const insertAction = new InsertChildrenBeforeAction(lineBreakNode, newTextNode);
|
|
9080
|
+
insertAction.isFlushable = true;
|
|
9081
|
+
textBlock.apply(insertAction);
|
|
9082
|
+
return newTextNode;
|
|
9083
|
+
}
|
|
9084
|
+
const updateAction = new UpdateTextAction(text);
|
|
9085
|
+
updateAction.isFlushable = true;
|
|
9086
|
+
lineBreakNode.apply(updateAction);
|
|
9087
|
+
return lineBreakNode;
|
|
9088
|
+
}
|
|
9089
|
+
}
|
|
9090
|
+
(() => {
|
|
9091
|
+
VegaInternalIconManager.register({ 'rte-special-characters': specialCharactersIcon });
|
|
9092
|
+
})();
|
|
9093
|
+
|
|
9094
|
+
/**
|
|
9095
|
+
* Vega Rich Text Editor Special Characters Extension.
|
|
9096
|
+
* Adds a toolbar button that opens a popover with categorized Unicode characters
|
|
9097
|
+
* for insertion into the editor content as plain text.
|
|
9098
|
+
*/
|
|
9099
|
+
class VegaRTESpecialCharactersExtension extends VegaRTEExtension {
|
|
9100
|
+
constructor(config) {
|
|
9101
|
+
super();
|
|
9102
|
+
this.categories = this.buildCategories(config);
|
|
9103
|
+
}
|
|
9104
|
+
/** @inheritDoc */
|
|
9105
|
+
prepareBeforeLoad(host) {
|
|
9106
|
+
this.registerToolbarButtonRenderer(VegaRTESpecialCharactersExtension.TOOLBAR_BUTTON_KEY, new RTESpecialCharactersToolbarButtonRenderer(this.categories), host);
|
|
9107
|
+
}
|
|
9108
|
+
/**
|
|
9109
|
+
* Builds the final list of special character categories from the provided config.
|
|
9110
|
+
*
|
|
9111
|
+
* @param {VegaRTESpecialCharactersConfig} config - Optional configuration with custom categories.
|
|
9112
|
+
* @returns {VegaRTESpecialCharacterCategory[]} - The merged categories array.
|
|
9113
|
+
*/
|
|
9114
|
+
buildCategories(config) {
|
|
9115
|
+
const includeDefaults = (config === null || config === void 0 ? void 0 : config.includeDefaults) !== false;
|
|
9116
|
+
const defaultCategories = includeDefaults
|
|
9117
|
+
? VegaRTESpecialCharactersEssentials
|
|
9118
|
+
: [];
|
|
9119
|
+
if (!(config === null || config === void 0 ? void 0 : config.categories)) {
|
|
9120
|
+
return defaultCategories;
|
|
9121
|
+
}
|
|
9122
|
+
return [...defaultCategories, ...config.categories];
|
|
9123
|
+
}
|
|
9124
|
+
}
|
|
9125
|
+
VegaRTESpecialCharactersExtension.TOOLBAR_BUTTON_KEY = 'special-characters';
|
|
9126
|
+
|
|
9127
|
+
const findReplaceIcon = {
|
|
9128
|
+
icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2026 Fonticons, Inc. --><path fill="currentColor" d="M208 48c39.6 0 75.9 14.4 103.8 38.2L279 119c-6.9 6.9-8.9 17.2-5.2 26.2S286.3 160 296 160l96 0c13.3 0 24-10.7 24-24l0-96c0-9.7-5.8-18.5-14.8-22.2S381.9 16.2 375 23L345.8 52.2c-36.7-32.5-85-52.2-137.8-52.2-99.4 0-182.4 69.7-203.1 162.8-2.9 12.9 5.3 25.8 18.2 28.6 1.4 .3 2.7 .5 4.1 .5l2.3 0C40.1 191.5 49.4 184 51.8 173.2 67.7 101.6 131.6 48 208 48zM386 224c-10.4 .7-19.4 8.1-21.8 18.8-15.9 71.7-79.8 125.2-156.2 125.2-39.6 0-75.9-14.4-103.8-38.2L137 297c6.9-6.9 8.9-17.2 5.2-26.2S129.7 256 120 256l-96 0c-13.3 0-24 10.7-24 24l0 96c0 9.7 5.8 18.5 14.8 22.2S34.1 399.8 41 393l29.2-29.2c36.7 32.5 85 52.2 137.8 52.2 48.8 0 93.7-16.8 129.1-44.9L471 505c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9L371.1 337.1c19.2-24.2 33.1-52.8 40-84 2.9-12.9-5.3-25.8-18.2-28.6-1.2-.3-2.4-.4-3.6-.5l-3.2 0z"/></svg>`,
|
|
9129
|
+
};
|
|
9130
|
+
|
|
9131
|
+
/**
|
|
9132
|
+
* Defines how a specific type of RTEBlock contributes searchable leaf blocks.
|
|
9133
|
+
*
|
|
9134
|
+
* Implementations are registered on FindReplaceState in priority order.
|
|
9135
|
+
* The first strategy whose `canHandle` returns true is used.
|
|
9136
|
+
*/
|
|
9137
|
+
class BlockSearchStrategy {
|
|
9138
|
+
}
|
|
9139
|
+
|
|
9140
|
+
/**
|
|
9141
|
+
* Handles RTECodeBlock — a searchable leaf block whose text lives in children[0].text.
|
|
9142
|
+
*/
|
|
9143
|
+
class CodeBlockSearchStrategy extends BlockSearchStrategy {
|
|
9144
|
+
/**
|
|
9145
|
+
* @inheritDoc
|
|
9146
|
+
*/
|
|
9147
|
+
canHandle(block) {
|
|
9148
|
+
return block instanceof RTECodeBlock;
|
|
9149
|
+
}
|
|
9150
|
+
/**
|
|
9151
|
+
* @inheritDoc
|
|
9152
|
+
*/
|
|
9153
|
+
collect(block) {
|
|
9154
|
+
return [block];
|
|
9155
|
+
}
|
|
9156
|
+
}
|
|
9157
|
+
|
|
9158
|
+
/**
|
|
9159
|
+
* Handles RTEListItemBlock — a searchable leaf block that may also carry
|
|
9160
|
+
* nested RTEListBlocks via its `nestList` property.
|
|
9161
|
+
*
|
|
9162
|
+
* Must be registered before TextBlockSearchStrategy because RTEListItemBlock
|
|
9163
|
+
* extends RTETextBlock and would otherwise be matched by the parent class check.
|
|
9164
|
+
*/
|
|
9165
|
+
class ListItemBlockSearchStrategy extends BlockSearchStrategy {
|
|
9166
|
+
/**
|
|
9167
|
+
* @inheritDoc
|
|
9168
|
+
*/
|
|
9169
|
+
canHandle(block) {
|
|
9170
|
+
return block instanceof RTEListItemBlock;
|
|
9171
|
+
}
|
|
9172
|
+
/**
|
|
9173
|
+
* @inheritDoc
|
|
9174
|
+
*/
|
|
9175
|
+
collect(block, recurse) {
|
|
9176
|
+
const listItemBlock = block;
|
|
9177
|
+
const result = [listItemBlock];
|
|
9178
|
+
if (listItemBlock.nestList && listItemBlock.nestList.length > 0) {
|
|
9179
|
+
for (const nestedList of listItemBlock.nestList) {
|
|
9180
|
+
result.push(...recurse(nestedList.children));
|
|
9181
|
+
}
|
|
9182
|
+
}
|
|
9183
|
+
return result;
|
|
9184
|
+
}
|
|
9185
|
+
}
|
|
9186
|
+
|
|
9187
|
+
/**
|
|
9188
|
+
* Handles plain RTETextBlock (paragraph, heading, etc.) — a searchable leaf
|
|
9189
|
+
* block whose text lives in its RTETextNode children.
|
|
9190
|
+
*/
|
|
9191
|
+
class TextBlockSearchStrategy extends BlockSearchStrategy {
|
|
9192
|
+
/**
|
|
9193
|
+
* @inheritDoc
|
|
9194
|
+
*/
|
|
9195
|
+
canHandle(block) {
|
|
9196
|
+
return block instanceof RTETextBlock;
|
|
9197
|
+
}
|
|
9198
|
+
/**
|
|
9199
|
+
* @inheritDoc
|
|
9200
|
+
*/
|
|
9201
|
+
collect(block) {
|
|
9202
|
+
return [block];
|
|
9203
|
+
}
|
|
9204
|
+
}
|
|
9205
|
+
|
|
9206
|
+
/**
|
|
9207
|
+
* Fallback strategy for generic container blocks whose concrete type is not
|
|
9208
|
+
* known at compile time (RTEHtmlBlock, table blocks, extension-defined blocks, …).
|
|
9209
|
+
*
|
|
9210
|
+
* A block is treated as a container when its children array is non-empty and
|
|
9211
|
+
* the first child is not an RTETextNode (i.e. children are blocks, not nodes).
|
|
9212
|
+
*/
|
|
9213
|
+
class ContainerBlockSearchStrategy extends BlockSearchStrategy {
|
|
9214
|
+
/**
|
|
9215
|
+
* @inheritDoc
|
|
9216
|
+
*/
|
|
9217
|
+
canHandle(block) {
|
|
9218
|
+
return (block.children != null && block.children.length > 0 && !(block.children[0] instanceof RTETextNode));
|
|
9219
|
+
}
|
|
9220
|
+
/**
|
|
9221
|
+
* @inheritDoc
|
|
9222
|
+
*/
|
|
9223
|
+
collect(block, recurse) {
|
|
9224
|
+
return recurse(block.children);
|
|
9225
|
+
}
|
|
9226
|
+
}
|
|
9227
|
+
|
|
9228
|
+
/**
|
|
9229
|
+
* Sentinel character inserted into the flat text in place of a non-editable
|
|
9230
|
+
* node (e.g. a token). It keeps surrounding searchable segments from bridging
|
|
9231
|
+
* across the removed node — producing false matches — while never matching any
|
|
9232
|
+
* user keyword. `\u0000` (NULL) does not appear in editable document text.
|
|
9233
|
+
*/
|
|
9234
|
+
const NON_EDITABLE_SENTINEL = '\u0000';
|
|
9235
|
+
/**
|
|
9236
|
+
* Manages the in-memory state for a single Find & Replace session.
|
|
9237
|
+
*
|
|
9238
|
+
* One instance is created per editor host inside VegaRTEFindReplaceExtension
|
|
9239
|
+
* during `initialExtensionAfterLoad`. It holds all search results, the
|
|
9240
|
+
* current navigation index, and a per-block flat-text cache.
|
|
9241
|
+
*
|
|
9242
|
+
* Public API:
|
|
9243
|
+
* - `search(content, keyword)` — run a search, replace current state using current options
|
|
9244
|
+
* - `next()` / `prev()` — navigate through results
|
|
9245
|
+
* - `getCurrentMatch()` / `getMatches()` / `getTotal()` / `getCurrentIndex()` — read state
|
|
9246
|
+
* - `clear()` — reset everything
|
|
9247
|
+
*/
|
|
9248
|
+
class FindReplaceState {
|
|
9249
|
+
constructor() {
|
|
9250
|
+
this.matches = [];
|
|
9251
|
+
this.currentIndex = -1;
|
|
9252
|
+
this.flatCache = new WeakMap();
|
|
9253
|
+
this.cachedContent = null;
|
|
9254
|
+
this.matchCase = false;
|
|
9255
|
+
this.wholeWords = false;
|
|
9256
|
+
/**
|
|
9257
|
+
* Ordered strategy chain used to classify and traverse each block type.
|
|
9258
|
+
* More specific types (RTEListItemBlock) must precede their parent class (RTETextBlock).
|
|
9259
|
+
*/
|
|
9260
|
+
this.blockSearchStrategies = [
|
|
9261
|
+
new CodeBlockSearchStrategy(),
|
|
9262
|
+
new ListItemBlockSearchStrategy(),
|
|
9263
|
+
new TextBlockSearchStrategy(),
|
|
9264
|
+
new ContainerBlockSearchStrategy(),
|
|
9265
|
+
];
|
|
9266
|
+
}
|
|
9267
|
+
/**
|
|
9268
|
+
* Runs a fresh search over the given content and replaces all previous results.
|
|
9269
|
+
*
|
|
9270
|
+
* The flat-text cache is invalidated whenever the content reference changes
|
|
9271
|
+
* (i.e. after the user edits the document). When only the keyword or options
|
|
9272
|
+
* change (user typing in the find input), the cache is reused.
|
|
9273
|
+
*
|
|
9274
|
+
* @param {VegaRTEContent} content - The root content DTO to search.
|
|
9275
|
+
* @param {string} keyword - The search keyword entered by the user.
|
|
9276
|
+
* Uses the currently stored `matchCase` and `wholeWords` options.
|
|
9277
|
+
*/
|
|
9278
|
+
search(content, keyword) {
|
|
9279
|
+
this.invalidateCacheIfNeeded(content);
|
|
9280
|
+
if (!keyword) {
|
|
9281
|
+
this.matches = [];
|
|
9282
|
+
this.currentIndex = -1;
|
|
9283
|
+
return;
|
|
9284
|
+
}
|
|
9285
|
+
const regex = this.buildRegExp(keyword, this.getFindOptions());
|
|
9286
|
+
const blocks = this.collectSearchableBlocks(content.children);
|
|
9287
|
+
const results = [];
|
|
9288
|
+
for (const block of blocks) {
|
|
9289
|
+
let cached = this.flatCache.get(block);
|
|
9290
|
+
if (!cached) {
|
|
9291
|
+
cached =
|
|
9292
|
+
block instanceof RTECodeBlock
|
|
9293
|
+
? this.buildCodeBlockFlat(block)
|
|
9294
|
+
: this.buildTextBlockFlat(block);
|
|
9295
|
+
this.flatCache.set(block, cached);
|
|
9296
|
+
}
|
|
9297
|
+
const { flatText, segments } = cached;
|
|
9298
|
+
if (!flatText) {
|
|
9299
|
+
continue;
|
|
9300
|
+
}
|
|
9301
|
+
regex.lastIndex = 0;
|
|
9302
|
+
let match;
|
|
9303
|
+
while ((match = regex.exec(flatText)) !== null) {
|
|
9304
|
+
// match[1] is always the keyword capture group. In wholeWords mode,
|
|
9305
|
+
// match[0] may include a leading non-letter boundary char, so we
|
|
9306
|
+
// compute the actual start from the end offset minus the keyword length.
|
|
9307
|
+
const matchEnd = match.index + match[0].length;
|
|
9308
|
+
const matchStart = matchEnd - match[1].length;
|
|
9309
|
+
const nodes = this.flatOffsetToNodePositions(segments, matchStart, matchEnd);
|
|
9310
|
+
if (nodes.length > 0) {
|
|
9311
|
+
results.push({ block, nodes, matchText: match[1] });
|
|
9312
|
+
}
|
|
9313
|
+
}
|
|
9314
|
+
}
|
|
9315
|
+
this.matches = results;
|
|
9316
|
+
this.currentIndex = results.length > 0 ? 0 : -1;
|
|
9317
|
+
}
|
|
9318
|
+
/**
|
|
9319
|
+
* Advances to the next match, wrapping around to the first.
|
|
9320
|
+
*/
|
|
9321
|
+
next() {
|
|
9322
|
+
if (this.matches.length === 0) {
|
|
9323
|
+
return;
|
|
9324
|
+
}
|
|
9325
|
+
this.currentIndex = (this.currentIndex + 1) % this.matches.length;
|
|
9326
|
+
}
|
|
9327
|
+
/**
|
|
9328
|
+
* Moves to the previous match, wrapping around to the last.
|
|
9329
|
+
*/
|
|
9330
|
+
prev() {
|
|
9331
|
+
if (this.matches.length === 0) {
|
|
9332
|
+
return;
|
|
9333
|
+
}
|
|
9334
|
+
this.currentIndex = (this.currentIndex - 1 + this.matches.length) % this.matches.length;
|
|
9335
|
+
}
|
|
9336
|
+
/**
|
|
9337
|
+
* Sets the current index directly. Clamps to valid range.
|
|
9338
|
+
*
|
|
9339
|
+
* @param {number} index - The desired index.
|
|
9340
|
+
*/
|
|
9341
|
+
setCurrentIndex(index) {
|
|
9342
|
+
if (this.matches.length === 0) {
|
|
9343
|
+
this.currentIndex = -1;
|
|
9344
|
+
return;
|
|
9345
|
+
}
|
|
9346
|
+
this.currentIndex = Math.max(0, Math.min(index, this.matches.length - 1));
|
|
9347
|
+
}
|
|
9348
|
+
/**
|
|
9349
|
+
* Returns the currently active match, or null if there are no results.
|
|
9350
|
+
*
|
|
9351
|
+
* @returns {FindMatch | null} The currently active match, or null if there are no results.
|
|
9352
|
+
*/
|
|
9353
|
+
getCurrentMatch() {
|
|
9354
|
+
var _a;
|
|
9355
|
+
return (_a = this.matches[this.currentIndex]) !== null && _a !== void 0 ? _a : null;
|
|
9356
|
+
}
|
|
9357
|
+
/**
|
|
9358
|
+
* Returns all matches from the last search.
|
|
9359
|
+
*
|
|
9360
|
+
* @returns {ReadonlyArray<FindMatch>} All matches from the last search.
|
|
9361
|
+
*/
|
|
9362
|
+
getMatches() {
|
|
9363
|
+
return this.matches;
|
|
9364
|
+
}
|
|
9365
|
+
/**
|
|
9366
|
+
* Returns the total number of matches from the last search.
|
|
9367
|
+
*
|
|
9368
|
+
* @returns {number} The total number of matches.
|
|
9369
|
+
*/
|
|
9370
|
+
getTotal() {
|
|
9371
|
+
return this.matches.length;
|
|
9372
|
+
}
|
|
9373
|
+
/**
|
|
9374
|
+
* Returns the 0-based index of the current match, or -1 when there are no results.
|
|
9375
|
+
*
|
|
9376
|
+
* @returns {number} The 0-based index, or -1 when there are no results.
|
|
9377
|
+
*/
|
|
9378
|
+
getCurrentIndex() {
|
|
9379
|
+
return this.currentIndex;
|
|
9380
|
+
}
|
|
9381
|
+
/**
|
|
9382
|
+
* Resets only match results and navigation index.
|
|
9383
|
+
* The flat-text cache and cached content reference are preserved so that
|
|
9384
|
+
* a subsequent search on the same document (e.g. after a keyword change)
|
|
9385
|
+
* can reuse the already-computed block flat texts.
|
|
9386
|
+
*/
|
|
9387
|
+
clearResults() {
|
|
9388
|
+
this.matches = [];
|
|
9389
|
+
this.currentIndex = -1;
|
|
9390
|
+
}
|
|
9391
|
+
/**
|
|
9392
|
+
* Resets all search state, including results, navigation index, and flat-text cache.
|
|
9393
|
+
* Use this when the session ends (e.g. panel closed) or the host changes.
|
|
9394
|
+
*/
|
|
9395
|
+
clear() {
|
|
9396
|
+
this.matches = [];
|
|
9397
|
+
this.currentIndex = -1;
|
|
9398
|
+
this.flatCache = new WeakMap();
|
|
9399
|
+
this.cachedContent = null;
|
|
9400
|
+
}
|
|
9401
|
+
/**
|
|
9402
|
+
* Sets the matchCase search option.
|
|
9403
|
+
*
|
|
9404
|
+
* @param {boolean} enabled - Whether to enable case-sensitive matching.
|
|
9405
|
+
*/
|
|
9406
|
+
setMatchCase(enabled) {
|
|
9407
|
+
this.matchCase = enabled;
|
|
9408
|
+
}
|
|
9409
|
+
/**
|
|
9410
|
+
* Sets the wholeWords search option.
|
|
9411
|
+
*
|
|
9412
|
+
* @param {boolean} enabled - Whether to enable whole-word matching.
|
|
9413
|
+
*/
|
|
9414
|
+
setWholeWords(enabled) {
|
|
9415
|
+
this.wholeWords = enabled;
|
|
9416
|
+
}
|
|
9417
|
+
// ─── Private helpers ──────────────────────────────────────────────────────────
|
|
9418
|
+
/**
|
|
9419
|
+
* Invalidates the flat-text cache when the content reference has changed.
|
|
9420
|
+
* Since flushChanges always produces a new VegaRTEContent via clone(), a
|
|
9421
|
+
* changed reference reliably indicates the document has been edited.
|
|
9422
|
+
*
|
|
9423
|
+
* @param {VegaRTEContent} content - The incoming content reference.
|
|
9424
|
+
*/
|
|
9425
|
+
invalidateCacheIfNeeded(content) {
|
|
9426
|
+
if (content !== this.cachedContent) {
|
|
9427
|
+
this.flatCache = new WeakMap();
|
|
9428
|
+
this.cachedContent = content;
|
|
9429
|
+
}
|
|
9430
|
+
}
|
|
9431
|
+
/**
|
|
9432
|
+
* Builds a RegExp from the keyword and search options.
|
|
9433
|
+
*
|
|
9434
|
+
* @param {string} keyword - The raw user input.
|
|
9435
|
+
* @param {FindOptions} options - Search options.
|
|
9436
|
+
* @returns {RegExp} The compiled regular expression.
|
|
9437
|
+
*/
|
|
9438
|
+
buildRegExp(keyword, options) {
|
|
9439
|
+
const escaped = this.escapeRegExp(keyword);
|
|
9440
|
+
// eslint-disable-next-line spellcheck/spell-checker
|
|
9441
|
+
let flags = 'gu';
|
|
9442
|
+
if (!options.matchCase) {
|
|
9443
|
+
flags += 'i';
|
|
9444
|
+
}
|
|
9445
|
+
if (options.wholeWords) {
|
|
9446
|
+
// Aligned with CKEditor's findByTextCallback non-letter boundary group.
|
|
9447
|
+
//
|
|
9448
|
+
// The keyword is wrapped in a capturing group (match[1]) so the search
|
|
9449
|
+
// loop can compute the exact start offset without lookbehind assertions
|
|
9450
|
+
// (not supported in Safari < 16.4). The non-capturing prefix group
|
|
9451
|
+
// `(?:^|nonLetter)` may consume one boundary char, which is why we
|
|
9452
|
+
// derive matchStart as matchEnd - match[1].length rather than match.index.
|
|
9453
|
+
const nonLetter = '[^a-zA-Z\u00C0-\u024F\u1E00-\u1EFF]';
|
|
9454
|
+
const prefix = !new RegExp(`^${nonLetter}`).test(keyword) ? `(?:^|${nonLetter})` : '';
|
|
9455
|
+
const suffix = !new RegExp(`${nonLetter}$`).test(keyword) ? `(?=${nonLetter}|$)` : '';
|
|
9456
|
+
return new RegExp(`${prefix}(${escaped})${suffix}`, flags);
|
|
9457
|
+
}
|
|
9458
|
+
return new RegExp(`(${escaped})`, flags);
|
|
9459
|
+
}
|
|
9460
|
+
/**
|
|
9461
|
+
* Escapes all regex special characters so the keyword is treated as literal text.
|
|
9462
|
+
*
|
|
9463
|
+
* @param {string} text - The raw input string.
|
|
9464
|
+
* @returns {string} The escaped string safe for use in a RegExp constructor.
|
|
9465
|
+
*/
|
|
9466
|
+
escapeRegExp(text) {
|
|
9467
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
9468
|
+
}
|
|
9469
|
+
/**
|
|
9470
|
+
* Recursively collects all searchable leaf blocks (RTETextBlock and RTECodeBlock)
|
|
9471
|
+
* from a block tree in document order.
|
|
9472
|
+
*
|
|
9473
|
+
* Each block is dispatched to the first matching BlockSearchStrategy.
|
|
9474
|
+
*
|
|
9475
|
+
* @param {RTEBlock[]} blocks - The block array to traverse.
|
|
9476
|
+
* @returns {(RTETextBlock | RTECodeBlock)[]} Ordered list of searchable leaf blocks.
|
|
9477
|
+
*/
|
|
9478
|
+
collectSearchableBlocks(blocks) {
|
|
9479
|
+
const result = [];
|
|
9480
|
+
const recurse = this.collectSearchableBlocks.bind(this);
|
|
9481
|
+
for (const block of blocks) {
|
|
9482
|
+
const strategy = this.blockSearchStrategies.find((s) => s.canHandle(block));
|
|
9483
|
+
if (strategy) {
|
|
9484
|
+
result.push(...strategy.collect(block, recurse));
|
|
9485
|
+
}
|
|
9486
|
+
}
|
|
9487
|
+
return result;
|
|
9488
|
+
}
|
|
9489
|
+
/**
|
|
9490
|
+
* Builds the flat text and segment table for a RTETextBlock.
|
|
9491
|
+
*
|
|
9492
|
+
* Non-editable child nodes (e.g. tokens, which render as read-only chips)
|
|
9493
|
+
* are excluded from the searchable flat text. Each is replaced by a single
|
|
9494
|
+
* sentinel character so that adjacent editable segments cannot bridge across
|
|
9495
|
+
* the token to form a false match, while keeping segment offsets aligned.
|
|
9496
|
+
*
|
|
9497
|
+
* @param {RTETextBlock} block - The text block to flatten.
|
|
9498
|
+
* @returns {BlockFlatCache} The flat text string and segment mapping.
|
|
9499
|
+
*/
|
|
9500
|
+
buildTextBlockFlat(block) {
|
|
9501
|
+
const segments = [];
|
|
9502
|
+
const flatParts = [];
|
|
9503
|
+
let cursor = 0;
|
|
9504
|
+
for (const node of block.children) {
|
|
9505
|
+
if (!node.isContentEditable()) {
|
|
9506
|
+
flatParts.push(NON_EDITABLE_SENTINEL);
|
|
9507
|
+
cursor += NON_EDITABLE_SENTINEL.length;
|
|
9508
|
+
continue;
|
|
9509
|
+
}
|
|
9510
|
+
const len = node.text.length;
|
|
9511
|
+
segments.push({ node, start: cursor, end: cursor + len });
|
|
9512
|
+
flatParts.push(node.text);
|
|
9513
|
+
cursor += len;
|
|
9514
|
+
}
|
|
9515
|
+
return {
|
|
9516
|
+
flatText: flatParts.join(''),
|
|
9517
|
+
segments,
|
|
9518
|
+
};
|
|
9519
|
+
}
|
|
9520
|
+
/**
|
|
9521
|
+
* Builds the flat text and segment table for a RTECodeBlock.
|
|
9522
|
+
*
|
|
9523
|
+
* @param {RTECodeBlock} block - The code block to flatten.
|
|
9524
|
+
* @returns {BlockFlatCache} The flat text string and segment mapping.
|
|
9525
|
+
*/
|
|
9526
|
+
buildCodeBlockFlat(block) {
|
|
9527
|
+
const codeNode = block.children[0];
|
|
9528
|
+
if (!codeNode) {
|
|
9529
|
+
return { flatText: '', segments: [] };
|
|
9530
|
+
}
|
|
9531
|
+
const flatText = codeNode.text;
|
|
9532
|
+
return {
|
|
9533
|
+
flatText,
|
|
9534
|
+
segments: [{ node: codeNode, start: 0, end: flatText.length }],
|
|
9535
|
+
};
|
|
9536
|
+
}
|
|
9537
|
+
/**
|
|
9538
|
+
* Maps a flat [matchStart, matchEnd) range back to individual node positions.
|
|
9539
|
+
*
|
|
9540
|
+
* @param {(TextSegment | CodeSegment)[]} segments - The segment table.
|
|
9541
|
+
* @param {number} matchStart - Inclusive start in the flat text.
|
|
9542
|
+
* @param {number} matchEnd - Exclusive end in the flat text.
|
|
9543
|
+
* @returns {NodePosition[]} Ordered list of node-local positions spanning the match.
|
|
9544
|
+
*/
|
|
9545
|
+
flatOffsetToNodePositions(segments, matchStart, matchEnd) {
|
|
9546
|
+
const positions = [];
|
|
9547
|
+
for (const segment of segments) {
|
|
9548
|
+
if (segment.end <= matchStart) {
|
|
9549
|
+
continue;
|
|
9550
|
+
}
|
|
9551
|
+
if (segment.start >= matchEnd) {
|
|
9552
|
+
break;
|
|
9553
|
+
}
|
|
9554
|
+
const localStart = Math.max(0, matchStart - segment.start);
|
|
9555
|
+
const localEnd = Math.min(segment.end, matchEnd) - segment.start;
|
|
9556
|
+
positions.push({ node: segment.node, startOffset: localStart, endOffset: localEnd });
|
|
9557
|
+
}
|
|
9558
|
+
return positions;
|
|
9559
|
+
}
|
|
9560
|
+
/**
|
|
9561
|
+
* Builds the current find options from the shared context.
|
|
9562
|
+
*
|
|
9563
|
+
* @returns {FindOptions} The options for the search engine.
|
|
9564
|
+
*/
|
|
9565
|
+
getFindOptions() {
|
|
9566
|
+
return { matchCase: this.matchCase, wholeWords: this.wholeWords };
|
|
9567
|
+
}
|
|
9568
|
+
}
|
|
9569
|
+
|
|
9570
|
+
// Light-mode values (used as CSS var() fall-backs).
|
|
9571
|
+
// Dark-mode values are registered via VegaDarkModeStyleController below.
|
|
9572
|
+
const HIGHLIGHT_BG_LIGHT = '255, 220, 0, 0.35';
|
|
9573
|
+
const HIGHLIGHT_ACTIVE_BG_LIGHT = '255, 150, 0, 0.5';
|
|
9574
|
+
// Dark-mode: brighter yellow/orange tones stand out on dark backgrounds.
|
|
9575
|
+
const HIGHLIGHT_BG_DARK = '255, 235, 0, 0.55';
|
|
9576
|
+
const HIGHLIGHT_ACTIVE_BG_DARK = '255, 190, 0, 0.7';
|
|
9577
|
+
// CSS var() expressions that auto-switch between light and dark.
|
|
9578
|
+
const HIGHLIGHT_BG = `rgba(var(--v-rte-find-highlight-bg, ${HIGHLIGHT_BG_LIGHT}))`;
|
|
9579
|
+
const HIGHLIGHT_ACTIVE_BG = `rgba(var(--v-rte-find-highlight-active-bg, ${HIGHLIGHT_ACTIVE_BG_LIGHT}))`;
|
|
9580
|
+
/**
|
|
9581
|
+
* Manages highlights for the Find & Replace extension using a DOM overlay.
|
|
9582
|
+
*
|
|
9583
|
+
* Instead of modifying the RTE content tree (which causes re-renders, cursor
|
|
9584
|
+
* jumping, and VegaChange events), this manager renders semi-transparent
|
|
9585
|
+
* highlight rectangles in an absolutely-positioned overlay container.
|
|
9586
|
+
*
|
|
9587
|
+
* The overlay lives inside `.rich-text-editor-container` (the RTE's scroll
|
|
9588
|
+
* container) so highlight divs scroll naturally with the text content.
|
|
9589
|
+
* `pointer-events: none` ensures highlights don't interfere with user
|
|
9590
|
+
* interaction (selection, typing, clicking).
|
|
9591
|
+
*
|
|
9592
|
+
* Position computation uses `Range.getClientRects()` on the actual text DOM
|
|
9593
|
+
* nodes (resolved via `stateEntityRenderingRegistry.getDOMByEntity`).
|
|
9594
|
+
*/
|
|
9595
|
+
class FindReplaceHighlightManager {
|
|
9596
|
+
constructor() {
|
|
9597
|
+
this.overlayContainer = null;
|
|
9598
|
+
// Register dark-mode overrides so `html.dark { --v-rte-find-highlight-*: ... }` is generated.
|
|
9599
|
+
// CSS variables cascade into shadow DOM and update automatically when html.dark is toggled.
|
|
9600
|
+
VegaDarkModeStyleController.registerCustomDarkModeColor({
|
|
9601
|
+
'rte-find-highlight-bg': HIGHLIGHT_BG_DARK,
|
|
9602
|
+
'rte-find-highlight-active-bg': HIGHLIGHT_ACTIVE_BG_DARK,
|
|
9603
|
+
'rte-find-highlight-blend': 'lighten',
|
|
9604
|
+
});
|
|
9605
|
+
}
|
|
9606
|
+
/**
|
|
9607
|
+
* Renders highlight overlays for all matches, with the active match
|
|
9608
|
+
* visually distinct. Clears any existing highlights before applying.
|
|
9609
|
+
*
|
|
9610
|
+
* @typedef HTMLVegaRichTextEditorElement - The type of the host rich text editor element.
|
|
9611
|
+
* @param {ReadonlyArray<FindMatch>} matches - All search results.
|
|
9612
|
+
* @param {number} currentIndex - 0-based index of the active match (-1 = none).
|
|
9613
|
+
* @param {HTMLVegaRichTextEditorElement} host - The RTE host element.
|
|
9614
|
+
* @param {boolean} scrollToActive - Whether to scroll the active highlight into view.
|
|
9615
|
+
*/
|
|
9616
|
+
apply(matches, currentIndex, host, scrollToActive = true) {
|
|
9617
|
+
this.clear();
|
|
9618
|
+
if (matches.length === 0) {
|
|
9619
|
+
return;
|
|
9620
|
+
}
|
|
9621
|
+
const overlay = this.getOrCreateOverlay(host);
|
|
9622
|
+
if (overlay) {
|
|
9623
|
+
const overlayRect = overlay.getBoundingClientRect();
|
|
9624
|
+
let activeHighlightDiv = null;
|
|
9625
|
+
matches.forEach((match, matchIndex) => {
|
|
9626
|
+
const isActive = matchIndex === currentIndex;
|
|
9627
|
+
const bg = isActive ? HIGHLIGHT_ACTIVE_BG : HIGHLIGHT_BG;
|
|
9628
|
+
match.nodes.forEach((nodePosition) => {
|
|
9629
|
+
const highlightDiv = this.createHighlightRectangles(overlay, overlayRect, nodePosition, bg);
|
|
9630
|
+
if (isActive && !activeHighlightDiv && highlightDiv) {
|
|
9631
|
+
activeHighlightDiv = highlightDiv;
|
|
9632
|
+
}
|
|
9633
|
+
});
|
|
9634
|
+
});
|
|
9635
|
+
if (scrollToActive && activeHighlightDiv) {
|
|
9636
|
+
activeHighlightDiv.scrollIntoView({ block: 'center', inline: 'nearest' });
|
|
9637
|
+
}
|
|
9638
|
+
}
|
|
9639
|
+
}
|
|
9640
|
+
/**
|
|
9641
|
+
* Removes all highlight overlay elements.
|
|
9642
|
+
* Safe to call multiple times or when no highlights are active.
|
|
9643
|
+
*/
|
|
9644
|
+
clear() {
|
|
9645
|
+
if (this.overlayContainer) {
|
|
9646
|
+
this.overlayContainer.innerHTML = '';
|
|
9647
|
+
}
|
|
9648
|
+
}
|
|
9649
|
+
// ─── Private helpers ──────────────────────────────────────────────────────────
|
|
9650
|
+
/**
|
|
9651
|
+
* Gets the existing overlay container or creates one inside the RTE's
|
|
9652
|
+
* scroll container (`.rich-text-editor-container`).
|
|
9653
|
+
*
|
|
9654
|
+
* @typedef HTMLVegaRichTextEditorElement - The type of the host rich text editor element.
|
|
9655
|
+
* @param {HTMLVegaRichTextEditorElement} host - The RTE host element.
|
|
9656
|
+
* @returns {HTMLDivElement | null} The overlay container, or null in SSR environments.
|
|
9657
|
+
*/
|
|
9658
|
+
getOrCreateOverlay(host) {
|
|
9659
|
+
if (this.overlayContainer && this.overlayContainer.isConnected) {
|
|
9660
|
+
return this.overlayContainer;
|
|
9661
|
+
}
|
|
9662
|
+
const safeDocument = tryGetDocument();
|
|
9663
|
+
if (safeDocument) {
|
|
9664
|
+
const scrollContainer = host.shadowRoot.querySelector('.rich-text-editor-container');
|
|
9665
|
+
if (scrollContainer) {
|
|
9666
|
+
// The overlay is appended as the last child (paints above content).
|
|
9667
|
+
// Blend mode is controlled via CSS variable --v-rte-find-highlight-blend:
|
|
9668
|
+
// - Light (default): multiply (yellow darkens white bg, text readable)
|
|
9669
|
+
// - Dark (via html.dark override): lighten (yellow visible on dark bg,
|
|
9670
|
+
// white text unaffected since lighten(255, x) = 255)
|
|
9671
|
+
// CSS vars cascade into shadow DOM and react to theme switches instantly.
|
|
9672
|
+
const overlay = safeDocument.createElement('div');
|
|
9673
|
+
overlay.style.position = 'absolute';
|
|
9674
|
+
overlay.style.top = '0';
|
|
9675
|
+
overlay.style.left = '0';
|
|
9676
|
+
overlay.style.width = '100%';
|
|
9677
|
+
overlay.style.height = '100%';
|
|
9678
|
+
overlay.style.pointerEvents = 'none';
|
|
9679
|
+
overlay.style.overflow = 'visible';
|
|
9680
|
+
overlay.style.setProperty('mix-blend-mode', 'var(--v-rte-find-highlight-blend, multiply)');
|
|
9681
|
+
scrollContainer.appendChild(overlay);
|
|
9682
|
+
this.overlayContainer = overlay;
|
|
9683
|
+
return overlay;
|
|
9684
|
+
}
|
|
9685
|
+
}
|
|
9686
|
+
return null;
|
|
9687
|
+
}
|
|
9688
|
+
/**
|
|
9689
|
+
* Creates absolutely-positioned highlight rectangles for a single node
|
|
9690
|
+
* position within a match.
|
|
9691
|
+
*
|
|
9692
|
+
* Uses client rectangle extraction from `Range` to handle multi-line matches (where a
|
|
9693
|
+
* single text range may produce multiple rectangles due to line wrapping).
|
|
9694
|
+
*
|
|
9695
|
+
* @param {HTMLDivElement} overlay - The overlay container.
|
|
9696
|
+
* @param {DOMRect} overlayRect - The overlay's bounding client rect (for coordinate conversion).
|
|
9697
|
+
* @param {NodePosition} nodePosition - The node and character offsets for this segment.
|
|
9698
|
+
* @param {string} bg - The background color to apply.
|
|
9699
|
+
* @returns {Nullable<HTMLDivElement>} The first created highlight div for this node segment.
|
|
9700
|
+
*/
|
|
9701
|
+
createHighlightRectangles(overlay, overlayRect, nodePosition, bg) {
|
|
9702
|
+
const domElement = stateEntityRenderingRegistry.getDOMByEntity(nodePosition.node);
|
|
9703
|
+
if (!domElement) {
|
|
9704
|
+
return null;
|
|
9705
|
+
}
|
|
9706
|
+
const range = this.buildRange(domElement, nodePosition);
|
|
9707
|
+
if (!range) {
|
|
9708
|
+
return null;
|
|
9709
|
+
}
|
|
9710
|
+
// eslint-disable-next-line spellcheck/spell-checker -- DOM API method name is fixed by platform.
|
|
9711
|
+
const clientRectList = range.getClientRects();
|
|
9712
|
+
const safeDocument = tryGetDocument();
|
|
9713
|
+
let firstHighlightDiv = null;
|
|
9714
|
+
if (safeDocument) {
|
|
9715
|
+
for (let i = 0; i < clientRectList.length; i++) {
|
|
9716
|
+
const rect = clientRectList[i];
|
|
9717
|
+
if (rect.width === 0 || rect.height === 0) {
|
|
9718
|
+
continue;
|
|
9719
|
+
}
|
|
9720
|
+
const div = safeDocument.createElement('div');
|
|
9721
|
+
div.style.position = 'absolute';
|
|
9722
|
+
div.style.top = `${rect.top - overlayRect.top}px`;
|
|
9723
|
+
div.style.left = `${rect.left - overlayRect.left}px`;
|
|
9724
|
+
div.style.width = `${rect.width}px`;
|
|
9725
|
+
div.style.height = `${rect.height}px`;
|
|
9726
|
+
div.style.backgroundColor = bg;
|
|
9727
|
+
div.style.pointerEvents = 'none';
|
|
9728
|
+
div.style.borderRadius = '2px';
|
|
9729
|
+
overlay.appendChild(div);
|
|
9730
|
+
if (!firstHighlightDiv) {
|
|
9731
|
+
firstHighlightDiv = div;
|
|
9732
|
+
}
|
|
9733
|
+
}
|
|
9734
|
+
}
|
|
9735
|
+
return firstHighlightDiv;
|
|
9736
|
+
}
|
|
9737
|
+
/**
|
|
9738
|
+
* Builds a DOM Range for the given node position.
|
|
9739
|
+
*
|
|
9740
|
+
* For standard text nodes (RTETextNode rendered as `<span>`), the first
|
|
9741
|
+
* child is a Text node and offsets apply directly.
|
|
9742
|
+
*
|
|
9743
|
+
* For code block nodes (`<vega-code-block>` with shadow DOM), we traverse
|
|
9744
|
+
* the shadow root's `<code>` element child spans to locate the correct
|
|
9745
|
+
* text positions by accumulated character offset.
|
|
9746
|
+
*
|
|
9747
|
+
* @param {HTMLElement} domElement - The registered DOM element for the node.
|
|
9748
|
+
* @param {NodePosition} nodePosition - The character offsets within the node.
|
|
9749
|
+
* @returns {Nullable<Range>} A Range if successfully built, or null on failure.
|
|
9750
|
+
*/
|
|
9751
|
+
buildRange(domElement, nodePosition) {
|
|
9752
|
+
// <vega-code-block> wraps syntax-highlighted tokens inside a shadow root <code> element.
|
|
9753
|
+
if (nodePosition.node instanceof RTECodeBlockNode) {
|
|
9754
|
+
return this.buildCodeBlockRange(domElement, nodePosition);
|
|
9755
|
+
}
|
|
9756
|
+
// Standard text node: <span> with a direct Text child.
|
|
9757
|
+
const textNode = domElement.firstChild;
|
|
9758
|
+
if (!textNode) {
|
|
9759
|
+
return null;
|
|
9760
|
+
}
|
|
9761
|
+
const safeDocument = tryGetDocument();
|
|
9762
|
+
if (safeDocument) {
|
|
9763
|
+
const range = safeDocument.createRange();
|
|
9764
|
+
try {
|
|
9765
|
+
range.setStart(textNode, nodePosition.startOffset);
|
|
9766
|
+
range.setEnd(textNode, nodePosition.endOffset);
|
|
9767
|
+
return range;
|
|
9768
|
+
}
|
|
9769
|
+
catch (_a) {
|
|
9770
|
+
return null;
|
|
9771
|
+
}
|
|
9772
|
+
}
|
|
9773
|
+
return null;
|
|
9774
|
+
}
|
|
9775
|
+
/**
|
|
9776
|
+
* Builds a Range inside a `<vega-code-block>` shadow DOM element.
|
|
9777
|
+
*
|
|
9778
|
+
* `<vega-code-block>` renders its content inside a shadow root with a `<code>`
|
|
9779
|
+
* element containing multiple child `<span>` nodes (one per syntax token).
|
|
9780
|
+
* This method walks all Text nodes within that `<code>` element, accumulating
|
|
9781
|
+
* character length until the positions corresponding to startOffset / endOffset
|
|
9782
|
+
* are found.
|
|
9783
|
+
*
|
|
9784
|
+
* @param {HTMLElement} domElement - The `<vega-code-block>` host element.
|
|
9785
|
+
* @param {NodePosition} nodePosition - The character offsets within the node.
|
|
9786
|
+
* @returns {Range | null} A Range if successfully built, or null on failure.
|
|
9787
|
+
*/
|
|
9788
|
+
buildCodeBlockRange(domElement, nodePosition) {
|
|
9789
|
+
const shadowRoot = domElement.shadowRoot;
|
|
9790
|
+
const codeEl = shadowRoot.querySelector('code');
|
|
9791
|
+
if (!codeEl) {
|
|
9792
|
+
return null;
|
|
9793
|
+
}
|
|
9794
|
+
const safeDocument = tryGetDocument();
|
|
9795
|
+
if (safeDocument) {
|
|
9796
|
+
const walker = safeDocument.createTreeWalker(codeEl, NodeFilter.SHOW_TEXT);
|
|
9797
|
+
let accumulated = 0;
|
|
9798
|
+
let startNode = null;
|
|
9799
|
+
let startLocal = 0;
|
|
9800
|
+
let endNode = null;
|
|
9801
|
+
let endLocal = 0;
|
|
9802
|
+
let current = walker.nextNode();
|
|
9803
|
+
while (current) {
|
|
9804
|
+
const len = current.textContent.length;
|
|
9805
|
+
if (!startNode && accumulated + len > nodePosition.startOffset) {
|
|
9806
|
+
startNode = current;
|
|
9807
|
+
startLocal = nodePosition.startOffset - accumulated;
|
|
9808
|
+
}
|
|
9809
|
+
if (accumulated + len >= nodePosition.endOffset) {
|
|
9810
|
+
endNode = current;
|
|
9811
|
+
endLocal = nodePosition.endOffset - accumulated;
|
|
9812
|
+
break;
|
|
9813
|
+
}
|
|
9814
|
+
accumulated += len;
|
|
9815
|
+
current = walker.nextNode();
|
|
9816
|
+
}
|
|
9817
|
+
if (startNode && endNode) {
|
|
9818
|
+
const range = safeDocument.createRange();
|
|
9819
|
+
try {
|
|
9820
|
+
range.setStart(startNode, startLocal);
|
|
9821
|
+
range.setEnd(endNode, endLocal);
|
|
9822
|
+
return range;
|
|
9823
|
+
}
|
|
9824
|
+
catch (_a) {
|
|
9825
|
+
return null;
|
|
9826
|
+
}
|
|
9827
|
+
}
|
|
9828
|
+
}
|
|
9829
|
+
return null;
|
|
9830
|
+
}
|
|
9831
|
+
}
|
|
9832
|
+
|
|
9833
|
+
/**
|
|
9834
|
+
* Creates a new shared controller context with default (empty) state.
|
|
9835
|
+
*
|
|
9836
|
+
* @returns {FindReplaceControllerContext} A fresh context instance.
|
|
9837
|
+
*/
|
|
9838
|
+
function createFindReplaceControllerContext() {
|
|
9839
|
+
return {
|
|
9840
|
+
state: new FindReplaceState(),
|
|
9841
|
+
highlightManager: new FindReplaceHighlightManager(),
|
|
9842
|
+
hostRef: null,
|
|
9843
|
+
keyword: '',
|
|
9844
|
+
};
|
|
9845
|
+
}
|
|
9846
|
+
/**
|
|
9847
|
+
* Abstract base controller for the Find & Replace extension.
|
|
9848
|
+
*
|
|
9849
|
+
* Holds shared operations ({@link setHost}, {@link setKeyword}) and the
|
|
9850
|
+
* common helper {@link applyAndGetResult}. Concrete implementations
|
|
9851
|
+
* ({@link FindController}, {@link ReplaceController}) add the respective
|
|
9852
|
+
* search/navigation and mutation capabilities.
|
|
9853
|
+
*/
|
|
9854
|
+
class FindReplaceControllerAbstract {
|
|
9855
|
+
constructor(context) {
|
|
9856
|
+
this.context = context;
|
|
9857
|
+
}
|
|
9858
|
+
/**
|
|
9859
|
+
* Updates the cached host reference. Called on each render cycle.
|
|
9860
|
+
*
|
|
9861
|
+
* @typedef HTMLVegaRichTextEditorElement - The type of the host rich text editor element.
|
|
9862
|
+
* @param {HTMLVegaRichTextEditorElement} host - The RTE host element.
|
|
9863
|
+
*/
|
|
9864
|
+
setHost(host) {
|
|
9865
|
+
this.context.hostRef = host;
|
|
9866
|
+
}
|
|
9867
|
+
/**
|
|
9868
|
+
* Updates the keyword. Clears previous match results and highlights,
|
|
9869
|
+
* but preserves the flat-text cache so the next find() on the same
|
|
9870
|
+
* document does not re-flatten every block.
|
|
9871
|
+
*
|
|
9872
|
+
* @param {string} keyword - The new search keyword.
|
|
9873
|
+
* @returns {FindReplaceResult} Reset result (0/0).
|
|
9874
|
+
*/
|
|
9875
|
+
setKeyword(keyword) {
|
|
9876
|
+
this.context.keyword = keyword;
|
|
9877
|
+
this.context.state.clearResults();
|
|
9878
|
+
if (this.context.hostRef) {
|
|
9879
|
+
this.context.highlightManager.clear();
|
|
9880
|
+
}
|
|
9881
|
+
return { current: 0, total: 0 };
|
|
9882
|
+
}
|
|
9883
|
+
// ─── Protected ────────────────────────────────────────────────────────────────
|
|
9884
|
+
/**
|
|
9885
|
+
* Applies highlight overlays and returns the current result for the panel.
|
|
9886
|
+
*
|
|
9887
|
+
* @param {boolean} scrollToActive - Whether to scroll the active highlight into view.
|
|
9888
|
+
* @returns {FindReplaceResult} The current match position and total count.
|
|
9889
|
+
*/
|
|
9890
|
+
applyAndGetResult(scrollToActive = true) {
|
|
9891
|
+
const total = this.context.state.getTotal();
|
|
9892
|
+
const current = total > 0 ? this.context.state.getCurrentIndex() + 1 : 0;
|
|
9893
|
+
if (this.context.hostRef) {
|
|
9894
|
+
this.context.highlightManager.apply(this.context.state.getMatches(), this.context.state.getCurrentIndex(), this.context.hostRef, scrollToActive);
|
|
9895
|
+
}
|
|
9896
|
+
return { current, total };
|
|
9897
|
+
}
|
|
9898
|
+
}
|
|
9899
|
+
|
|
9900
|
+
/**
|
|
9901
|
+
* Concrete find controller.
|
|
9902
|
+
*
|
|
9903
|
+
* Provides search, navigation, highlight, and lifecycle capabilities.
|
|
9904
|
+
* Shares state with {@link ReplaceController} via the injected
|
|
9905
|
+
* {@link FindReplaceControllerContext}.
|
|
9906
|
+
*/
|
|
9907
|
+
class FindController extends FindReplaceControllerAbstract {
|
|
9908
|
+
constructor(context) {
|
|
9909
|
+
super(context);
|
|
9910
|
+
}
|
|
9911
|
+
/**
|
|
9912
|
+
* Runs a search with the current keyword on the host's content.
|
|
9913
|
+
* Applies highlights and returns the result for panel display.
|
|
9914
|
+
*
|
|
9915
|
+
* @returns {FindReplaceResult} The match count and current position.
|
|
9916
|
+
*/
|
|
9917
|
+
find() {
|
|
9918
|
+
if (!this.context.hostRef) {
|
|
9919
|
+
return { current: 0, total: 0 };
|
|
9920
|
+
}
|
|
9921
|
+
this.context.state.search(this.context.hostRef.value, this.context.keyword);
|
|
9922
|
+
return this.applyAndGetResult();
|
|
9923
|
+
}
|
|
9924
|
+
/**
|
|
9925
|
+
* Navigates to the next match.
|
|
9926
|
+
*
|
|
9927
|
+
* @returns {FindReplaceResult} Updated position.
|
|
9928
|
+
*/
|
|
9929
|
+
next() {
|
|
9930
|
+
this.context.state.next();
|
|
9931
|
+
return this.applyAndGetResult();
|
|
9932
|
+
}
|
|
9933
|
+
/**
|
|
9934
|
+
* Navigates to the previous match.
|
|
9935
|
+
*
|
|
9936
|
+
* @returns {FindReplaceResult} Updated position.
|
|
9937
|
+
*/
|
|
9938
|
+
prev() {
|
|
9939
|
+
this.context.state.prev();
|
|
9940
|
+
return this.applyAndGetResult();
|
|
9941
|
+
}
|
|
9942
|
+
/**
|
|
9943
|
+
* Clears all search state and removes highlight overlays.
|
|
9944
|
+
*/
|
|
9945
|
+
clear() {
|
|
9946
|
+
this.context.state.clear();
|
|
9947
|
+
if (this.context.hostRef) {
|
|
9948
|
+
this.context.highlightManager.clear();
|
|
9949
|
+
}
|
|
9950
|
+
}
|
|
9951
|
+
/**
|
|
9952
|
+
* Re-runs the search after content has changed (e.g. user typed while
|
|
9953
|
+
* panel is open). Attempts to preserve the previous navigation position.
|
|
9954
|
+
*
|
|
9955
|
+
* @param {boolean} scrollToActive - Whether to scroll the active highlight into view.
|
|
9956
|
+
* @returns {FindReplaceResult | null} Updated result, or null if no search is active.
|
|
9957
|
+
*/
|
|
9958
|
+
refresh(scrollToActive = true) {
|
|
9959
|
+
if (!this.context.keyword || !this.context.hostRef) {
|
|
9960
|
+
return null;
|
|
9961
|
+
}
|
|
9962
|
+
const prevIndex = this.context.state.getCurrentIndex();
|
|
9963
|
+
this.context.state.search(this.context.hostRef.value, this.context.keyword);
|
|
9964
|
+
this.context.state.setCurrentIndex(prevIndex);
|
|
9965
|
+
return this.applyAndGetResult(scrollToActive);
|
|
9966
|
+
}
|
|
9967
|
+
/**
|
|
9968
|
+
* Toggles the match-case search option, clears previous results,
|
|
9969
|
+
* and re-runs the search if a keyword is set.
|
|
9970
|
+
*
|
|
9971
|
+
* @param {boolean} enabled - Whether match-case should be enabled.
|
|
9972
|
+
* @returns {FindReplaceResult} The updated search result.
|
|
9973
|
+
*/
|
|
9974
|
+
setMatchCase(enabled) {
|
|
9975
|
+
this.context.state.setMatchCase(enabled);
|
|
9976
|
+
return this.updateFindResults();
|
|
9977
|
+
}
|
|
9978
|
+
/**
|
|
9979
|
+
* Toggles the whole-words search option, clears previous results,
|
|
9980
|
+
* and re-runs the search if a keyword is set.
|
|
9981
|
+
*
|
|
9982
|
+
* @param {boolean} enabled - Whether whole-words should be enabled.
|
|
9983
|
+
* @returns {FindReplaceResult} The updated search result.
|
|
9984
|
+
*/
|
|
9985
|
+
setWholeWords(enabled) {
|
|
9986
|
+
this.context.state.setWholeWords(enabled);
|
|
9987
|
+
return this.updateFindResults();
|
|
9988
|
+
}
|
|
9989
|
+
// ─── Private helpers ──────────────────────────────────────────────────────────
|
|
9990
|
+
/**
|
|
9991
|
+
* Clears previous results and re-runs the search with the current keyword and options.
|
|
9992
|
+
*
|
|
9993
|
+
* @returns {FindReplaceResult} The updated search result.
|
|
9994
|
+
*/
|
|
9995
|
+
updateFindResults() {
|
|
9996
|
+
this.context.state.clearResults();
|
|
9997
|
+
if (this.context.keyword && this.context.hostRef) {
|
|
9998
|
+
this.context.state.search(this.context.hostRef.value, this.context.keyword);
|
|
9999
|
+
return this.applyAndGetResult();
|
|
10000
|
+
}
|
|
10001
|
+
return { current: 0, total: 0 };
|
|
10002
|
+
}
|
|
10003
|
+
}
|
|
10004
|
+
|
|
10005
|
+
/**
|
|
10006
|
+
* Replace controller — adds mutation capabilities on top of the shared
|
|
10007
|
+
* find state.
|
|
10008
|
+
*
|
|
10009
|
+
* Shares state with {@link FindController} via the injected
|
|
10010
|
+
* {@link FindReplaceControllerContext}, so both controllers always operate
|
|
10011
|
+
* on the same search results and highlight overlays.
|
|
10012
|
+
*/
|
|
10013
|
+
class ReplaceController extends FindReplaceControllerAbstract {
|
|
10014
|
+
constructor(context) {
|
|
10015
|
+
super(context);
|
|
10016
|
+
/** The replacement string used by replace() and replaceAll(). Updated via setReplacement(). */
|
|
10017
|
+
this.replacement = '';
|
|
10018
|
+
this.flushFn = null;
|
|
10019
|
+
}
|
|
10020
|
+
/**
|
|
10021
|
+
* Stores the flush callback used to commit content changes to the editor.
|
|
10022
|
+
* Called by the toolbar renderer after obtaining it from the extension context.
|
|
10023
|
+
*
|
|
10024
|
+
* Calling `flushValue` once — even after mutating multiple nodes — creates a
|
|
10025
|
+
* single entry in the editor's undo stack, so the entire replace/replaceAll
|
|
10026
|
+
* operation is undone as one atomic step.
|
|
10027
|
+
*
|
|
10028
|
+
* @param {(value: VegaRTEContent) => void} fn - The flush function.
|
|
10029
|
+
* @returns {void} No return value.
|
|
10030
|
+
*/
|
|
10031
|
+
setFlushValue(fn) {
|
|
10032
|
+
this.flushFn = fn;
|
|
10033
|
+
}
|
|
10034
|
+
/**
|
|
10035
|
+
* Stores the replacement text for the next replace() or replaceAll() call.
|
|
10036
|
+
*
|
|
10037
|
+
* @param {string} text - The replacement string.
|
|
10038
|
+
* @returns {void} No return value.
|
|
10039
|
+
*/
|
|
10040
|
+
setReplacement(text) {
|
|
10041
|
+
this.replacement = text;
|
|
10042
|
+
}
|
|
10043
|
+
/**
|
|
10044
|
+
* Replaces the current match with the stored replacement text,
|
|
10045
|
+
* advances to the next match, and refreshes highlights.
|
|
10046
|
+
* An empty replacement string deletes the matched text.
|
|
10047
|
+
*
|
|
10048
|
+
* No-op when there is no current match or no host.
|
|
10049
|
+
*
|
|
10050
|
+
* @returns {FindReplaceResult} Updated match position and total.
|
|
10051
|
+
*/
|
|
10052
|
+
replace() {
|
|
10053
|
+
const currentMatch = this.context.state.getCurrentMatch();
|
|
10054
|
+
if (currentMatch && this.context.hostRef && this.flushFn) {
|
|
10055
|
+
const previousIndex = this.context.state.getCurrentIndex();
|
|
10056
|
+
this.applyMatchReplacement(currentMatch);
|
|
10057
|
+
this.flushFn(this.context.hostRef.value);
|
|
10058
|
+
this.context.state.clear();
|
|
10059
|
+
this.context.state.search(this.context.hostRef.value, this.context.keyword);
|
|
10060
|
+
// setCurrentIndex clamps to [0, total-1], so passing previousIndex
|
|
10061
|
+
// directly keeps the cursor as close as possible to the replaced position
|
|
10062
|
+
// rather than wrapping around when previousIndex >= total.
|
|
10063
|
+
this.context.state.setCurrentIndex(previousIndex);
|
|
10064
|
+
}
|
|
10065
|
+
return this.applyAndGetResult();
|
|
10066
|
+
}
|
|
10067
|
+
/**
|
|
10068
|
+
* Replaces all matches with the stored replacement text in a single operation
|
|
10069
|
+
* (one flushValue call = one Undo step), then re-runs the search.
|
|
10070
|
+
* An empty replacement string deletes all matched text.
|
|
10071
|
+
*
|
|
10072
|
+
* No-op when there are no matches or no host.
|
|
10073
|
+
*
|
|
10074
|
+
* @returns {FindReplaceResult} Updated match position and total.
|
|
10075
|
+
*/
|
|
10076
|
+
replaceAll() {
|
|
10077
|
+
if (this.context.state.getTotal() > 0 && this.flushFn && this.context.hostRef) {
|
|
10078
|
+
const matches = this.context.state.getMatches();
|
|
10079
|
+
// Iterate in reverse so that mutating a later match does not shift the
|
|
10080
|
+
// startOffset/endOffset of earlier matches that have not yet been processed.
|
|
10081
|
+
for (let i = matches.length - 1; i >= 0; i -= 1) {
|
|
10082
|
+
this.applyMatchReplacement(matches[i]);
|
|
10083
|
+
}
|
|
10084
|
+
this.flushFn(this.context.hostRef.value);
|
|
10085
|
+
this.context.state.clear();
|
|
10086
|
+
this.context.state.search(this.context.hostRef.value, this.context.keyword);
|
|
10087
|
+
}
|
|
10088
|
+
return this.applyAndGetResult();
|
|
10089
|
+
}
|
|
10090
|
+
// ─── Private ──────────────────────────────────────────────────────────────────
|
|
10091
|
+
/**
|
|
10092
|
+
* Directly mutates the matched nodes in the live content tree.
|
|
10093
|
+
* `position.node` is already a reference into `this.context.hostRef.value`,
|
|
10094
|
+
* so no cloning or path navigation is needed.
|
|
10095
|
+
*
|
|
10096
|
+
* For multi-node (cross-span) matches the first node receives the replacement
|
|
10097
|
+
* text and every subsequent node has its matched slice deleted.
|
|
10098
|
+
*
|
|
10099
|
+
* This method mutates the content tree in place. The caller is
|
|
10100
|
+
* responsible for calling `state.clear()` afterwards to invalidate the
|
|
10101
|
+
* flat-text cache, which uses reference equality and will not detect the
|
|
10102
|
+
* in-place change automatically.
|
|
10103
|
+
*
|
|
10104
|
+
* @param {FindMatch} match - The match to replace.
|
|
10105
|
+
* @returns {void} No return value.
|
|
10106
|
+
*/
|
|
10107
|
+
applyMatchReplacement(match) {
|
|
10108
|
+
match.nodes.forEach((position, index) => {
|
|
10109
|
+
const replacementText = index === 0 ? this.replacement : '';
|
|
10110
|
+
position.node.text =
|
|
10111
|
+
position.node.text.slice(0, position.startOffset) +
|
|
10112
|
+
replacementText +
|
|
10113
|
+
position.node.text.slice(position.endOffset);
|
|
10114
|
+
});
|
|
10115
|
+
}
|
|
10116
|
+
}
|
|
10117
|
+
|
|
10118
|
+
/**
|
|
10119
|
+
* Toolbar button renderer for the Find & Replace extension.
|
|
10120
|
+
*
|
|
10121
|
+
* Responsible only for UI concerns:
|
|
10122
|
+
* - Renders the toolbar button wrapped in a vega-popover.
|
|
10123
|
+
* - Wires panel events to the {@link FindController} and {@link ReplaceController}.
|
|
10124
|
+
* - Pushes controller results back to the panel display.
|
|
10125
|
+
*
|
|
10126
|
+
* Both controllers share a single {@link FindReplaceControllerContext} so they
|
|
10127
|
+
* operate on the same search state and highlight overlays.
|
|
10128
|
+
*/
|
|
10129
|
+
class FindReplaceToolbarButtonRenderer extends VegaRTEToolbarButtonRenderer {
|
|
10130
|
+
constructor() {
|
|
10131
|
+
super(...arguments);
|
|
10132
|
+
this.sharedContext = createFindReplaceControllerContext();
|
|
10133
|
+
this.findController = new FindController(this.sharedContext);
|
|
10134
|
+
this.replaceController = new ReplaceController(this.sharedContext);
|
|
10135
|
+
this.panelRef = null;
|
|
10136
|
+
this.popoverRef = null;
|
|
10137
|
+
this.isPopoverOpen = false;
|
|
10138
|
+
this.scrollOnNextRefresh = false;
|
|
10139
|
+
this.debouncedRefresh = debounce(this.onContentChanged, 100, this);
|
|
10140
|
+
/* eslint-disable jsdoc/require-jsdoc */
|
|
10141
|
+
this.panelActionHandlers = {
|
|
10142
|
+
'find': () => this.findController.find(),
|
|
10143
|
+
'prev': () => this.findController.prev(),
|
|
10144
|
+
'next': () => this.findController.next(),
|
|
10145
|
+
'replace': () => {
|
|
10146
|
+
this.scrollOnNextRefresh = true;
|
|
10147
|
+
return this.replaceController.replace();
|
|
10148
|
+
},
|
|
10149
|
+
'replace-all': () => {
|
|
10150
|
+
this.scrollOnNextRefresh = true;
|
|
10151
|
+
return this.replaceController.replaceAll();
|
|
10152
|
+
},
|
|
10153
|
+
'match-case': (data) => this.findController.setMatchCase(data),
|
|
10154
|
+
'whole-words': (data) => this.findController.setWholeWords(data),
|
|
10155
|
+
'close': () => this.closePanel(),
|
|
10156
|
+
};
|
|
10157
|
+
}
|
|
10158
|
+
/* eslint-enable jsdoc/require-jsdoc */
|
|
10159
|
+
/**
|
|
10160
|
+
* @inheritDoc
|
|
10161
|
+
*/
|
|
10162
|
+
render(h, editorContext) {
|
|
10163
|
+
this.syncEditorState(editorContext);
|
|
10164
|
+
return h('vega-popover', {
|
|
10165
|
+
trigger: 'none',
|
|
10166
|
+
alignment: 'start',
|
|
10167
|
+
placement: 'bottom',
|
|
10168
|
+
showArrow: false,
|
|
10169
|
+
isScreenPosition: true,
|
|
10170
|
+
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
10171
|
+
ref: (ref) => {
|
|
10172
|
+
if (ref) {
|
|
10173
|
+
this.popoverRef = ref;
|
|
10174
|
+
DomNodeSubjectObserverFactory.addUniqueObserverToNode(ref, VegaPopoverHide, () => {
|
|
10175
|
+
this.isPopoverOpen = false;
|
|
10176
|
+
this.findController.clear();
|
|
10177
|
+
this.updatePanel({ current: 0, total: 0 });
|
|
10178
|
+
});
|
|
10179
|
+
DomNodeSubjectObserverFactory.addUniqueObserverToNode(ref, VegaPopoverShow, () => {
|
|
10180
|
+
this.isPopoverOpen = true;
|
|
10181
|
+
});
|
|
10182
|
+
}
|
|
10183
|
+
},
|
|
10184
|
+
}, [
|
|
10185
|
+
h('div', { slot: 'popover' }, [
|
|
10186
|
+
h('vega-rich-text-find-replace-panel', {
|
|
10187
|
+
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
10188
|
+
ref: (ref) => {
|
|
10189
|
+
if (ref) {
|
|
10190
|
+
this.panelRef = ref;
|
|
10191
|
+
DomNodeSubjectObserverFactory.addUniqueObserverToNode(ref, VegaChange, (e) => {
|
|
10192
|
+
if (e.detail.type === 'keyword') {
|
|
10193
|
+
this.updatePanel(this.findController.setKeyword(e.detail.value));
|
|
10194
|
+
}
|
|
10195
|
+
else if (e.detail.type === 'replace') {
|
|
10196
|
+
this.replaceController.setReplacement(e.detail.value);
|
|
10197
|
+
}
|
|
10198
|
+
});
|
|
10199
|
+
DomNodeSubjectObserverFactory.addUniqueObserverToNode(ref, VegaClick, (payload) => {
|
|
10200
|
+
this.handlePanelAction(payload.detail);
|
|
10201
|
+
});
|
|
10202
|
+
}
|
|
10203
|
+
},
|
|
10204
|
+
}),
|
|
10205
|
+
]),
|
|
10206
|
+
h('div', { slot: 'popover-content' }, this.renderButton(h, editorContext, {
|
|
10207
|
+
icon: 'rte-find-replace',
|
|
10208
|
+
tooltip: { text: VegaInternalTranslation.t('Find and replace') },
|
|
10209
|
+
})),
|
|
10210
|
+
]);
|
|
10211
|
+
}
|
|
10212
|
+
/**
|
|
10213
|
+
* Toggles the popover open/close when the toolbar button is clicked.
|
|
10214
|
+
*/
|
|
10215
|
+
handleButtonClick() {
|
|
10216
|
+
if (this.popoverRef) {
|
|
10217
|
+
if (this.isPopoverOpen) {
|
|
10218
|
+
void this.popoverRef.hide();
|
|
10219
|
+
}
|
|
10220
|
+
else {
|
|
10221
|
+
void this.popoverRef.show();
|
|
10222
|
+
}
|
|
10223
|
+
}
|
|
10224
|
+
}
|
|
10225
|
+
/* istanbul ignore next */
|
|
10226
|
+
/**
|
|
10227
|
+
* Not used — this button has no dropdown.
|
|
10228
|
+
*/
|
|
10229
|
+
handleDropdownClick() {
|
|
10230
|
+
// no-op
|
|
10231
|
+
}
|
|
10232
|
+
// ─── Private helpers ──────────────────────────────────────────────────────────
|
|
10233
|
+
/**
|
|
10234
|
+
* Synchronizes internal state with the editor context on each render cycle.
|
|
10235
|
+
* Registers the host reference and content-change listener, and hides the
|
|
10236
|
+
* popover if the toolbar item becomes hidden while open.
|
|
10237
|
+
*
|
|
10238
|
+
* @param {VegaRTEToolbarRenderContext} editorContext - The render context.
|
|
10239
|
+
*/
|
|
10240
|
+
syncEditorState(editorContext) {
|
|
10241
|
+
this.findController.setHost(editorContext.host);
|
|
10242
|
+
const extensionContext = this.getExtensionContext(editorContext.host);
|
|
10243
|
+
if (extensionContext) {
|
|
10244
|
+
this.replaceController.setFlushValue(extensionContext.flushValue);
|
|
10245
|
+
}
|
|
10246
|
+
DomNodeSubjectObserverFactory.addUniqueObserverToNode(editorContext.host, VegaChange, this.debouncedRefresh);
|
|
10247
|
+
if (this.isHidden(editorContext) && this.isPopoverOpen && this.popoverRef) {
|
|
10248
|
+
void this.popoverRef.hide();
|
|
10249
|
+
}
|
|
10250
|
+
}
|
|
10251
|
+
/**
|
|
10252
|
+
* Dispatches panel button actions to the controller.
|
|
10253
|
+
*
|
|
10254
|
+
* @param {FindReplacePanelAction} panelAction - The structured action emitted by the panel.
|
|
10255
|
+
*/
|
|
10256
|
+
handlePanelAction(panelAction) {
|
|
10257
|
+
const { action, data } = panelAction;
|
|
10258
|
+
const handler = this.panelActionHandlers[action];
|
|
10259
|
+
if (handler) {
|
|
10260
|
+
const result = handler(data);
|
|
10261
|
+
if (result) {
|
|
10262
|
+
this.updatePanel(result);
|
|
10263
|
+
}
|
|
10264
|
+
}
|
|
10265
|
+
}
|
|
10266
|
+
/**
|
|
10267
|
+
* Pushes result to the panel counter display.
|
|
10268
|
+
*
|
|
10269
|
+
* @param {FindReplaceResult} result - The current/total values.
|
|
10270
|
+
*/
|
|
10271
|
+
updatePanel(result) {
|
|
10272
|
+
if (this.panelRef) {
|
|
10273
|
+
void this.panelRef.updateResult(result.current, result.total);
|
|
10274
|
+
}
|
|
10275
|
+
}
|
|
10276
|
+
/**
|
|
10277
|
+
* Content changed — ask the controller to refresh search results.
|
|
10278
|
+
*/
|
|
10279
|
+
onContentChanged() {
|
|
10280
|
+
if (!this.isPopoverOpen) {
|
|
10281
|
+
return;
|
|
10282
|
+
}
|
|
10283
|
+
const shouldScroll = this.scrollOnNextRefresh;
|
|
10284
|
+
this.scrollOnNextRefresh = false;
|
|
10285
|
+
const result = this.findController.refresh(shouldScroll);
|
|
10286
|
+
if (result) {
|
|
10287
|
+
this.updatePanel(result);
|
|
10288
|
+
}
|
|
10289
|
+
}
|
|
10290
|
+
/**
|
|
10291
|
+
* Closes the panel popover.
|
|
10292
|
+
*/
|
|
10293
|
+
closePanel() {
|
|
10294
|
+
if (this.popoverRef) {
|
|
10295
|
+
void this.popoverRef.hide();
|
|
10296
|
+
}
|
|
10297
|
+
}
|
|
10298
|
+
}
|
|
10299
|
+
(() => {
|
|
10300
|
+
VegaInternalIconManager.register({ 'rte-find-replace': findReplaceIcon });
|
|
10301
|
+
})();
|
|
7978
10302
|
|
|
7979
|
-
|
|
7980
|
-
|
|
7981
|
-
|
|
7982
|
-
|
|
7983
|
-
|
|
7984
|
-
|
|
7985
|
-
|
|
7986
|
-
|
|
7987
|
-
|
|
7988
|
-
|
|
7989
|
-
|
|
7990
|
-
{
|
|
7991
|
-
|
|
7992
|
-
|
|
7993
|
-
|
|
7994
|
-
|
|
7995
|
-
{ character: 'ď', title: 'Latin small letter d with caron' },
|
|
7996
|
-
{ character: 'Đ', title: 'Latin capital letter d with stroke' },
|
|
7997
|
-
{ character: 'đ', title: 'Latin small letter d with stroke' },
|
|
7998
|
-
{ character: 'Ē', title: 'Latin capital letter e with macron' },
|
|
7999
|
-
{ character: 'ē', title: 'Latin small letter e with macron' },
|
|
8000
|
-
{ character: 'Ĕ', title: 'Latin capital letter e with breve' },
|
|
8001
|
-
{ character: 'ĕ', title: 'Latin small letter e with breve' },
|
|
8002
|
-
{ character: 'Ė', title: 'Latin capital letter e with dot above' },
|
|
8003
|
-
{ character: 'ė', title: 'Latin small letter e with dot above' },
|
|
8004
|
-
{ character: 'Ę', title: 'Latin capital letter e with ogonek' },
|
|
8005
|
-
{ character: 'ę', title: 'Latin small letter e with ogonek' },
|
|
8006
|
-
{ character: 'Ě', title: 'Latin capital letter e with caron' },
|
|
8007
|
-
{ character: 'ě', title: 'Latin small letter e with caron' },
|
|
8008
|
-
{ character: 'Ĝ', title: 'Latin capital letter g with circumflex' },
|
|
8009
|
-
{ character: 'ĝ', title: 'Latin small letter g with circumflex' },
|
|
8010
|
-
{ character: 'Ğ', title: 'Latin capital letter g with breve' },
|
|
8011
|
-
{ character: 'ğ', title: 'Latin small letter g with breve' },
|
|
8012
|
-
{ character: 'Ġ', title: 'Latin capital letter g with dot above' },
|
|
8013
|
-
{ character: 'ġ', title: 'Latin small letter g with dot above' },
|
|
8014
|
-
{ character: 'Ģ', title: 'Latin capital letter g with cedilla' },
|
|
8015
|
-
{ character: 'ģ', title: 'Latin small letter g with cedilla' },
|
|
8016
|
-
{ character: 'Ĥ', title: 'Latin capital letter h with circumflex' },
|
|
8017
|
-
{ character: 'ĥ', title: 'Latin small letter h with circumflex' },
|
|
8018
|
-
{ character: 'Ħ', title: 'Latin capital letter h with stroke' },
|
|
8019
|
-
{ character: 'ħ', title: 'Latin small letter h with stroke' },
|
|
8020
|
-
{ character: 'Ĩ', title: 'Latin capital letter i with tilde' },
|
|
8021
|
-
{ character: 'ĩ', title: 'Latin small letter i with tilde' },
|
|
8022
|
-
{ character: 'Ī', title: 'Latin capital letter i with macron' },
|
|
8023
|
-
{ character: 'ī', title: 'Latin small letter i with macron' },
|
|
8024
|
-
{ character: 'Ĭ', title: 'Latin capital letter i with breve' },
|
|
8025
|
-
{ character: 'ĭ', title: 'Latin small letter i with breve' },
|
|
8026
|
-
{ character: 'Į', title: 'Latin capital letter i with ogonek' },
|
|
8027
|
-
{ character: 'į', title: 'Latin small letter i with ogonek' },
|
|
8028
|
-
{ character: 'İ', title: 'Latin capital letter i with dot above' },
|
|
8029
|
-
{ character: 'ı', title: 'Latin small letter dotless i' },
|
|
8030
|
-
{ character: 'IJ', title: 'Latin capital ligature ij' },
|
|
8031
|
-
{ character: 'ij', title: 'Latin small ligature ij' },
|
|
8032
|
-
{ character: 'Ĵ', title: 'Latin capital letter j with circumflex' },
|
|
8033
|
-
{ character: 'ĵ', title: 'Latin small letter j with circumflex' },
|
|
8034
|
-
{ character: 'Ķ', title: 'Latin capital letter k with cedilla' },
|
|
8035
|
-
{ character: 'ķ', title: 'Latin small letter k with cedilla' },
|
|
8036
|
-
{ character: 'ĸ', title: 'Latin small letter kra' },
|
|
8037
|
-
{ character: 'Ĺ', title: 'Latin capital letter l with acute' },
|
|
8038
|
-
{ character: 'ĺ', title: 'Latin small letter l with acute' },
|
|
8039
|
-
{ character: 'Ļ', title: 'Latin capital letter l with cedilla' },
|
|
8040
|
-
{ character: 'ļ', title: 'Latin small letter l with cedilla' },
|
|
8041
|
-
{ character: 'Ľ', title: 'Latin capital letter l with caron' },
|
|
8042
|
-
{ character: 'ľ', title: 'Latin small letter l with caron' },
|
|
8043
|
-
{ character: 'Ŀ', title: 'Latin capital letter l with middle dot' },
|
|
8044
|
-
{ character: 'ŀ', title: 'Latin small letter l with middle dot' },
|
|
8045
|
-
{ character: 'Ł', title: 'Latin capital letter l with stroke' },
|
|
8046
|
-
{ character: 'ł', title: 'Latin small letter l with stroke' },
|
|
8047
|
-
{ character: 'Ń', title: 'Latin capital letter n with acute' },
|
|
8048
|
-
{ character: 'ń', title: 'Latin small letter n with acute' },
|
|
8049
|
-
{ character: 'Ņ', title: 'Latin capital letter n with cedilla' },
|
|
8050
|
-
{ character: 'ņ', title: 'Latin small letter n with cedilla' },
|
|
8051
|
-
{ character: 'Ň', title: 'Latin capital letter n with caron' },
|
|
8052
|
-
{ character: 'ň', title: 'Latin small letter n with caron' },
|
|
8053
|
-
{ character: 'ʼn', title: 'Latin small letter n preceded by apostrophe' },
|
|
8054
|
-
{ character: 'Ŋ', title: 'Latin capital letter eng' },
|
|
8055
|
-
{ character: 'ŋ', title: 'Latin small letter eng' },
|
|
8056
|
-
{ character: 'Ō', title: 'Latin capital letter o with macron' },
|
|
8057
|
-
{ character: 'ō', title: 'Latin small letter o with macron' },
|
|
8058
|
-
{ character: 'Ŏ', title: 'Latin capital letter o with breve' },
|
|
8059
|
-
{ character: 'ŏ', title: 'Latin small letter o with breve' },
|
|
8060
|
-
{ character: 'Ő', title: 'Latin capital letter o with double acute' },
|
|
8061
|
-
{ character: 'ő', title: 'Latin small letter o with double acute' },
|
|
8062
|
-
{ character: 'Œ', title: 'Latin capital ligature oe' },
|
|
8063
|
-
{ character: 'œ', title: 'Latin small ligature oe' },
|
|
8064
|
-
{ character: 'Ŕ', title: 'Latin capital letter r with acute' },
|
|
8065
|
-
{ character: 'ŕ', title: 'Latin small letter r with acute' },
|
|
8066
|
-
{ character: 'Ŗ', title: 'Latin capital letter r with cedilla' },
|
|
8067
|
-
{ character: 'ŗ', title: 'Latin small letter r with cedilla' },
|
|
8068
|
-
{ character: 'Ř', title: 'Latin capital letter r with caron' },
|
|
8069
|
-
{ character: 'ř', title: 'Latin small letter r with caron' },
|
|
8070
|
-
{ character: 'Ś', title: 'Latin capital letter s with acute' },
|
|
8071
|
-
{ character: 'ś', title: 'Latin small letter s with acute' },
|
|
8072
|
-
{ character: 'Ŝ', title: 'Latin capital letter s with circumflex' },
|
|
8073
|
-
{ character: 'ŝ', title: 'Latin small letter s with circumflex' },
|
|
8074
|
-
{ character: 'Ş', title: 'Latin capital letter s with cedilla' },
|
|
8075
|
-
{ character: 'ş', title: 'Latin small letter s with cedilla' },
|
|
8076
|
-
{ character: 'Š', title: 'Latin capital letter s with caron' },
|
|
8077
|
-
{ character: 'š', title: 'Latin small letter s with caron' },
|
|
8078
|
-
{ character: 'Ţ', title: 'Latin capital letter t with cedilla' },
|
|
8079
|
-
{ character: 'ţ', title: 'Latin small letter t with cedilla' },
|
|
8080
|
-
{ character: 'Ť', title: 'Latin capital letter t with caron' },
|
|
8081
|
-
{ character: 'ť', title: 'Latin small letter t with caron' },
|
|
8082
|
-
{ character: 'Ŧ', title: 'Latin capital letter t with stroke' },
|
|
8083
|
-
{ character: 'ŧ', title: 'Latin small letter t with stroke' },
|
|
8084
|
-
{ character: 'Ũ', title: 'Latin capital letter u with tilde' },
|
|
8085
|
-
{ character: 'ũ', title: 'Latin small letter u with tilde' },
|
|
8086
|
-
{ character: 'Ū', title: 'Latin capital letter u with macron' },
|
|
8087
|
-
{ character: 'ū', title: 'Latin small letter u with macron' },
|
|
8088
|
-
{ character: 'Ŭ', title: 'Latin capital letter u with breve' },
|
|
8089
|
-
{ character: 'ŭ', title: 'Latin small letter u with breve' },
|
|
8090
|
-
{ character: 'Ů', title: 'Latin capital letter u with ring above' },
|
|
8091
|
-
{ character: 'ů', title: 'Latin small letter u with ring above' },
|
|
8092
|
-
{ character: 'Ű', title: 'Latin capital letter u with double acute' },
|
|
8093
|
-
{ character: 'ű', title: 'Latin small letter u with double acute' },
|
|
8094
|
-
{ character: 'Ų', title: 'Latin capital letter u with ogonek' },
|
|
8095
|
-
{ character: 'ų', title: 'Latin small letter u with ogonek' },
|
|
8096
|
-
{ character: 'Ŵ', title: 'Latin capital letter w with circumflex' },
|
|
8097
|
-
{ character: 'ŵ', title: 'Latin small letter w with circumflex' },
|
|
8098
|
-
{ character: 'Ŷ', title: 'Latin capital letter y with circumflex' },
|
|
8099
|
-
{ character: 'ŷ', title: 'Latin small letter y with circumflex' },
|
|
8100
|
-
{ character: 'Ÿ', title: 'Latin capital letter y with diaeresis' },
|
|
8101
|
-
{ character: 'Ź', title: 'Latin capital letter z with acute' },
|
|
8102
|
-
{ character: 'ź', title: 'Latin small letter z with acute' },
|
|
8103
|
-
{ character: 'Ż', title: 'Latin capital letter z with dot above' },
|
|
8104
|
-
{ character: 'ż', title: 'Latin small letter z with dot above' },
|
|
8105
|
-
{ character: 'Ž', title: 'Latin capital letter z with caron' },
|
|
8106
|
-
{ character: 'ž', title: 'Latin small letter z with caron' },
|
|
8107
|
-
{ character: 'ſ', title: 'Latin small letter long s' },
|
|
8108
|
-
];
|
|
10303
|
+
/**
|
|
10304
|
+
* Find & Replace extension for Vega Rich Text Editor.
|
|
10305
|
+
*
|
|
10306
|
+
* Registers a toolbar button (magnifying-glass icon) that toggles the find-replace panel.
|
|
10307
|
+
* Each editor host gets its own isolated FindReplaceState and toolbar renderer instance,
|
|
10308
|
+
* created during the per-host prepareBeforeLoad lifecycle hook.
|
|
10309
|
+
*
|
|
10310
|
+
* Toolbar key: 'findReplace'
|
|
10311
|
+
*/
|
|
10312
|
+
class VegaRTEFindReplaceExtension extends VegaRTEExtension {
|
|
10313
|
+
/** @inheritDoc */
|
|
10314
|
+
prepareBeforeLoad(host) {
|
|
10315
|
+
this.registerToolbarButtonRenderer(VegaRTEFindReplaceExtension.TOOLBAR_BUTTON_KEY, new FindReplaceToolbarButtonRenderer(), host);
|
|
10316
|
+
}
|
|
10317
|
+
}
|
|
10318
|
+
VegaRTEFindReplaceExtension.TOOLBAR_BUTTON_KEY = 'findReplace';
|
|
8109
10319
|
|
|
8110
|
-
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
{
|
|
8116
|
-
{
|
|
8117
|
-
{
|
|
8118
|
-
{
|
|
8119
|
-
{
|
|
8120
|
-
{ character: '−', title: 'Minus sign' },
|
|
8121
|
-
{ character: '±', title: 'Plus-minus sign' },
|
|
8122
|
-
{ character: '÷', title: 'Division sign' },
|
|
8123
|
-
{ character: '⁄', title: 'Fraction slash' },
|
|
8124
|
-
{ character: '×', title: 'Multiplication sign' },
|
|
8125
|
-
{ character: 'ƒ', title: 'Latin small letter f with hook' },
|
|
8126
|
-
{ character: '∫', title: 'Integral' },
|
|
8127
|
-
{ character: '∑', title: 'N-ary summation' },
|
|
8128
|
-
{ character: '∞', title: 'Infinity' },
|
|
8129
|
-
{ character: '√', title: 'Square root' },
|
|
8130
|
-
{ character: '∼', title: 'Tilde operator' },
|
|
8131
|
-
{ character: '≅', title: 'Approximately equal to' },
|
|
8132
|
-
{ character: '≈', title: 'Almost equal to' },
|
|
8133
|
-
{ character: '≠', title: 'Not equal to' },
|
|
8134
|
-
{ character: '≡', title: 'Identical to' },
|
|
8135
|
-
{ character: '∈', title: 'Element of' },
|
|
8136
|
-
{ character: '∉', title: 'Not an element of' },
|
|
8137
|
-
{ character: '∋', title: 'Contains as member' },
|
|
8138
|
-
{ character: '∏', title: 'N-ary product' },
|
|
8139
|
-
{ character: '∧', title: 'Logical and' },
|
|
8140
|
-
{ character: '∨', title: 'Logical or' },
|
|
8141
|
-
{ character: '¬', title: 'Not sign' },
|
|
8142
|
-
{ character: '∩', title: 'Intersection' },
|
|
8143
|
-
{ character: '∪', title: 'Union' },
|
|
8144
|
-
{ character: '∂', title: 'Partial differential' },
|
|
8145
|
-
{ character: '∀', title: 'For all' },
|
|
8146
|
-
{ character: '∃', title: 'There exists' },
|
|
8147
|
-
{ character: '∅', title: 'Empty set' },
|
|
8148
|
-
{ character: '∇', title: 'Nabla' },
|
|
8149
|
-
{ character: '∗', title: 'Asterisk operator' },
|
|
8150
|
-
{ character: '∝', title: 'Proportional to' },
|
|
8151
|
-
{ character: '∠', title: 'Angle' },
|
|
8152
|
-
{ character: '¼', title: 'Vulgar fraction one quarter' },
|
|
8153
|
-
{ character: '½', title: 'Vulgar fraction one half' },
|
|
8154
|
-
{ character: '¾', title: 'Vulgar fraction three quarters' },
|
|
10320
|
+
/**
|
|
10321
|
+
* Default line height options used by the line-height extension when no
|
|
10322
|
+
* custom options are provided.
|
|
10323
|
+
*/
|
|
10324
|
+
const VegaRTEDefaultLineHeightOptions = [
|
|
10325
|
+
{ title: '0.5', value: '0.5' },
|
|
10326
|
+
{ title: '1', value: '1' },
|
|
10327
|
+
{ title: '1.5', value: '1.5' },
|
|
10328
|
+
{ title: '2', value: '2' },
|
|
10329
|
+
{ title: '2.5', value: '2.5' },
|
|
8155
10330
|
];
|
|
8156
10331
|
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
{
|
|
8166
|
-
|
|
8167
|
-
|
|
8168
|
-
|
|
8169
|
-
|
|
8170
|
-
|
|
8171
|
-
|
|
8172
|
-
|
|
8173
|
-
|
|
8174
|
-
|
|
8175
|
-
|
|
8176
|
-
|
|
8177
|
-
|
|
8178
|
-
|
|
8179
|
-
|
|
8180
|
-
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
10332
|
+
var RTELineHeightExtensionAnnotationTypeEnum;
|
|
10333
|
+
(function (RTELineHeightExtensionAnnotationTypeEnum) {
|
|
10334
|
+
RTELineHeightExtensionAnnotationTypeEnum["LINE_HEIGHT"] = "LINE_HEIGHT";
|
|
10335
|
+
})(RTELineHeightExtensionAnnotationTypeEnum || (RTELineHeightExtensionAnnotationTypeEnum = {}));
|
|
10336
|
+
/**
|
|
10337
|
+
* Annotation to set block line height.
|
|
10338
|
+
*/
|
|
10339
|
+
class LineHeightAnnotation extends BlockAnnotation {
|
|
10340
|
+
constructor(lineHeight) {
|
|
10341
|
+
super();
|
|
10342
|
+
this.type = RTELineHeightExtensionAnnotationTypeEnum.LINE_HEIGHT;
|
|
10343
|
+
this.lineHeight = lineHeight;
|
|
10344
|
+
}
|
|
10345
|
+
/**
|
|
10346
|
+
* Generate the map item for the annotation.
|
|
10347
|
+
*
|
|
10348
|
+
* @param {string} lineHeight - Line height value.
|
|
10349
|
+
* @returns {[RTELineHeightExtensionAnnotationTypeEnum.LINE_HEIGHT, LineHeightAnnotation]} Map item for the annotation.
|
|
10350
|
+
*/
|
|
10351
|
+
static from(lineHeight) {
|
|
10352
|
+
return [RTELineHeightExtensionAnnotationTypeEnum.LINE_HEIGHT, new LineHeightAnnotation(lineHeight)];
|
|
10353
|
+
}
|
|
10354
|
+
/**
|
|
10355
|
+
* Get the default standalone styles for a given line height value.
|
|
10356
|
+
*
|
|
10357
|
+
* @param {string} lineHeight - The line height value.
|
|
10358
|
+
* @returns {AnnotationStyle} The default standalone styles.
|
|
10359
|
+
*/
|
|
10360
|
+
static getDefaultStyles(lineHeight) {
|
|
10361
|
+
return { lineHeight };
|
|
10362
|
+
}
|
|
10363
|
+
/**
|
|
10364
|
+
* Generate the JSON item of the annotation.
|
|
10365
|
+
*
|
|
10366
|
+
* @returns {Record<string, unknown>} JSON representation of the annotation.
|
|
10367
|
+
*/
|
|
10368
|
+
toJSON() {
|
|
10369
|
+
return {
|
|
10370
|
+
lineHeight: this.lineHeight,
|
|
10371
|
+
};
|
|
10372
|
+
}
|
|
10373
|
+
/**
|
|
10374
|
+
* @inheritDoc
|
|
10375
|
+
*/
|
|
10376
|
+
renderClass() {
|
|
10377
|
+
return null;
|
|
10378
|
+
}
|
|
10379
|
+
/**
|
|
10380
|
+
* @inheritDoc
|
|
10381
|
+
*/
|
|
10382
|
+
clone() {
|
|
10383
|
+
return new LineHeightAnnotation(this.lineHeight);
|
|
10384
|
+
}
|
|
10385
|
+
/**
|
|
10386
|
+
* @inheritDoc
|
|
10387
|
+
*/
|
|
10388
|
+
renderStyle() {
|
|
10389
|
+
return LineHeightAnnotation.getDefaultStyles(this.lineHeight);
|
|
10390
|
+
}
|
|
10391
|
+
}
|
|
10392
|
+
|
|
10393
|
+
/**
|
|
10394
|
+
* Action to update block line height annotation.
|
|
10395
|
+
*/
|
|
10396
|
+
class LineHeightAnnotationAction extends AnnotationAction {
|
|
10397
|
+
constructor(lineHeight) {
|
|
10398
|
+
super();
|
|
10399
|
+
this.isFlushable = true;
|
|
10400
|
+
this.lineHeight = lineHeight;
|
|
10401
|
+
}
|
|
10402
|
+
/**
|
|
10403
|
+
* Converts the action to an annotation.
|
|
10404
|
+
*
|
|
10405
|
+
* @returns {LineHeightAnnotation} The annotation.
|
|
10406
|
+
*/
|
|
10407
|
+
toAnnotation() {
|
|
10408
|
+
return new LineHeightAnnotation(this.lineHeight);
|
|
10409
|
+
}
|
|
10410
|
+
}
|
|
8186
10411
|
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
|
|
8192
|
-
|
|
8193
|
-
|
|
8194
|
-
|
|
8195
|
-
|
|
8196
|
-
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
label: 'Currency',
|
|
8205
|
-
characters: CURRENCY_CHARACTERS,
|
|
8206
|
-
};
|
|
8207
|
-
const VegaRTESpecialCharactersText = {
|
|
8208
|
-
key: 'text',
|
|
8209
|
-
label: 'Text',
|
|
8210
|
-
characters: TEXT_CHARACTERS,
|
|
8211
|
-
};
|
|
8212
|
-
const VegaRTESpecialCharactersEssentials = [
|
|
8213
|
-
VegaRTESpecialCharactersCurrency,
|
|
8214
|
-
VegaRTESpecialCharactersText,
|
|
8215
|
-
VegaRTESpecialCharactersMathematical,
|
|
8216
|
-
VegaRTESpecialCharactersArrows,
|
|
8217
|
-
VegaRTESpecialCharactersLatin,
|
|
8218
|
-
];
|
|
10412
|
+
/**
|
|
10413
|
+
* Update the block line height strategy.
|
|
10414
|
+
*
|
|
10415
|
+
* Registered for RTETextBlock and RTEListItemBlock, so the
|
|
10416
|
+
* target type is widened to the common RTEBlock base.
|
|
10417
|
+
*/
|
|
10418
|
+
class BlockUpdateLineHeightStrategy extends ActionHandleStrategy {
|
|
10419
|
+
/**
|
|
10420
|
+
* Updating the line height of a block requires cascading updates to the custom style.
|
|
10421
|
+
*
|
|
10422
|
+
* @param {LineHeightAnnotationAction} _action - The line height annotation action.
|
|
10423
|
+
* @param {RTEBlock} target - The current block.
|
|
10424
|
+
*/
|
|
10425
|
+
handleAction(_action, target) {
|
|
10426
|
+
this.updateCustomStyleAnnotation(target, ['lineHeight']);
|
|
10427
|
+
}
|
|
10428
|
+
}
|
|
8219
10429
|
|
|
8220
|
-
const
|
|
8221
|
-
icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0
|
|
10430
|
+
const lineHeight = {
|
|
10431
|
+
icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Pro 6.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path fill="currentColor" d="M113.8 39.9c-4.6-5.1-11-7.9-17.8-7.9s-13.3 2.9-17.8 7.9l-72 80c-8.9 9.9-8.1 25 1.8 33.9s25 8.1 33.9-1.8L72 118.5V393.5L41.8 359.9c-8.9-9.9-24-10.7-33.9-1.8s-10.7 24-1.8 33.9l72 80c4.6 5.1 11 7.9 17.8 7.9s13.3-2.9 17.8-7.9l72-80c8.9-9.9 8.1-25-1.8-33.9s-25-8.1-33.9 1.8L120 393.5V118.5l30.2 33.5c8.9 9.9 24 10.7 33.9 1.8s10.7-24 1.8-33.9l-72-80zM248 72c-13.3 0-24 10.7-24 24s10.7 24 24 24H552c13.3 0 24-10.7 24-24s-10.7-24-24-24H248zm0 160c-13.3 0-24 10.7-24 24s10.7 24 24 24H552c13.3 0 24-10.7 24-24s-10.7-24-24-24H248zM224 416c0 13.3 10.7 24 24 24H552c13.3 0 24-10.7 24-24s-10.7-24-24-24H248c-13.3 0-24 10.7-24 24z"/></svg>`,
|
|
8222
10432
|
};
|
|
8223
10433
|
|
|
8224
10434
|
/**
|
|
8225
|
-
*
|
|
8226
|
-
* Renders a toolbar button with an Ω icon that opens a popover panel
|
|
8227
|
-
* with categorized character grids.
|
|
10435
|
+
* Renderer for the line-height toolbar button in the Vega Rich Text Editor.
|
|
8228
10436
|
*/
|
|
8229
|
-
class
|
|
8230
|
-
constructor(
|
|
10437
|
+
class RTELineHeightToolbarButtonRenderer extends VegaRTEToolbarButtonRenderer {
|
|
10438
|
+
constructor(options) {
|
|
8231
10439
|
super();
|
|
8232
|
-
this.
|
|
8233
|
-
this.categories = categories;
|
|
10440
|
+
this.options = options;
|
|
8234
10441
|
}
|
|
8235
10442
|
/**
|
|
8236
|
-
*
|
|
8237
|
-
*
|
|
8238
|
-
* @param {VegaRTECreateElementFunction} h - Create element function.
|
|
8239
|
-
* @param {VegaRTEToolbarRenderContext} editorContext - The render context for the editor.
|
|
8240
|
-
* @returns {VegaRTERenderResult} - The toolbar button render result.
|
|
10443
|
+
* @inheritDoc
|
|
8241
10444
|
*/
|
|
8242
|
-
render(
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
|
|
8247
|
-
|
|
8248
|
-
|
|
8249
|
-
|
|
8250
|
-
|
|
8251
|
-
|
|
8252
|
-
|
|
8253
|
-
this.handlePopoverShow();
|
|
8254
|
-
});
|
|
8255
|
-
}
|
|
8256
|
-
},
|
|
10445
|
+
render(createElement, editorContext) {
|
|
10446
|
+
const dropdownItems = this.options.map((item) => {
|
|
10447
|
+
var _a;
|
|
10448
|
+
return ({
|
|
10449
|
+
key: item.value,
|
|
10450
|
+
label: (_a = item.title) !== null && _a !== void 0 ? _a : item.value,
|
|
10451
|
+
});
|
|
10452
|
+
});
|
|
10453
|
+
return this.renderDropdown(createElement, editorContext, {
|
|
10454
|
+
selectType: 'single',
|
|
10455
|
+
selectedSourceKey: this.getSelectedSourceValue(editorContext),
|
|
8257
10456
|
}, [
|
|
8258
|
-
|
|
8259
|
-
|
|
8260
|
-
|
|
8261
|
-
|
|
8262
|
-
|
|
8263
|
-
|
|
8264
|
-
this.panelRef = ref;
|
|
8265
|
-
DomNodeSubjectObserverFactory.addUniqueObserverToNode(ref, VegaClick,
|
|
8266
|
-
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
8267
|
-
(payload) => {
|
|
8268
|
-
this.insertCharacter(editorContext, payload.detail);
|
|
8269
|
-
});
|
|
8270
|
-
}
|
|
8271
|
-
},
|
|
8272
|
-
}),
|
|
8273
|
-
]),
|
|
8274
|
-
h('div', { slot: 'popover-content' }, this.renderButton(h, editorContext, {
|
|
8275
|
-
icon: 'rte-special-characters',
|
|
8276
|
-
tooltip: { text: 'Special Characters' },
|
|
8277
|
-
})),
|
|
10457
|
+
this.renderButton(createElement, editorContext, {
|
|
10458
|
+
icon: 'line-height',
|
|
10459
|
+
showArrowIcon: true,
|
|
10460
|
+
tooltip: { text: VegaInternalTranslation.t('Line Height') },
|
|
10461
|
+
}),
|
|
10462
|
+
this.renderDropdownListTemplate(createElement, dropdownItems, editorContext),
|
|
8278
10463
|
]);
|
|
8279
10464
|
}
|
|
8280
10465
|
/**
|
|
8281
10466
|
* @inheritDoc
|
|
8282
10467
|
*/
|
|
8283
|
-
|
|
8284
|
-
|
|
10468
|
+
isSelected() {
|
|
10469
|
+
return false;
|
|
8285
10470
|
}
|
|
8286
|
-
/* istanbul ignore next */
|
|
8287
10471
|
/**
|
|
8288
10472
|
* @inheritDoc
|
|
8289
10473
|
*/
|
|
8290
|
-
|
|
8291
|
-
|
|
10474
|
+
isDisabled(editorContext) {
|
|
10475
|
+
var _a;
|
|
10476
|
+
if (editorContext.host.disabled) {
|
|
10477
|
+
return true;
|
|
10478
|
+
}
|
|
10479
|
+
const extensionContext = this.getExtensionContext(editorContext.host);
|
|
10480
|
+
const selectedNodes = (_a = extensionContext === null || extensionContext === void 0 ? void 0 : extensionContext.getSelectedNodes()) !== null && _a !== void 0 ? _a : [];
|
|
10481
|
+
if (selectedNodes.some((node) => {
|
|
10482
|
+
var _a;
|
|
10483
|
+
return ((_a = node.getAnnotationByType(NodeAnnotationTypeEnum.CODE)) === null || _a === void 0 ? void 0 : _a.code) ||
|
|
10484
|
+
node.parent instanceof RTECodeBlock;
|
|
10485
|
+
})) {
|
|
10486
|
+
return true;
|
|
10487
|
+
}
|
|
10488
|
+
const selectedBlocks = this.getSelectedBlocks(editorContext);
|
|
10489
|
+
return selectedBlocks.some((block) => block instanceof RTECodeBlock ||
|
|
10490
|
+
block instanceof RTEImageBlock ||
|
|
10491
|
+
!ActionHandleStrategyRegistry.canHandle(LineHeightAnnotationAction.name, block));
|
|
8292
10492
|
}
|
|
8293
10493
|
/**
|
|
8294
|
-
*
|
|
8295
|
-
* popover opens.
|
|
10494
|
+
* @inheritDoc
|
|
8296
10495
|
*/
|
|
8297
|
-
|
|
8298
|
-
|
|
8299
|
-
|
|
10496
|
+
handleDropdownClick(itemValue, editorContext) {
|
|
10497
|
+
const selectedLineHeight = itemValue;
|
|
10498
|
+
const selectedBlocks = this.getSelectedBlocks(editorContext);
|
|
10499
|
+
selectedBlocks.forEach((block) => {
|
|
10500
|
+
block.apply(new LineHeightAnnotationAction(selectedLineHeight));
|
|
10501
|
+
});
|
|
10502
|
+
}
|
|
10503
|
+
/**
|
|
10504
|
+
* Handle direct toolbar button click.
|
|
10505
|
+
*/
|
|
10506
|
+
handleButtonClick() {
|
|
10507
|
+
// No direct click action; selection happens in dropdown options.
|
|
10508
|
+
}
|
|
10509
|
+
/**
|
|
10510
|
+
* Get the selected line-height key for dropdown selection state.
|
|
10511
|
+
*
|
|
10512
|
+
* @param {VegaRTEToolbarRenderContext} editorContext - Current toolbar context.
|
|
10513
|
+
* @returns {string} Selected line-height key or empty string for mixed/none.
|
|
10514
|
+
*/
|
|
10515
|
+
getSelectedSourceValue(editorContext) {
|
|
10516
|
+
const selectedBlocks = this.getSelectedBlocks(editorContext);
|
|
10517
|
+
if (selectedBlocks.length === 0) {
|
|
10518
|
+
return '';
|
|
10519
|
+
}
|
|
10520
|
+
const firstAnnotation = selectedBlocks[0].getAnnotationByType(RTELineHeightExtensionAnnotationTypeEnum.LINE_HEIGHT);
|
|
10521
|
+
const isSameLineHeight = selectedBlocks.every((block) => {
|
|
10522
|
+
const annotation = block.getAnnotationByType(RTELineHeightExtensionAnnotationTypeEnum.LINE_HEIGHT);
|
|
10523
|
+
return (annotation === null || annotation === void 0 ? void 0 : annotation.lineHeight) === (firstAnnotation === null || firstAnnotation === void 0 ? void 0 : firstAnnotation.lineHeight);
|
|
10524
|
+
});
|
|
10525
|
+
return isSameLineHeight && firstAnnotation ? firstAnnotation.lineHeight : '';
|
|
10526
|
+
}
|
|
10527
|
+
/**
|
|
10528
|
+
* Resolve selected blocks from selected nodes with range fallback.
|
|
10529
|
+
*
|
|
10530
|
+
* @param {VegaRTEToolbarRenderContext} editorContext - Current toolbar context.
|
|
10531
|
+
* @returns {RTEBlock[]} Selected blocks.
|
|
10532
|
+
*/
|
|
10533
|
+
getSelectedBlocks(editorContext) {
|
|
10534
|
+
var _a, _b;
|
|
10535
|
+
const extensionContext = this.getExtensionContext(editorContext.host);
|
|
10536
|
+
const selectedNodes = (_a = extensionContext === null || extensionContext === void 0 ? void 0 : extensionContext.getSelectedNodes()) !== null && _a !== void 0 ? _a : [];
|
|
10537
|
+
const selectedBlocks = [];
|
|
10538
|
+
selectedNodes.forEach((node) => {
|
|
10539
|
+
if (node.parent instanceof RTEBlock && !selectedBlocks.includes(node.parent)) {
|
|
10540
|
+
selectedBlocks.push(node.parent);
|
|
10541
|
+
}
|
|
10542
|
+
});
|
|
10543
|
+
if (selectedBlocks.length === 0 && ((_b = editorContext.rteRange.startNode) === null || _b === void 0 ? void 0 : _b.parent) instanceof RTEBlock) {
|
|
10544
|
+
selectedBlocks.push(editorContext.rteRange.startNode.parent);
|
|
8300
10545
|
}
|
|
10546
|
+
return selectedBlocks;
|
|
10547
|
+
}
|
|
10548
|
+
}
|
|
10549
|
+
(() => {
|
|
10550
|
+
VegaInternalIconManager.register({
|
|
10551
|
+
'line-height': lineHeight,
|
|
10552
|
+
});
|
|
10553
|
+
})();
|
|
10554
|
+
|
|
10555
|
+
const LINE_HEIGHT_AWARE_BLOCK_NAMES = new Set([
|
|
10556
|
+
RTETextBlock.name,
|
|
10557
|
+
RTEListItemBlock.name,
|
|
10558
|
+
]);
|
|
10559
|
+
/** Handle line height annotation. */
|
|
10560
|
+
class LineHeightAnnotationHandler extends AnnotationGeneratorStrategyAbstract {
|
|
10561
|
+
constructor(options = VegaRTEDefaultLineHeightOptions) {
|
|
10562
|
+
super();
|
|
10563
|
+
this.allowedLineHeights = new Set(options.map((option) => option.value));
|
|
8301
10564
|
}
|
|
8302
10565
|
/**
|
|
8303
|
-
*
|
|
8304
|
-
* then restores the cursor position after flushing.
|
|
10566
|
+
* Can be handled.
|
|
8305
10567
|
*
|
|
8306
|
-
* @param {
|
|
8307
|
-
* @
|
|
10568
|
+
* @param {RTEDtoClassPrototype} targetDto - Target DTO.
|
|
10569
|
+
* @returns {boolean} Whether the target DTO supports line-height annotations.
|
|
8308
10570
|
*/
|
|
8309
|
-
|
|
8310
|
-
|
|
8311
|
-
const cursorNode = currentRange.startNode;
|
|
8312
|
-
if (!cursorNode)
|
|
8313
|
-
return;
|
|
8314
|
-
const target = this.isCaretOnLineBreak(cursorNode, currentRange)
|
|
8315
|
-
? this.insertAtLineBreak(cursorNode, char, currentRange)
|
|
8316
|
-
: this.insertAtTextNode(cursorNode, char, currentRange);
|
|
8317
|
-
editorContext.host.value.apply(new UpdateCursorPositionAction(target.node, target.offset));
|
|
10571
|
+
canHandle(targetDto) {
|
|
10572
|
+
return LINE_HEIGHT_AWARE_BLOCK_NAMES.has(targetDto.name);
|
|
8318
10573
|
}
|
|
8319
10574
|
/**
|
|
8320
|
-
*
|
|
10575
|
+
* Extract inline `line-height` as a semantic `lineHeight` annotation.
|
|
8321
10576
|
*
|
|
8322
|
-
* @param {
|
|
8323
|
-
* @
|
|
8324
|
-
* @returns {boolean} True if the caret is collapsed on a '\n' text node.
|
|
10577
|
+
* @param {HTMLElement} [element] - Current element.
|
|
10578
|
+
* @returns {VegaRTEBlockAnnotations} `{ lineHeight }` when present, otherwise `{}`.
|
|
8325
10579
|
*/
|
|
8326
|
-
|
|
8327
|
-
|
|
8328
|
-
|
|
8329
|
-
|
|
10580
|
+
handle(element) {
|
|
10581
|
+
var _a, _b;
|
|
10582
|
+
const lineHeight = (_b = (_a = element === null || element === void 0 ? void 0 : element.style.lineHeight) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : '';
|
|
10583
|
+
if (!lineHeight) {
|
|
10584
|
+
return {};
|
|
10585
|
+
}
|
|
10586
|
+
if (!this.allowedLineHeights.has(lineHeight)) {
|
|
10587
|
+
return {};
|
|
10588
|
+
}
|
|
10589
|
+
return { lineHeight };
|
|
10590
|
+
}
|
|
10591
|
+
}
|
|
10592
|
+
|
|
10593
|
+
/**
|
|
10594
|
+
* Removes a redundant inline `customStyle.lineHeight` when it duplicates the
|
|
10595
|
+
* semantic `lineHeight` annotation during HTML-to-DTO conversion.
|
|
10596
|
+
*
|
|
10597
|
+
* Note: moving out-of-list line-height values from the semantic annotation to
|
|
10598
|
+
* `customStyle.lineHeight` is handled by the per-host interceptor in
|
|
10599
|
+
* `line-height-host-interceptor.ts`, not here.
|
|
10600
|
+
*/
|
|
10601
|
+
class LineHeightFilterStylesStrategy extends RTEFilterStylesStrategy {
|
|
10602
|
+
constructor() {
|
|
10603
|
+
super(...arguments);
|
|
10604
|
+
this.stylesToBeFiltered = {};
|
|
8330
10605
|
}
|
|
8331
10606
|
/**
|
|
8332
|
-
*
|
|
10607
|
+
* Always run this strategy, even when autoMatchFormat is disabled.
|
|
8333
10608
|
*
|
|
8334
|
-
* @
|
|
8335
|
-
* @param {string} char - The Unicode character to insert.
|
|
8336
|
-
* @param {RTERange} range - The current selection range.
|
|
8337
|
-
* @returns {CaretTarget} The node and offset for the resulting caret position.
|
|
10609
|
+
* @returns {boolean} Always true.
|
|
8338
10610
|
*/
|
|
8339
|
-
|
|
8340
|
-
|
|
8341
|
-
action.isFlushable = true;
|
|
8342
|
-
cursorNode.apply(action);
|
|
8343
|
-
// Clamp to node length: empty paragraphs contain a ZWS in the DOM (startOffset=1)
|
|
8344
|
-
// but the DTO text node is empty, so `startOffset + char.length` would overflow.
|
|
8345
|
-
return {
|
|
8346
|
-
node: cursorNode,
|
|
8347
|
-
offset: Math.min(range.startOffset + char.length, cursorNode.getRangeEndOffset()),
|
|
8348
|
-
};
|
|
10611
|
+
get alwaysFilter() {
|
|
10612
|
+
return true;
|
|
8349
10613
|
}
|
|
8350
10614
|
/**
|
|
8351
|
-
*
|
|
8352
|
-
* the caret at the end of the inserted text.
|
|
10615
|
+
* Drop `customStyle.lineHeight` when it duplicates the semantic `lineHeight`.
|
|
8353
10616
|
*
|
|
8354
|
-
* @param {
|
|
8355
|
-
* @
|
|
8356
|
-
* @param {RTERange} range - The current selection range.
|
|
8357
|
-
* @returns {CaretTarget} Caret target.
|
|
10617
|
+
* @param {VegaRTEAnnotations} annotations - The annotations to filter.
|
|
10618
|
+
* @returns {VegaRTEAnnotations} The same annotations reference.
|
|
8358
10619
|
*/
|
|
8359
|
-
|
|
8360
|
-
|
|
8361
|
-
|
|
8362
|
-
|
|
8363
|
-
|
|
10620
|
+
filterAnnotations(annotations) {
|
|
10621
|
+
const blockAnnotations = annotations;
|
|
10622
|
+
const lineHeight = blockAnnotations.lineHeight;
|
|
10623
|
+
const customStyle = blockAnnotations.customStyle;
|
|
10624
|
+
if (lineHeight && customStyle && customStyle.lineHeight === lineHeight) {
|
|
10625
|
+
delete customStyle.lineHeight;
|
|
10626
|
+
}
|
|
10627
|
+
if (customStyle && Object.keys(customStyle).length === 0) {
|
|
10628
|
+
delete blockAnnotations.customStyle;
|
|
10629
|
+
}
|
|
10630
|
+
return annotations;
|
|
10631
|
+
}
|
|
10632
|
+
}
|
|
10633
|
+
|
|
10634
|
+
/**
|
|
10635
|
+
* Per-class set of allowed line-height values, updated as extension instances
|
|
10636
|
+
* are constructed with (possibly custom) options.
|
|
10637
|
+
*/
|
|
10638
|
+
const allowedLineHeightsByClass = new WeakMap();
|
|
10639
|
+
/**
|
|
10640
|
+
* Tracks which block classes have had their JSON `lineHeight` annotation
|
|
10641
|
+
* handler installed, so we register it exactly once per class.
|
|
10642
|
+
*/
|
|
10643
|
+
const handlerInstalledClasses = new WeakSet();
|
|
10644
|
+
/**
|
|
10645
|
+
* Move an out-of-range `lineHeight` annotation into `customStyle.lineHeight`.
|
|
10646
|
+
*
|
|
10647
|
+
* @param {RTEBlock} block - The block to inspect.
|
|
10648
|
+
* @param {ReadonlySet<string>} allowed - Allowed line-height values.
|
|
10649
|
+
*/
|
|
10650
|
+
function normalizeLineHeightStyle(block, allowed) {
|
|
10651
|
+
const annotation = block.annotationMap.get(RTELineHeightExtensionAnnotationTypeEnum.LINE_HEIGHT);
|
|
10652
|
+
if (!annotation || allowed.has(annotation.lineHeight)) {
|
|
10653
|
+
return;
|
|
10654
|
+
}
|
|
10655
|
+
const value = annotation.lineHeight;
|
|
10656
|
+
block.annotationMap.delete(RTELineHeightExtensionAnnotationTypeEnum.LINE_HEIGHT);
|
|
10657
|
+
const existing = block.annotationMap.get(CommonAnnotationTypeEnum.CUSTOM_STYLE);
|
|
10658
|
+
if (existing) {
|
|
10659
|
+
existing.customStyle = Object.assign(Object.assign({}, existing.customStyle), { lineHeight: value });
|
|
10660
|
+
}
|
|
10661
|
+
else {
|
|
10662
|
+
block.annotationMap.set(...CustomStyleAnnotation.from({ lineHeight: value }));
|
|
10663
|
+
}
|
|
10664
|
+
}
|
|
10665
|
+
/**
|
|
10666
|
+
* Install the JSON `lineHeight` annotation handler for a block class.
|
|
10667
|
+
*
|
|
10668
|
+
* @param {LineHeightBlockClass} BlockClass - Target block class.
|
|
10669
|
+
*/
|
|
10670
|
+
function attachLineHeightFromJson(BlockClass) {
|
|
10671
|
+
if (handlerInstalledClasses.has(BlockClass)) {
|
|
10672
|
+
return;
|
|
10673
|
+
}
|
|
10674
|
+
handlerInstalledClasses.add(BlockClass);
|
|
10675
|
+
VegaRTEExtension.attachAnnotationFromJson(BlockClass, 'lineHeight', (instance, value) => {
|
|
10676
|
+
if (typeof value !== 'string' || value === '') {
|
|
10677
|
+
return;
|
|
10678
|
+
}
|
|
10679
|
+
instance.annotationMap.set(...LineHeightAnnotation.from(value));
|
|
10680
|
+
const allowed = allowedLineHeightsByClass.get(BlockClass);
|
|
10681
|
+
if (allowed && allowed.size > 0) {
|
|
10682
|
+
normalizeLineHeightStyle(instance, allowed);
|
|
10683
|
+
}
|
|
10684
|
+
});
|
|
10685
|
+
}
|
|
10686
|
+
/**
|
|
10687
|
+
* Update the set of allowed line-height values for a block class.
|
|
10688
|
+
*
|
|
10689
|
+
* @param {LineHeightBlockClass} BlockClass - Target block class.
|
|
10690
|
+
* @param {VegaRTELineHeightOption[]} options - Allowed line-height options.
|
|
10691
|
+
*/
|
|
10692
|
+
function setAllowedLineHeights(BlockClass, options) {
|
|
10693
|
+
allowedLineHeightsByClass.set(BlockClass, new Set(options.map((option) => option.value)));
|
|
10694
|
+
}
|
|
10695
|
+
|
|
10696
|
+
/**
|
|
10697
|
+
* Rich Text Editor extension that adds configurable line-height support
|
|
10698
|
+
* (toolbar control, annotations, content transforms, and JSON parsing).
|
|
10699
|
+
*/
|
|
10700
|
+
class VegaRTELineHeightExtension extends VegaRTEExtension {
|
|
10701
|
+
constructor(options = VegaRTEDefaultLineHeightOptions) {
|
|
10702
|
+
super();
|
|
10703
|
+
this.options = options;
|
|
10704
|
+
this.toolbarButtonRenderer = null;
|
|
10705
|
+
this.registerAnnotationHandler(new LineHeightAnnotationHandler(this.options));
|
|
10706
|
+
[RTETextBlock, RTEListItemBlock].forEach((BlockClass) => {
|
|
10707
|
+
setAllowedLineHeights(BlockClass, this.options);
|
|
10708
|
+
});
|
|
8364
10709
|
}
|
|
8365
10710
|
/**
|
|
8366
|
-
*
|
|
8367
|
-
*
|
|
8368
|
-
* new sibling text node inserted before the break, or the break's text
|
|
8369
|
-
* replaced. The default ReplaceSelectedTextAction + getRangeEndOffset()
|
|
8370
|
-
* path returns 0 for '\n' text nodes and drops the caret in front of the
|
|
8371
|
-
* inserted character.
|
|
10711
|
+
* Register the per-host toolbar renderer so each editor on the page gets
|
|
10712
|
+
* its own isolated line-height renderer instance.
|
|
8372
10713
|
*
|
|
8373
|
-
* @
|
|
8374
|
-
* @param {
|
|
8375
|
-
* @param {RTERange} currentRange - The current selection range.
|
|
8376
|
-
* @returns {RTETextNode} The node the caret should land in.
|
|
10714
|
+
* @typedef HTMLVegaRichTextEditorElement - The type of the host rich text editor element.
|
|
10715
|
+
* @param {HTMLVegaRichTextEditorElement} host - The host element.
|
|
8377
10716
|
*/
|
|
8378
|
-
|
|
8379
|
-
|
|
8380
|
-
|
|
8381
|
-
const index = textBlock.children.indexOf(lineBreakNode);
|
|
8382
|
-
if (currentRange.startOffset === 0 && textBlock.children[index + 1]) {
|
|
8383
|
-
const newTextNode = lineBreakNode.cloneWithText(text);
|
|
8384
|
-
const insertAction = new InsertChildrenBeforeAction(lineBreakNode, newTextNode);
|
|
8385
|
-
insertAction.isFlushable = true;
|
|
8386
|
-
textBlock.apply(insertAction);
|
|
8387
|
-
return newTextNode;
|
|
8388
|
-
}
|
|
8389
|
-
const updateAction = new UpdateTextAction(text);
|
|
8390
|
-
updateAction.isFlushable = true;
|
|
8391
|
-
lineBreakNode.apply(updateAction);
|
|
8392
|
-
return lineBreakNode;
|
|
10717
|
+
prepareBeforeLoad(host) {
|
|
10718
|
+
this.toolbarButtonRenderer = new RTELineHeightToolbarButtonRenderer(this.options);
|
|
10719
|
+
this.registerToolbarButtonRenderer(VegaRTELineHeightExtension.TOOLBAR_BUTTON_KEY, this.toolbarButtonRenderer, host);
|
|
8393
10720
|
}
|
|
8394
10721
|
}
|
|
8395
10722
|
(() => {
|
|
8396
|
-
|
|
10723
|
+
[RTETextBlock, RTEListItemBlock].forEach((BlockClass) => {
|
|
10724
|
+
VegaRTEExtension.registerActionHandleStrategy(BlockClass.name, LineHeightAnnotationAction.name, new BlockUpdateLineHeightStrategy());
|
|
10725
|
+
RTEFilterStylesStrategyRegistry.register(BlockClass.name, new LineHeightFilterStylesStrategy());
|
|
10726
|
+
attachLineHeightFromJson(BlockClass);
|
|
10727
|
+
});
|
|
8397
10728
|
})();
|
|
10729
|
+
VegaRTELineHeightExtension.TOOLBAR_BUTTON_KEY = 'lineHeight';
|
|
10730
|
+
|
|
10731
|
+
const paste = {
|
|
10732
|
+
icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path fill="currentColor" d="M80 96v16c0 17.7 14.3 32 32 32h60.8c16.6-28.7 47.6-48 83.2-48h62c-7.1-27.6-32.2-48-62-48H215.4C211.6 20.9 188.2 0 160 0s-51.6 20.9-55.4 48H64C28.7 48 0 76.7 0 112V384c0 35.3 28.7 64 64 64h96V400H64c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H80zm64-40a16 16 0 1 1 32 0 16 16 0 1 1 -32 0zM256 464c-8.8 0-16-7.2-16-16V192c0-8.8 7.2-16 16-16H384v48c0 17.7 14.3 32 32 32h48V448c0 8.8-7.2 16-16 16H256zm192 48c35.3 0 64-28.7 64-64V227.9c0-12.7-5.1-24.9-14.1-33.9l-51.9-51.9c-9-9-21.2-14.1-33.9-14.1H256c-35.3 0-64 28.7-64 64V448c0 35.3 28.7 64 64 64H448z"/></svg>`,
|
|
10733
|
+
};
|
|
8398
10734
|
|
|
8399
10735
|
/**
|
|
8400
|
-
*
|
|
8401
|
-
* Adds a toolbar button that opens a popover with categorized Unicode characters
|
|
8402
|
-
* for insertion into the editor content as plain text.
|
|
10736
|
+
* Renders and manages the paste format chooser UI.
|
|
8403
10737
|
*/
|
|
8404
|
-
class
|
|
8405
|
-
constructor(
|
|
8406
|
-
|
|
8407
|
-
this.
|
|
10738
|
+
class RTEPasteChooserUI {
|
|
10739
|
+
constructor(hostRoot, callbacks) {
|
|
10740
|
+
this.hostRoot = hostRoot;
|
|
10741
|
+
this.callbacks = callbacks;
|
|
10742
|
+
this.chooserEl = null;
|
|
10743
|
+
this.dropdownEl = null;
|
|
10744
|
+
this.triggerButtonEl = null;
|
|
10745
|
+
}
|
|
10746
|
+
/**
|
|
10747
|
+
* Opens the paste-mode chooser at the given position.
|
|
10748
|
+
*
|
|
10749
|
+
* @param {PasteChooserPosition} position Viewport position for the chooser.
|
|
10750
|
+
*/
|
|
10751
|
+
open(position) {
|
|
10752
|
+
this.destroy();
|
|
10753
|
+
const doc = this.hostRoot.ownerDocument;
|
|
10754
|
+
const chooser = doc.createElement('div');
|
|
10755
|
+
chooser.setAttribute(RTEPasteChooserUI.CHOOSER_ATTR, 'true');
|
|
10756
|
+
chooser.style.position = 'fixed';
|
|
10757
|
+
chooser.style.zIndex = '1000';
|
|
10758
|
+
chooser.style.left = `${position.left}px`;
|
|
10759
|
+
chooser.style.top = `${position.top}px`;
|
|
10760
|
+
const dropdownEl = this.createPasteChooserDropdown(doc, position.placement);
|
|
10761
|
+
dropdownEl.placement = position.placement;
|
|
10762
|
+
chooser.append(dropdownEl);
|
|
10763
|
+
this.hostRoot.appendChild(chooser);
|
|
10764
|
+
this.chooserEl = chooser;
|
|
10765
|
+
this.dropdownEl = dropdownEl;
|
|
10766
|
+
dropdownEl.addEventListener('vegaShow', () => {
|
|
10767
|
+
if (this.triggerButtonEl) {
|
|
10768
|
+
this.triggerButtonEl.selected = true;
|
|
10769
|
+
}
|
|
10770
|
+
});
|
|
10771
|
+
dropdownEl.addEventListener('vegaHide', () => {
|
|
10772
|
+
if (this.triggerButtonEl) {
|
|
10773
|
+
this.triggerButtonEl.selected = false;
|
|
10774
|
+
}
|
|
10775
|
+
});
|
|
8408
10776
|
}
|
|
8409
|
-
/**
|
|
8410
|
-
|
|
8411
|
-
|
|
10777
|
+
/**
|
|
10778
|
+
* Removes the chooser from the DOM.
|
|
10779
|
+
*/
|
|
10780
|
+
destroy() {
|
|
10781
|
+
var _a;
|
|
10782
|
+
if (this.dropdownEl && typeof this.dropdownEl.hide === 'function') {
|
|
10783
|
+
void this.dropdownEl.hide();
|
|
10784
|
+
}
|
|
10785
|
+
if ((_a = this.chooserEl) === null || _a === void 0 ? void 0 : _a.parentNode) {
|
|
10786
|
+
this.chooserEl.parentNode.removeChild(this.chooserEl);
|
|
10787
|
+
}
|
|
10788
|
+
if (this.triggerButtonEl) {
|
|
10789
|
+
this.triggerButtonEl.selected = false;
|
|
10790
|
+
}
|
|
10791
|
+
this.dropdownEl = null;
|
|
10792
|
+
this.chooserEl = null;
|
|
10793
|
+
this.triggerButtonEl = null;
|
|
8412
10794
|
}
|
|
8413
10795
|
/**
|
|
8414
|
-
*
|
|
10796
|
+
* Determines whether the given event path targets the chooser.
|
|
8415
10797
|
*
|
|
8416
|
-
* @param {
|
|
8417
|
-
* @returns {
|
|
10798
|
+
* @param {EventTarget[]} eventPath Composed path of an event.
|
|
10799
|
+
* @returns {boolean} `true` when the path includes the chooser.
|
|
8418
10800
|
*/
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
|
|
8422
|
-
? VegaRTESpecialCharactersEssentials
|
|
8423
|
-
: [];
|
|
8424
|
-
if (!(config === null || config === void 0 ? void 0 : config.categories)) {
|
|
8425
|
-
return defaultCategories;
|
|
10801
|
+
includesEventPath(eventPath) {
|
|
10802
|
+
if (!this.chooserEl) {
|
|
10803
|
+
return false;
|
|
8426
10804
|
}
|
|
8427
|
-
|
|
10805
|
+
const isDropdownContentClick = eventPath.some((target) => {
|
|
10806
|
+
return 'tagName' in target && target.tagName === 'VEGA-DROPDOWN-CONTENT-BOX';
|
|
10807
|
+
});
|
|
10808
|
+
return eventPath.includes(this.chooserEl) || isDropdownContentClick;
|
|
10809
|
+
}
|
|
10810
|
+
/**
|
|
10811
|
+
* Builds the dropdown element with the rich text paste option.
|
|
10812
|
+
*
|
|
10813
|
+
* @param {Document} doc Document used to create elements.
|
|
10814
|
+
* @param {['bottom', 'top'] | ['top', 'bottom']} placement Preferred placement order.
|
|
10815
|
+
* @returns {HTMLElement} Configured dropdown element.
|
|
10816
|
+
*/
|
|
10817
|
+
createPasteChooserDropdown(doc, placement) {
|
|
10818
|
+
const dropdownEl = doc.createElement('vega-dropdown');
|
|
10819
|
+
dropdownEl.trigger = 'click';
|
|
10820
|
+
dropdownEl.selectType = 'single';
|
|
10821
|
+
dropdownEl.selectedSourceKey = 'rich';
|
|
10822
|
+
dropdownEl.maxWidth = 220;
|
|
10823
|
+
dropdownEl.alignment = 'start';
|
|
10824
|
+
dropdownEl.itemDisplayRule = 'ellipsis';
|
|
10825
|
+
const triggerButton = doc.createElement('vega-rich-text-editor-toolbar-button');
|
|
10826
|
+
const arrowIcon = placement[0] === 'top' ? 'chevron-up' : 'chevron-down';
|
|
10827
|
+
triggerButton.setAttribute(RTEPasteChooserUI.CHOOSER_TRIGGER_ATTR, 'true');
|
|
10828
|
+
triggerButton.setAttribute('aria-label', VegaInternalTranslation.t('Paste options'));
|
|
10829
|
+
triggerButton.setAttribute('arrow-icon', arrowIcon);
|
|
10830
|
+
triggerButton.icon = 'rte-paste';
|
|
10831
|
+
triggerButton.showArrowIcon = true;
|
|
10832
|
+
triggerButton.selected = false;
|
|
10833
|
+
this.triggerButtonEl = triggerButton;
|
|
10834
|
+
const tooltipEl = doc.createElement('vega-tooltip');
|
|
10835
|
+
tooltipEl.text = VegaInternalTranslation.t('Paste options');
|
|
10836
|
+
tooltipEl.placement = ['bottom', 'top'];
|
|
10837
|
+
tooltipEl.append(triggerButton);
|
|
10838
|
+
dropdownEl.append(tooltipEl);
|
|
10839
|
+
const itemsContainer = doc.createElement('div');
|
|
10840
|
+
itemsContainer.setAttribute('slot', 'items');
|
|
10841
|
+
itemsContainer.append(this.createDropdownItem(doc, 'rich', VegaInternalTranslation.t('Use rich text'), RTEPasteChooserUI.CHOOSER_RICH_ATTR));
|
|
10842
|
+
dropdownEl.append(itemsContainer);
|
|
10843
|
+
dropdownEl.addEventListener('vegaDropdownClick', (event) => {
|
|
10844
|
+
const mode = event.detail;
|
|
10845
|
+
if (mode === 'rich') {
|
|
10846
|
+
this.callbacks.onModeSelect(mode);
|
|
10847
|
+
}
|
|
10848
|
+
});
|
|
10849
|
+
return dropdownEl;
|
|
10850
|
+
}
|
|
10851
|
+
/**
|
|
10852
|
+
* Creates a single dropdown item element for a paste mode.
|
|
10853
|
+
*
|
|
10854
|
+
* @param {Document} doc Document used to create the element.
|
|
10855
|
+
* @param {PasteMode} key Paste mode key for the item.
|
|
10856
|
+
* @param {string} label Display label for the item.
|
|
10857
|
+
* @param {string} dataAttrName Data attribute marker for the item.
|
|
10858
|
+
* @returns {HTMLVegaDropdownItemElement} Configured dropdown item element.
|
|
10859
|
+
*/
|
|
10860
|
+
createDropdownItem(doc, key, label, dataAttrName) {
|
|
10861
|
+
const itemEl = doc.createElement('vega-dropdown-item');
|
|
10862
|
+
itemEl.itemKey = key;
|
|
10863
|
+
itemEl.label = label;
|
|
10864
|
+
itemEl.setAttribute(dataAttrName, 'true');
|
|
10865
|
+
return itemEl;
|
|
8428
10866
|
}
|
|
8429
10867
|
}
|
|
10868
|
+
(() => {
|
|
10869
|
+
VegaInternalIconManager.register({
|
|
10870
|
+
'rte-paste': paste,
|
|
10871
|
+
});
|
|
10872
|
+
})();
|
|
10873
|
+
RTEPasteChooserUI.CHOOSER_ATTR = 'data-rte-paste-chooser';
|
|
10874
|
+
RTEPasteChooserUI.CHOOSER_RICH_ATTR = 'data-rte-paste-type-rich';
|
|
10875
|
+
RTEPasteChooserUI.CHOOSER_TRIGGER_ATTR = 'data-rte-paste-trigger';
|
|
8430
10876
|
|
|
8431
10877
|
/**
|
|
8432
|
-
*
|
|
8433
|
-
*
|
|
10878
|
+
* Custom DataTransfer MIME type used to tag a synthetic paste with the chosen
|
|
10879
|
+
* paste mode (e.g. 'rich' or 'plain'), so downstream paste handling can decide
|
|
10880
|
+
* whether to preserve the original inline styles.
|
|
8434
10881
|
*/
|
|
8435
|
-
const
|
|
8436
|
-
{ title: '0.5', value: '0.5' },
|
|
8437
|
-
{ title: '1', value: '1' },
|
|
8438
|
-
{ title: '1.5', value: '1.5' },
|
|
8439
|
-
{ title: '2', value: '2' },
|
|
8440
|
-
{ title: '2.5', value: '2.5' },
|
|
8441
|
-
];
|
|
10882
|
+
const RTE_PASTE_MODE_DATA_TYPE = 'application/x-vega-rte-paste-mode';
|
|
8442
10883
|
|
|
8443
|
-
var RTELineHeightExtensionAnnotationTypeEnum;
|
|
8444
|
-
(function (RTELineHeightExtensionAnnotationTypeEnum) {
|
|
8445
|
-
RTELineHeightExtensionAnnotationTypeEnum["LINE_HEIGHT"] = "LINE_HEIGHT";
|
|
8446
|
-
})(RTELineHeightExtensionAnnotationTypeEnum || (RTELineHeightExtensionAnnotationTypeEnum = {}));
|
|
8447
10884
|
/**
|
|
8448
|
-
*
|
|
10885
|
+
* Vega Rich Text Editor paste extension.
|
|
10886
|
+
* Intercepts keyboard paste and allows users to choose paste mode.
|
|
8449
10887
|
*/
|
|
8450
|
-
class
|
|
8451
|
-
constructor(
|
|
8452
|
-
super();
|
|
8453
|
-
this.
|
|
8454
|
-
this.
|
|
10888
|
+
class VegaRTEPasteExtension extends VegaRTEExtension {
|
|
10889
|
+
constructor() {
|
|
10890
|
+
super(...arguments);
|
|
10891
|
+
this.editableEl = null;
|
|
10892
|
+
this.contentHostEl = null;
|
|
10893
|
+
this.chooserUI = null;
|
|
10894
|
+
this.pendingPastePayload = null;
|
|
10895
|
+
this.listenerDocument = null;
|
|
10896
|
+
this.listenerWindow = null;
|
|
10897
|
+
/**
|
|
10898
|
+
* Handles a native paste event by capturing the clipboard payload and
|
|
10899
|
+
* rendering the paste-mode chooser.
|
|
10900
|
+
*
|
|
10901
|
+
* @param {ClipboardEvent} event Native clipboard paste event.
|
|
10902
|
+
*/
|
|
10903
|
+
this.handlePasteEvent = (event) => {
|
|
10904
|
+
var _a, _b;
|
|
10905
|
+
const clipboardData = event.clipboardData;
|
|
10906
|
+
if (!clipboardData) {
|
|
10907
|
+
return;
|
|
10908
|
+
}
|
|
10909
|
+
// HTML is sanitized via DOMParser and ElementToDtoStrategyProcessor pipeline
|
|
10910
|
+
const html = clipboardData.getData('text/html') || '';
|
|
10911
|
+
const text = clipboardData.getData('text/plain') || '';
|
|
10912
|
+
if (!html && !text) {
|
|
10913
|
+
// Empty clipboard paste should not mutate editor content.
|
|
10914
|
+
event.preventDefault();
|
|
10915
|
+
event.stopPropagation();
|
|
10916
|
+
return;
|
|
10917
|
+
}
|
|
10918
|
+
event.preventDefault();
|
|
10919
|
+
event.stopPropagation();
|
|
10920
|
+
const range = this.cloneCurrentSelectionRange();
|
|
10921
|
+
const selectedNodes = ((_b = (_a = this.extensionContext) === null || _a === void 0 ? void 0 : _a.getSelectedNodes) === null || _b === void 0 ? void 0 : _b.call(_a)) || [];
|
|
10922
|
+
this.pendingPastePayload = { html, text, range, selectedNodes: [...selectedNodes] };
|
|
10923
|
+
this.renderPasteChooser(event);
|
|
10924
|
+
};
|
|
10925
|
+
/**
|
|
10926
|
+
* Dismisses the chooser when a pointer interaction occurs outside of it.
|
|
10927
|
+
*
|
|
10928
|
+
* @param {Event} event Pointer event captured on the document.
|
|
10929
|
+
*/
|
|
10930
|
+
this.handleOutsidePointerDown = (event) => {
|
|
10931
|
+
if (!this.chooserUI) {
|
|
10932
|
+
return;
|
|
10933
|
+
}
|
|
10934
|
+
const eventPath = typeof event.composedPath === 'function' ? event.composedPath() : [];
|
|
10935
|
+
if (this.chooserUI.includesEventPath(eventPath)) {
|
|
10936
|
+
return;
|
|
10937
|
+
}
|
|
10938
|
+
this.clearPasteChooser();
|
|
10939
|
+
};
|
|
10940
|
+
/**
|
|
10941
|
+
* Dismisses the chooser when the Escape key is pressed.
|
|
10942
|
+
*
|
|
10943
|
+
* @param {KeyboardEvent} event Keyboard event captured on the document.
|
|
10944
|
+
*/
|
|
10945
|
+
this.handleOutsideKeyDown = (event) => {
|
|
10946
|
+
if (event.key === 'Escape') {
|
|
10947
|
+
this.clearPasteChooser();
|
|
10948
|
+
}
|
|
10949
|
+
};
|
|
10950
|
+
/**
|
|
10951
|
+
* Dismisses the chooser when any relevant scroll container scrolls.
|
|
10952
|
+
*/
|
|
10953
|
+
this.handleOutsideScroll = () => {
|
|
10954
|
+
this.clearPasteChooser();
|
|
10955
|
+
};
|
|
10956
|
+
}
|
|
10957
|
+
/** @inheritDoc */
|
|
10958
|
+
initialExtensionAfterLoad(context) {
|
|
10959
|
+
super.initialExtensionAfterLoad(context);
|
|
10960
|
+
this.hostEl = context.host;
|
|
10961
|
+
this.attachPasteListener();
|
|
10962
|
+
}
|
|
10963
|
+
/** @inheritDoc */
|
|
10964
|
+
destroyExtension() {
|
|
10965
|
+
this.detachPasteListener();
|
|
10966
|
+
this.clearPasteChooser();
|
|
8455
10967
|
}
|
|
8456
10968
|
/**
|
|
8457
|
-
*
|
|
8458
|
-
*
|
|
8459
|
-
* @param {string} lineHeight - Line height value.
|
|
8460
|
-
* @returns {[RTELineHeightExtensionAnnotationTypeEnum.LINE_HEIGHT, LineHeightAnnotation]} Map item for the annotation.
|
|
10969
|
+
* Attaches the paste listener to the editable element.
|
|
8461
10970
|
*/
|
|
8462
|
-
|
|
8463
|
-
|
|
10971
|
+
attachPasteListener() {
|
|
10972
|
+
const { editableEl, contentHostEl, } = this.getEditorElements();
|
|
10973
|
+
this.editableEl = editableEl;
|
|
10974
|
+
this.contentHostEl = contentHostEl;
|
|
10975
|
+
if (this.editableEl) {
|
|
10976
|
+
this.editableEl.addEventListener('paste', this.handlePasteEvent);
|
|
10977
|
+
}
|
|
8464
10978
|
}
|
|
8465
10979
|
/**
|
|
8466
|
-
*
|
|
10980
|
+
* Detaches the paste listener from the editable element.
|
|
10981
|
+
*/
|
|
10982
|
+
detachPasteListener() {
|
|
10983
|
+
if (this.editableEl) {
|
|
10984
|
+
this.editableEl.removeEventListener('paste', this.handlePasteEvent);
|
|
10985
|
+
}
|
|
10986
|
+
this.editableEl = null;
|
|
10987
|
+
this.contentHostEl = null;
|
|
10988
|
+
}
|
|
10989
|
+
/**
|
|
10990
|
+
* Resolves the content host and editable element from the host shadow tree.
|
|
8467
10991
|
*
|
|
8468
|
-
* @
|
|
8469
|
-
* @returns {AnnotationStyle} The default standalone styles.
|
|
10992
|
+
* @returns {EditorElements} Editor elements.
|
|
8470
10993
|
*/
|
|
8471
|
-
|
|
8472
|
-
|
|
10994
|
+
getEditorElements() {
|
|
10995
|
+
var _a, _b, _c, _d, _e;
|
|
10996
|
+
const contentHostEl = (_c = (_b = (_a = this.hostEl) === null || _a === void 0 ? void 0 : _a.shadowRoot) === null || _b === void 0 ? void 0 : _b.querySelector('vega-rich-text-content')) !== null && _c !== void 0 ? _c : null;
|
|
10997
|
+
const editableEl = (_e = (_d = contentHostEl === null || contentHostEl === void 0 ? void 0 : contentHostEl.shadowRoot) === null || _d === void 0 ? void 0 : _d.querySelector('.rich-text-content')) !== null && _e !== void 0 ? _e : null;
|
|
10998
|
+
return { editableEl, contentHostEl };
|
|
8473
10999
|
}
|
|
8474
11000
|
/**
|
|
8475
|
-
*
|
|
11001
|
+
* Resolves the editable content element from the host shadow tree.
|
|
8476
11002
|
*
|
|
8477
|
-
* @returns {
|
|
11003
|
+
* @returns {HTMLDivElement | null} Editable element or `null` when unavailable.
|
|
8478
11004
|
*/
|
|
8479
|
-
|
|
8480
|
-
|
|
8481
|
-
|
|
8482
|
-
};
|
|
11005
|
+
getEditableElement() {
|
|
11006
|
+
const { editableEl } = this.getEditorElements();
|
|
11007
|
+
return editableEl;
|
|
8483
11008
|
}
|
|
8484
11009
|
/**
|
|
8485
|
-
*
|
|
11010
|
+
* Clones the current selection range so it can be restored after the user
|
|
11011
|
+
* chooses a paste mode.
|
|
11012
|
+
*
|
|
11013
|
+
* @returns {Range | null} Cloned range or `null` when there is no selection.
|
|
8486
11014
|
*/
|
|
8487
|
-
|
|
11015
|
+
cloneCurrentSelectionRange() {
|
|
11016
|
+
var _a;
|
|
11017
|
+
const selection = (_a = this.extensionContext) === null || _a === void 0 ? void 0 : _a.getSelection();
|
|
11018
|
+
if (selection && selection.rangeCount > 0) {
|
|
11019
|
+
return selection.getRangeAt(0).cloneRange();
|
|
11020
|
+
}
|
|
8488
11021
|
return null;
|
|
8489
11022
|
}
|
|
8490
11023
|
/**
|
|
8491
|
-
*
|
|
11024
|
+
* Renders the paste-mode chooser and wires up dismissal listeners.
|
|
11025
|
+
*
|
|
11026
|
+
* @param {ClipboardEvent} pasteEvent Originating paste event used for positioning.
|
|
8492
11027
|
*/
|
|
8493
|
-
|
|
8494
|
-
|
|
11028
|
+
renderPasteChooser(pasteEvent) {
|
|
11029
|
+
var _a, _b;
|
|
11030
|
+
const hostRoot = (_b = (_a = this.hostEl) === null || _a === void 0 ? void 0 : _a.shadowRoot) !== null && _b !== void 0 ? _b : null;
|
|
11031
|
+
if (!hostRoot) {
|
|
11032
|
+
return;
|
|
11033
|
+
}
|
|
11034
|
+
this.clearPasteChooser(false);
|
|
11035
|
+
const position = this.computeChooserPosition(pasteEvent);
|
|
11036
|
+
this.chooserUI = new RTEPasteChooserUI(hostRoot, {
|
|
11037
|
+
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
11038
|
+
onModeSelect: (mode) => {
|
|
11039
|
+
this.applyPasteMode(mode);
|
|
11040
|
+
},
|
|
11041
|
+
});
|
|
11042
|
+
this.chooserUI.open(position);
|
|
11043
|
+
this.listenerDocument = this.hostEl.ownerDocument;
|
|
11044
|
+
this.listenerWindow = this.listenerDocument.defaultView;
|
|
11045
|
+
this.listenerDocument.addEventListener('mousedown', this.handleOutsidePointerDown, true);
|
|
11046
|
+
this.listenerDocument.addEventListener('keydown', this.handleOutsideKeyDown, true);
|
|
11047
|
+
this.listenerDocument.addEventListener('scroll', this.handleOutsideScroll, true);
|
|
11048
|
+
if (this.listenerWindow) {
|
|
11049
|
+
this.listenerWindow.addEventListener('scroll', this.handleOutsideScroll, true);
|
|
11050
|
+
}
|
|
11051
|
+
if (this.contentHostEl) {
|
|
11052
|
+
this.contentHostEl.addEventListener('scroll', this.handleOutsideScroll);
|
|
11053
|
+
}
|
|
8495
11054
|
}
|
|
8496
11055
|
/**
|
|
8497
|
-
*
|
|
11056
|
+
* Computes the on-screen position for the chooser based on pointer or caret.
|
|
11057
|
+
*
|
|
11058
|
+
* @param {ClipboardEvent} event Originating paste event.
|
|
11059
|
+
* @returns {ChooserPosition} Position in viewport coordinates.
|
|
8498
11060
|
*/
|
|
8499
|
-
|
|
8500
|
-
|
|
8501
|
-
|
|
8502
|
-
|
|
8503
|
-
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
|
|
8508
|
-
|
|
8509
|
-
|
|
8510
|
-
|
|
8511
|
-
|
|
11061
|
+
computeChooserPosition(event) {
|
|
11062
|
+
var _a, _b, _c;
|
|
11063
|
+
const hostRect = this.hostEl.getBoundingClientRect();
|
|
11064
|
+
const viewportHeight = (_b = (_a = this.hostEl.ownerDocument.defaultView) === null || _a === void 0 ? void 0 : _a.innerHeight) !== null && _b !== void 0 ? _b : window.innerHeight;
|
|
11065
|
+
const pointerEvent = event;
|
|
11066
|
+
const hasPointer = typeof pointerEvent.clientX === 'number' &&
|
|
11067
|
+
pointerEvent.clientX > 0 &&
|
|
11068
|
+
typeof pointerEvent.clientY === 'number' &&
|
|
11069
|
+
pointerEvent.clientY > 0;
|
|
11070
|
+
// For keyboard-initiated paste (Command/Control+V) clientX/Y are 0, so anchor
|
|
11071
|
+
// the chooser to the current caret instead of a fixed host-relative spot.
|
|
11072
|
+
const caretRect = hasPointer ? null : this.getCaretRect();
|
|
11073
|
+
const CHOOSER_GAP_PX = 8;
|
|
11074
|
+
const CHOOSER_MIN_MARGIN_PX = 12;
|
|
11075
|
+
const CHOOSER_WIDTH_PX = 220;
|
|
11076
|
+
const CHOOSER_FALLBACK_OFFSET_PX = 40;
|
|
11077
|
+
const CHOOSER_MIN_VISIBLE_BELOW_SPACE_PX = 88;
|
|
11078
|
+
const baseLeft = hasPointer
|
|
11079
|
+
? pointerEvent.clientX
|
|
11080
|
+
: (_c = caretRect === null || caretRect === void 0 ? void 0 : caretRect.left) !== null && _c !== void 0 ? _c : hostRect.left + CHOOSER_MIN_MARGIN_PX;
|
|
11081
|
+
const baseTop = hasPointer
|
|
11082
|
+
? pointerEvent.clientY
|
|
11083
|
+
: caretRect
|
|
11084
|
+
? caretRect.bottom
|
|
11085
|
+
: hostRect.top + CHOOSER_FALLBACK_OFFSET_PX;
|
|
11086
|
+
const maxLeft = Math.max(hostRect.left + CHOOSER_MIN_MARGIN_PX, hostRect.right - CHOOSER_WIDTH_PX);
|
|
11087
|
+
const left = Math.min(Math.max(baseLeft, hostRect.left + CHOOSER_MIN_MARGIN_PX), maxLeft);
|
|
11088
|
+
const top = Math.max(baseTop + CHOOSER_GAP_PX, hostRect.top + CHOOSER_MIN_MARGIN_PX);
|
|
11089
|
+
const availableBelow = viewportHeight - top - CHOOSER_MIN_MARGIN_PX;
|
|
11090
|
+
const availableAbove = top - CHOOSER_MIN_MARGIN_PX;
|
|
11091
|
+
const placement = availableBelow >= CHOOSER_MIN_VISIBLE_BELOW_SPACE_PX || availableBelow >= availableAbove
|
|
11092
|
+
? ['bottom', 'top']
|
|
11093
|
+
: ['top', 'bottom'];
|
|
11094
|
+
return { left, top, placement };
|
|
11095
|
+
}
|
|
11096
|
+
/**
|
|
11097
|
+
* Resolves the bounding rect of the pending caret position.
|
|
11098
|
+
*
|
|
11099
|
+
* @returns {DOMRect | null} Caret rect or `null` when it cannot be determined.
|
|
11100
|
+
*/
|
|
11101
|
+
getCaretRect() {
|
|
11102
|
+
var _a, _b;
|
|
11103
|
+
const range = (_b = (_a = this.pendingPastePayload) === null || _a === void 0 ? void 0 : _a.range) !== null && _b !== void 0 ? _b : this.cloneCurrentSelectionRange();
|
|
11104
|
+
if (!range) {
|
|
11105
|
+
return null;
|
|
11106
|
+
}
|
|
11107
|
+
const rect = range.getBoundingClientRect();
|
|
11108
|
+
if (this.isUnavailableRect(rect)) {
|
|
11109
|
+
// eslint-disable-next-line spellcheck/spell-checker
|
|
11110
|
+
const domRectList = range.getClientRects();
|
|
11111
|
+
if (domRectList.length > 0) {
|
|
11112
|
+
return domRectList[0];
|
|
11113
|
+
}
|
|
11114
|
+
return this.getRangeContainerRect(range);
|
|
11115
|
+
}
|
|
11116
|
+
return rect;
|
|
8512
11117
|
}
|
|
8513
11118
|
/**
|
|
8514
|
-
*
|
|
11119
|
+
* Returns whether the browser could not resolve a meaningful caret rectangle.
|
|
8515
11120
|
*
|
|
8516
|
-
* @
|
|
11121
|
+
* @param {DOMRect} rect Candidate caret rect.
|
|
11122
|
+
* @returns {boolean} Whether the rect is unusable.
|
|
8517
11123
|
*/
|
|
8518
|
-
|
|
8519
|
-
return
|
|
11124
|
+
isUnavailableRect(rect) {
|
|
11125
|
+
return rect.width === 0 && rect.height === 0 && rect.left === 0 && rect.top === 0;
|
|
8520
11126
|
}
|
|
8521
|
-
}
|
|
8522
|
-
|
|
8523
|
-
/**
|
|
8524
|
-
* Update the block line height strategy.
|
|
8525
|
-
*
|
|
8526
|
-
* Registered for RTETextBlock and RTEListItemBlock, so the
|
|
8527
|
-
* target type is widened to the common RTEBlock base.
|
|
8528
|
-
*/
|
|
8529
|
-
class BlockUpdateLineHeightStrategy extends ActionHandleStrategy {
|
|
8530
11127
|
/**
|
|
8531
|
-
*
|
|
11128
|
+
* Falls back to the range container or neighbor child node bounds.
|
|
8532
11129
|
*
|
|
8533
|
-
* @param {
|
|
8534
|
-
* @
|
|
11130
|
+
* @param {Range} range Caret/selection range.
|
|
11131
|
+
* @returns {DOMRect | null} Best-effort caret anchor rectangle.
|
|
8535
11132
|
*/
|
|
8536
|
-
|
|
8537
|
-
|
|
11133
|
+
getRangeContainerRect(range) {
|
|
11134
|
+
var _a, _b, _c, _d, _e, _f;
|
|
11135
|
+
const containerNode = (_a = range.startContainer) !== null && _a !== void 0 ? _a : null;
|
|
11136
|
+
if (!containerNode) {
|
|
11137
|
+
return null;
|
|
11138
|
+
}
|
|
11139
|
+
if (containerNode.nodeType === Node.ELEMENT_NODE) {
|
|
11140
|
+
const containerElement = containerNode;
|
|
11141
|
+
const previousNode = (_b = containerElement.childNodes[range.startOffset - 1]) !== null && _b !== void 0 ? _b : null;
|
|
11142
|
+
const currentNode = (_c = containerElement.childNodes[range.startOffset]) !== null && _c !== void 0 ? _c : null;
|
|
11143
|
+
return ((_e = (_d = this.getNodeRect(previousNode)) !== null && _d !== void 0 ? _d : this.getNodeRect(currentNode)) !== null && _e !== void 0 ? _e : this.getNodeRect(containerElement));
|
|
11144
|
+
}
|
|
11145
|
+
return (_f = this.getNodeRect(containerNode)) !== null && _f !== void 0 ? _f : this.getNodeRect(containerNode.parentNode);
|
|
8538
11146
|
}
|
|
8539
|
-
|
|
8540
|
-
|
|
8541
|
-
|
|
8542
|
-
|
|
8543
|
-
}
|
|
8544
|
-
|
|
8545
|
-
|
|
8546
|
-
|
|
8547
|
-
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
|
|
11147
|
+
/**
|
|
11148
|
+
* Returns an element-backed rectangle from a DOM node.
|
|
11149
|
+
*
|
|
11150
|
+
* @param {Node | null} node Candidate node.
|
|
11151
|
+
* @returns {DOMRect | null} Node rectangle when resolvable.
|
|
11152
|
+
*/
|
|
11153
|
+
getNodeRect(node) {
|
|
11154
|
+
var _a, _b;
|
|
11155
|
+
if (!node) {
|
|
11156
|
+
return null;
|
|
11157
|
+
}
|
|
11158
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
11159
|
+
return (_b = (_a = node.parentElement) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect()) !== null && _b !== void 0 ? _b : null;
|
|
11160
|
+
}
|
|
11161
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
11162
|
+
const rect = node.getBoundingClientRect();
|
|
11163
|
+
return this.isUnavailableRect(rect) ? null : rect;
|
|
11164
|
+
}
|
|
11165
|
+
return null;
|
|
8552
11166
|
}
|
|
8553
11167
|
/**
|
|
8554
|
-
*
|
|
11168
|
+
* Applies the chosen paste mode by dispatching a synthetic paste input event.
|
|
11169
|
+
*
|
|
11170
|
+
* @param {PasteMode} mode Selected paste mode.
|
|
8555
11171
|
*/
|
|
8556
|
-
|
|
8557
|
-
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
|
|
8561
|
-
|
|
8562
|
-
|
|
8563
|
-
|
|
8564
|
-
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
|
|
8568
|
-
|
|
8569
|
-
|
|
8570
|
-
|
|
8571
|
-
|
|
8572
|
-
|
|
8573
|
-
this.renderDropdownListTemplate(createElement, dropdownItems, editorContext),
|
|
8574
|
-
]);
|
|
11172
|
+
applyPasteMode(mode) {
|
|
11173
|
+
var _a;
|
|
11174
|
+
if (!this.pendingPastePayload) {
|
|
11175
|
+
this.clearPasteChooser();
|
|
11176
|
+
return;
|
|
11177
|
+
}
|
|
11178
|
+
const editableEl = (_a = this.editableEl) !== null && _a !== void 0 ? _a : this.getEditableElement();
|
|
11179
|
+
if (!editableEl) {
|
|
11180
|
+
this.clearPasteChooser();
|
|
11181
|
+
return;
|
|
11182
|
+
}
|
|
11183
|
+
editableEl.focus();
|
|
11184
|
+
this.restoreSelectionRange(this.pendingPastePayload.range);
|
|
11185
|
+
this.restoreSelectionControllerState(this.pendingPastePayload);
|
|
11186
|
+
const dataTransfer = this.createDataTransfer(this.pendingPastePayload.html, this.pendingPastePayload.text, mode);
|
|
11187
|
+
this.dispatchBeforeInputPaste(editableEl, dataTransfer);
|
|
11188
|
+
this.clearPasteChooser();
|
|
8575
11189
|
}
|
|
8576
11190
|
/**
|
|
8577
|
-
*
|
|
11191
|
+
* Restores SelectionController state from the captured selection so synthetic
|
|
11192
|
+
* beforeinput paste can replace the selected content instead of appending.
|
|
11193
|
+
*
|
|
11194
|
+
* @param {PendingPastePayload} payload Pending paste payload.
|
|
8578
11195
|
*/
|
|
8579
|
-
|
|
8580
|
-
|
|
11196
|
+
restoreSelectionControllerState(payload) {
|
|
11197
|
+
if (!payload.range || !this.extensionContext) {
|
|
11198
|
+
return;
|
|
11199
|
+
}
|
|
11200
|
+
const rangeOptions = {
|
|
11201
|
+
startNode: payload.range.startContainer,
|
|
11202
|
+
endNode: payload.range.endContainer,
|
|
11203
|
+
startOffset: payload.range.startOffset,
|
|
11204
|
+
endOffset: payload.range.endOffset,
|
|
11205
|
+
};
|
|
11206
|
+
this.extensionContext.setSelectedNodes(payload.selectedNodes || [], rangeOptions);
|
|
8581
11207
|
}
|
|
8582
11208
|
/**
|
|
8583
|
-
*
|
|
11209
|
+
* Restores a previously cloned selection range.
|
|
11210
|
+
*
|
|
11211
|
+
* @param {Range | null} range Range to restore.
|
|
8584
11212
|
*/
|
|
8585
|
-
|
|
11213
|
+
restoreSelectionRange(range) {
|
|
8586
11214
|
var _a;
|
|
8587
|
-
if (
|
|
8588
|
-
return
|
|
11215
|
+
if (!range) {
|
|
11216
|
+
return;
|
|
8589
11217
|
}
|
|
8590
|
-
const
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
return ((_a = node.getAnnotationByType(NodeAnnotationTypeEnum.CODE)) === null || _a === void 0 ? void 0 : _a.code) ||
|
|
8595
|
-
node.parent instanceof RTECodeBlock;
|
|
8596
|
-
})) {
|
|
8597
|
-
return true;
|
|
11218
|
+
const selection = (_a = this.extensionContext) === null || _a === void 0 ? void 0 : _a.getSelection();
|
|
11219
|
+
if (selection) {
|
|
11220
|
+
selection.removeAllRanges();
|
|
11221
|
+
selection.addRange(range);
|
|
8598
11222
|
}
|
|
8599
|
-
const selectedBlocks = this.getSelectedBlocks(editorContext);
|
|
8600
|
-
return selectedBlocks.some((block) => block instanceof RTECodeBlock ||
|
|
8601
|
-
block instanceof RTEImageBlock ||
|
|
8602
|
-
!ActionHandleStrategyRegistry.canHandle(LineHeightAnnotationAction.name, block));
|
|
8603
11223
|
}
|
|
8604
11224
|
/**
|
|
8605
|
-
*
|
|
11225
|
+
* Dispatches a synthetic `beforeinput` paste event into the editor pipeline.
|
|
11226
|
+
*
|
|
11227
|
+
* @param {HTMLDivElement} editableEl Editable content element.
|
|
11228
|
+
* @param {DataTransfer} dataTransfer Clipboard payload for the paste.
|
|
8606
11229
|
*/
|
|
8607
|
-
|
|
8608
|
-
|
|
8609
|
-
|
|
8610
|
-
|
|
8611
|
-
|
|
11230
|
+
dispatchBeforeInputPaste(editableEl, dataTransfer) {
|
|
11231
|
+
// This synthetic beforeinput event intentionally reuses the existing
|
|
11232
|
+
// UserInputController -> insertFromPaste -> InsertPasteHandler pipeline.
|
|
11233
|
+
const beforeInputEvent = new CustomEvent('beforeinput', {
|
|
11234
|
+
bubbles: true,
|
|
11235
|
+
cancelable: true,
|
|
8612
11236
|
});
|
|
11237
|
+
try {
|
|
11238
|
+
Object.defineProperties(beforeInputEvent, {
|
|
11239
|
+
inputType: {
|
|
11240
|
+
value: 'insertFromPaste',
|
|
11241
|
+
configurable: true,
|
|
11242
|
+
},
|
|
11243
|
+
dataTransfer: {
|
|
11244
|
+
value: dataTransfer,
|
|
11245
|
+
configurable: true,
|
|
11246
|
+
},
|
|
11247
|
+
});
|
|
11248
|
+
}
|
|
11249
|
+
catch (_a) {
|
|
11250
|
+
Object.assign(beforeInputEvent, {
|
|
11251
|
+
inputType: 'insertFromPaste',
|
|
11252
|
+
dataTransfer,
|
|
11253
|
+
});
|
|
11254
|
+
}
|
|
11255
|
+
editableEl.dispatchEvent(beforeInputEvent);
|
|
8613
11256
|
}
|
|
8614
11257
|
/**
|
|
8615
|
-
*
|
|
8616
|
-
*/
|
|
8617
|
-
handleButtonClick() {
|
|
8618
|
-
// No direct click action; selection happens in dropdown options.
|
|
8619
|
-
}
|
|
8620
|
-
/**
|
|
8621
|
-
* Get the selected line-height key for dropdown selection state.
|
|
11258
|
+
* Builds a `DataTransfer` payload carrying the paste content and mode marker.
|
|
8622
11259
|
*
|
|
8623
|
-
* @param {
|
|
8624
|
-
* @
|
|
11260
|
+
* @param {string} html HTML clipboard content.
|
|
11261
|
+
* @param {string} text Plain text clipboard content.
|
|
11262
|
+
* @param {PasteMode} [mode] Selected paste mode marker.
|
|
11263
|
+
* @returns {DataTransfer} Populated data transfer payload.
|
|
8625
11264
|
*/
|
|
8626
|
-
|
|
8627
|
-
|
|
8628
|
-
|
|
8629
|
-
|
|
11265
|
+
createDataTransfer(html, text, mode) {
|
|
11266
|
+
if (typeof DataTransfer !== 'undefined') {
|
|
11267
|
+
const dataTransfer = new DataTransfer();
|
|
11268
|
+
if (html) {
|
|
11269
|
+
dataTransfer.setData('text/html', html);
|
|
11270
|
+
}
|
|
11271
|
+
if (text) {
|
|
11272
|
+
dataTransfer.setData('text/plain', text);
|
|
11273
|
+
}
|
|
11274
|
+
if (mode) {
|
|
11275
|
+
dataTransfer.setData(RTE_PASTE_MODE_DATA_TYPE, mode);
|
|
11276
|
+
}
|
|
11277
|
+
return dataTransfer;
|
|
8630
11278
|
}
|
|
8631
|
-
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
11279
|
+
return {
|
|
11280
|
+
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
11281
|
+
getData: (type) => {
|
|
11282
|
+
if (type === 'text/html') {
|
|
11283
|
+
return html;
|
|
11284
|
+
}
|
|
11285
|
+
if (type === 'text/plain') {
|
|
11286
|
+
return text;
|
|
11287
|
+
}
|
|
11288
|
+
if (type === RTE_PASTE_MODE_DATA_TYPE) {
|
|
11289
|
+
return mode !== null && mode !== void 0 ? mode : '';
|
|
11290
|
+
}
|
|
11291
|
+
return '';
|
|
11292
|
+
},
|
|
11293
|
+
};
|
|
8637
11294
|
}
|
|
8638
11295
|
/**
|
|
8639
|
-
*
|
|
11296
|
+
* Tears down the chooser UI and removes dismissal listeners.
|
|
8640
11297
|
*
|
|
8641
|
-
* @param {
|
|
8642
|
-
* @returns {RTEBlock[]} Selected blocks.
|
|
11298
|
+
* @param {boolean} [clearPayload] Whether to also clear the pending payload.
|
|
8643
11299
|
*/
|
|
8644
|
-
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
|
|
8648
|
-
|
|
8649
|
-
|
|
8650
|
-
|
|
8651
|
-
|
|
8652
|
-
|
|
8653
|
-
|
|
8654
|
-
|
|
8655
|
-
|
|
11300
|
+
clearPasteChooser(clearPayload = true) {
|
|
11301
|
+
if (this.contentHostEl) {
|
|
11302
|
+
this.contentHostEl.removeEventListener('scroll', this.handleOutsideScroll);
|
|
11303
|
+
}
|
|
11304
|
+
if (this.listenerWindow) {
|
|
11305
|
+
this.listenerWindow.removeEventListener('scroll', this.handleOutsideScroll, true);
|
|
11306
|
+
this.listenerWindow = null;
|
|
11307
|
+
}
|
|
11308
|
+
if (this.listenerDocument) {
|
|
11309
|
+
this.listenerDocument.removeEventListener('mousedown', this.handleOutsidePointerDown, true);
|
|
11310
|
+
this.listenerDocument.removeEventListener('keydown', this.handleOutsideKeyDown, true);
|
|
11311
|
+
this.listenerDocument.removeEventListener('scroll', this.handleOutsideScroll, true);
|
|
11312
|
+
this.listenerDocument = null;
|
|
11313
|
+
}
|
|
11314
|
+
if (this.chooserUI) {
|
|
11315
|
+
this.chooserUI.destroy();
|
|
11316
|
+
}
|
|
11317
|
+
this.chooserUI = null;
|
|
11318
|
+
if (clearPayload) {
|
|
11319
|
+
this.pendingPastePayload = null;
|
|
8656
11320
|
}
|
|
8657
|
-
return selectedBlocks;
|
|
8658
11321
|
}
|
|
8659
11322
|
}
|
|
8660
|
-
(() => {
|
|
8661
|
-
VegaInternalIconManager.register({
|
|
8662
|
-
'line-height': lineHeight,
|
|
8663
|
-
});
|
|
8664
|
-
})();
|
|
8665
11323
|
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
11324
|
+
/**
|
|
11325
|
+
* Action to extract the currently selected content (HTML and plain text) from the editor.
|
|
11326
|
+
*
|
|
11327
|
+
* The action is read-only: it does not mutate the content state. The handling strategy resolves
|
|
11328
|
+
* the selected HTML and plain text and writes them directly to the clipboard.
|
|
11329
|
+
*
|
|
11330
|
+
* @example host.value.apply(new CopySelectedContentAction(selection, selectedNodes));
|
|
11331
|
+
*/
|
|
11332
|
+
class CopySelectedContentAction extends ModifyContentAction {
|
|
11333
|
+
constructor(selection, selectedNodes) {
|
|
8673
11334
|
super();
|
|
8674
|
-
this.
|
|
11335
|
+
this.type = 'COPY_SELECTED_CONTENT_ACTION';
|
|
11336
|
+
this.selection = null;
|
|
11337
|
+
this.selectedNodes = [];
|
|
11338
|
+
// The result of the copy operation, which may be a Promise if the clipboard write is asynchronous.
|
|
11339
|
+
this.copyResult = null;
|
|
11340
|
+
this.selection = selection;
|
|
11341
|
+
this.selectedNodes = selectedNodes;
|
|
11342
|
+
}
|
|
11343
|
+
}
|
|
11344
|
+
|
|
11345
|
+
/**
|
|
11346
|
+
* Toolbar button renderer for the Copy extension.
|
|
11347
|
+
* Renders a toolbar button that copies the currently selected editor text to the clipboard.
|
|
11348
|
+
*/
|
|
11349
|
+
class RTECopyToolbarButtonRenderer extends VegaRTEToolbarButtonRenderer {
|
|
11350
|
+
/**
|
|
11351
|
+
* Renders the copy toolbar button.
|
|
11352
|
+
*
|
|
11353
|
+
* @param {VegaRTECreateElementFunction} h - Create element function.
|
|
11354
|
+
* @param {VegaRTEToolbarRenderContext} editorContext - The render context for the editor.
|
|
11355
|
+
* @returns {VegaRTERenderResult} - The toolbar button render result.
|
|
11356
|
+
*/
|
|
11357
|
+
render(h, editorContext) {
|
|
11358
|
+
return this.renderButton(h, editorContext, {
|
|
11359
|
+
icon: 'rte-copy',
|
|
11360
|
+
tooltip: { text: 'Copy' },
|
|
11361
|
+
});
|
|
8675
11362
|
}
|
|
8676
11363
|
/**
|
|
8677
|
-
*
|
|
11364
|
+
* Determines if the copy button should be disabled.
|
|
11365
|
+
* Disabled when the editor is disabled or when no content is selected.
|
|
8678
11366
|
*
|
|
8679
|
-
* @param {
|
|
8680
|
-
* @returns {boolean}
|
|
11367
|
+
* @param {VegaRTEToolbarRenderContext} editorContext - The render context for the editor.
|
|
11368
|
+
* @returns {boolean} - True if the button should be disabled.
|
|
8681
11369
|
*/
|
|
8682
|
-
|
|
8683
|
-
|
|
11370
|
+
isDisabled(editorContext) {
|
|
11371
|
+
const context = this.getExtensionContext(editorContext.host);
|
|
11372
|
+
return (!context || context.host.disabled || !context.getSelection() || context.getSelection().isCollapsed);
|
|
8684
11373
|
}
|
|
8685
11374
|
/**
|
|
8686
|
-
*
|
|
11375
|
+
* Handles the copy button click event.
|
|
11376
|
+
* Dispatches a copy action through `host.value`; the action handler extracts the current selection
|
|
11377
|
+
* and writes the resulting HTML and plain text content to the clipboard.
|
|
8687
11378
|
*
|
|
8688
|
-
* @param {
|
|
8689
|
-
* @returns {VegaRTEBlockAnnotations} `{ lineHeight }` when present, otherwise `{}`.
|
|
11379
|
+
* @param {VegaRTEToolbarRenderContext} editorContext - The render context for the editor.
|
|
8690
11380
|
*/
|
|
8691
|
-
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
return {};
|
|
8696
|
-
}
|
|
8697
|
-
if (!this.allowedLineHeights.has(lineHeight)) {
|
|
8698
|
-
return {};
|
|
11381
|
+
handleButtonClick(editorContext) {
|
|
11382
|
+
const context = this.getExtensionContext(editorContext.host);
|
|
11383
|
+
if (context && context.host) {
|
|
11384
|
+
context.host.value.apply(new CopySelectedContentAction(context.getSelection(), context.getSelectedNodes()));
|
|
8699
11385
|
}
|
|
8700
|
-
|
|
11386
|
+
}
|
|
11387
|
+
/*istanbul ignore next*/
|
|
11388
|
+
/**
|
|
11389
|
+
* Handles dropdown click event. Not applicable for the copy button, so it's left empty.
|
|
11390
|
+
*/
|
|
11391
|
+
handleDropdownClick() {
|
|
8701
11392
|
}
|
|
8702
11393
|
}
|
|
11394
|
+
(() => {
|
|
11395
|
+
VegaInternalIconManager.register({ 'rte-copy': copy });
|
|
11396
|
+
})();
|
|
8703
11397
|
|
|
8704
11398
|
/**
|
|
8705
|
-
*
|
|
8706
|
-
*
|
|
8707
|
-
*
|
|
8708
|
-
* Note: moving out-of-list line-height values from the semantic annotation to
|
|
8709
|
-
* `customStyle.lineHeight` is handled by the per-host interceptor in
|
|
8710
|
-
* `line-height-host-interceptor.ts`, not here.
|
|
11399
|
+
* Strategy that extracts the HTML and plain text content of the current selection, stores the
|
|
11400
|
+
* result on the {@link CopySelectedContentAction} and writes it to the clipboard.
|
|
8711
11401
|
*/
|
|
8712
|
-
class
|
|
8713
|
-
|
|
8714
|
-
|
|
8715
|
-
|
|
11402
|
+
class CopySelectedContentStrategy extends ActionHandleStrategy {
|
|
11403
|
+
/**
|
|
11404
|
+
* @inheritDoc
|
|
11405
|
+
*/
|
|
11406
|
+
handleAction(action) {
|
|
11407
|
+
const parentBlocks = this.getParentBlocks(action.selectedNodes);
|
|
11408
|
+
const { html, text } = this.getSelectedContent(action.selection, parentBlocks);
|
|
11409
|
+
if (text || html) {
|
|
11410
|
+
action.copyResult = this.copyToClipboard(html, text);
|
|
11411
|
+
}
|
|
11412
|
+
}
|
|
11413
|
+
/**
|
|
11414
|
+
* Extracts the HTML and text content from the current selection.
|
|
11415
|
+
*
|
|
11416
|
+
* `Range.cloneContents()` returns only the nodes inside the selection, dropping the ancestor chain
|
|
11417
|
+
* between the selection's common ancestor and its block. When the selection stays inside a single
|
|
11418
|
+
* block, those dropped ancestors carry the styling, so the cloned fragment is re-wrapped with
|
|
11419
|
+
* shallow clones of every ancestor from the common ancestor up to the block, preserving arbitrary
|
|
11420
|
+
* inline nesting (e.g. `block > strong > em > span > text`).
|
|
11421
|
+
*
|
|
11422
|
+
* When the selection covers the whole block (its common ancestor sits above the block) or spans
|
|
11423
|
+
* multiple blocks, each block is already kept by `cloneContents()`, so no re-wrapping is done.
|
|
11424
|
+
*
|
|
11425
|
+
* @param {Nullable<Selection>} selection - The current selection object.
|
|
11426
|
+
* @param {RTEBlock[]} parentBlocks - The parent blocks of the selected nodes.
|
|
11427
|
+
* @returns {RTESelectedContent} - The HTML and text content of the selected content.
|
|
11428
|
+
*/
|
|
11429
|
+
getSelectedContent(selection, parentBlocks) {
|
|
11430
|
+
const result = { html: '', text: '' };
|
|
11431
|
+
if (!selection || selection.rangeCount === 0 || selection.isCollapsed) {
|
|
11432
|
+
return result;
|
|
11433
|
+
}
|
|
11434
|
+
const range = selection.getRangeAt(0);
|
|
11435
|
+
const doc = tryGetDocument();
|
|
11436
|
+
if (!doc) {
|
|
11437
|
+
return result;
|
|
11438
|
+
}
|
|
11439
|
+
// `cloneContents()` returns a `DocumentFragment` of real DOM nodes (already well-formed). Its
|
|
11440
|
+
// `textContent` is the plain text; the fragment itself is wrapped/serialized into HTML below.
|
|
11441
|
+
const fragment = range.cloneContents();
|
|
11442
|
+
// A `DocumentFragment` has no `innerHTML`, so a temporary element is used to serialize it to HTML.
|
|
11443
|
+
const container = doc.createElement('div');
|
|
11444
|
+
const commonAncestor = range.commonAncestorContainer;
|
|
11445
|
+
if (parentBlocks.length === 1) {
|
|
11446
|
+
const block = this.getDOMByEntity(parentBlocks[0]);
|
|
11447
|
+
// Only re-wrap when the block is dropped from the clone, i.e. the selection stays inside the
|
|
11448
|
+
// block. When the whole block is selected, `commonAncestor` sits above it and the clone
|
|
11449
|
+
// already keeps the block, so wrapping again would nest a duplicate block.
|
|
11450
|
+
if (block && block.contains(commonAncestor)) {
|
|
11451
|
+
// Wrap the cloned fragment with the dropped ancestor chain (from `commonAncestor` up to the
|
|
11452
|
+
// block), preserving each element's styles, then serialize the rebuilt block to HTML.
|
|
11453
|
+
const cloneNode = this.wrapWithAncestorChain(fragment, commonAncestor, block);
|
|
11454
|
+
container.appendChild(cloneNode);
|
|
11455
|
+
result.html = container.innerHTML;
|
|
11456
|
+
result.text = container.textContent;
|
|
11457
|
+
return result;
|
|
11458
|
+
}
|
|
11459
|
+
}
|
|
11460
|
+
else if (parentBlocks.length > 1 &&
|
|
11461
|
+
parentBlocks.every((block) => block.type === 'list-item')) {
|
|
11462
|
+
const firstList = parentBlocks[0].parent;
|
|
11463
|
+
if (firstList && parentBlocks.every((block) => block.parent === firstList)) {
|
|
11464
|
+
const listElement = this.getDOMByEntity(firstList);
|
|
11465
|
+
if (listElement && listElement.contains(commonAncestor)) {
|
|
11466
|
+
const cloneListElement = listElement.cloneNode(false);
|
|
11467
|
+
cloneListElement.appendChild(fragment);
|
|
11468
|
+
container.appendChild(cloneListElement);
|
|
11469
|
+
result.html = container.innerHTML;
|
|
11470
|
+
result.text = container.textContent;
|
|
11471
|
+
return result;
|
|
11472
|
+
}
|
|
11473
|
+
}
|
|
11474
|
+
}
|
|
11475
|
+
// The selection covers whole/multiple blocks: the clone already keeps them, so serialize as-is.
|
|
11476
|
+
container.appendChild(fragment);
|
|
11477
|
+
result.html = container.innerHTML;
|
|
11478
|
+
result.text = container.textContent;
|
|
11479
|
+
return result;
|
|
8716
11480
|
}
|
|
8717
11481
|
/**
|
|
8718
|
-
*
|
|
11482
|
+
* Wraps the cloned selection fragment with the ancestor chain that `cloneContents()` dropped,
|
|
11483
|
+
* rebuilding every element from the selection's common ancestor up to the block while preserving
|
|
11484
|
+
* each element's styles. Returns the cloned block that holds the rebuilt chain and content.
|
|
11485
|
+
*
|
|
11486
|
+
* When the common ancestor is the block itself, the fragment is appended directly under the cloned
|
|
11487
|
+
* block, since no inline ancestor chain was dropped.
|
|
11488
|
+
*
|
|
11489
|
+
* @param {Node} content - The cloned selection fragment to place at the innermost level.
|
|
11490
|
+
* @param {Node} commonAncestor - The selection's common ancestor container.
|
|
11491
|
+
* @param {HTMLElement} block - The block element to stop cloning at.
|
|
11492
|
+
* @returns {HTMLElement} - The cloned block wrapping the rebuilt ancestor chain and content.
|
|
11493
|
+
*/
|
|
11494
|
+
wrapWithAncestorChain(content, commonAncestor, block) {
|
|
11495
|
+
const blockClone = block.cloneNode(false);
|
|
11496
|
+
// `cloneContents()` keeps everything inside `commonAncestor`, so the dropped chain runs from
|
|
11497
|
+
// `commonAncestor` (its element, when the ancestor is a text node) up to the block.
|
|
11498
|
+
const targetElement = commonAncestor.nodeType === Node.TEXT_NODE
|
|
11499
|
+
? commonAncestor.parentElement
|
|
11500
|
+
: commonAncestor;
|
|
11501
|
+
// The target is the block itself: the content sits directly under the block, no chain to rebuild.
|
|
11502
|
+
if (targetElement === block) {
|
|
11503
|
+
blockClone.appendChild(content);
|
|
11504
|
+
return blockClone;
|
|
11505
|
+
}
|
|
11506
|
+
// Clone the target as the innermost container and place the content inside it.
|
|
11507
|
+
const innermost = targetElement.cloneNode(false);
|
|
11508
|
+
innermost.appendChild(content);
|
|
11509
|
+
// Walk up from the target to the block, cloning each ancestor and nesting the chain bottom-up.
|
|
11510
|
+
let shouldAppendNode = innermost;
|
|
11511
|
+
let current = targetElement.parentElement;
|
|
11512
|
+
while (current && current !== block) {
|
|
11513
|
+
const parentClone = current.cloneNode(false);
|
|
11514
|
+
parentClone.appendChild(shouldAppendNode);
|
|
11515
|
+
shouldAppendNode = parentClone;
|
|
11516
|
+
current = current.parentElement;
|
|
11517
|
+
}
|
|
11518
|
+
// Append the assembled ancestor chain to the cloned block.
|
|
11519
|
+
blockClone.appendChild(shouldAppendNode);
|
|
11520
|
+
// When the block is a list item inside an ordered list, wrap the cloned list item with a
|
|
11521
|
+
// shallow clone of the ordered list to preserve the numbering.
|
|
11522
|
+
if (blockClone.tagName === 'LI' &&
|
|
11523
|
+
current &&
|
|
11524
|
+
current.parentElement &&
|
|
11525
|
+
current.parentElement.tagName === 'OL') {
|
|
11526
|
+
const cloneList = current.parentElement.cloneNode(false);
|
|
11527
|
+
cloneList.appendChild(blockClone);
|
|
11528
|
+
return cloneList;
|
|
11529
|
+
}
|
|
11530
|
+
return blockClone;
|
|
11531
|
+
}
|
|
11532
|
+
/**
|
|
11533
|
+
* Retrieves the parent blocks of the given selected nodes.
|
|
11534
|
+
*
|
|
11535
|
+
* @param {RTENode[]} selectedNodes - The nodes for which to find parent blocks.
|
|
11536
|
+
* @returns {RTEBlock[]} An array of parent blocks.
|
|
11537
|
+
*/
|
|
11538
|
+
getParentBlocks(selectedNodes) {
|
|
11539
|
+
const parentBlocks = [];
|
|
11540
|
+
for (const node of selectedNodes) {
|
|
11541
|
+
const block = node.parent;
|
|
11542
|
+
if (block && !parentBlocks.includes(block)) {
|
|
11543
|
+
parentBlocks.push(block);
|
|
11544
|
+
}
|
|
11545
|
+
}
|
|
11546
|
+
return parentBlocks;
|
|
11547
|
+
}
|
|
11548
|
+
/**
|
|
11549
|
+
* Copies both HTML and plain text content to the clipboard using the Clipboard API.
|
|
8719
11550
|
*
|
|
8720
|
-
*
|
|
11551
|
+
* Browser compatibility for `navigator.clipboard.write()` with `ClipboardItem`:
|
|
11552
|
+
* - Chrome 76+, Edge 79+, Safari 13.1+, Firefox 127+
|
|
11553
|
+
*
|
|
11554
|
+
* Since Firefox 86+ must be supported, fall back to `execCommand('copy')` when the
|
|
11555
|
+
* Clipboard API is unavailable or fails (e.g. Firefox 86-126).
|
|
11556
|
+
*
|
|
11557
|
+
* @param {string} htmlContent - The HTML content to copy.
|
|
11558
|
+
* @param {string} textContent - The plain text content to copy.
|
|
11559
|
+
* @returns {Promise<boolean>} - A promise that resolves to true if the copy operation succeeded.
|
|
8721
11560
|
*/
|
|
8722
|
-
|
|
8723
|
-
|
|
11561
|
+
async copyToClipboard(htmlContent, textContent) {
|
|
11562
|
+
let result = false;
|
|
11563
|
+
try {
|
|
11564
|
+
const htmlBlob = new Blob([htmlContent], { type: 'text/html' });
|
|
11565
|
+
const textBlob = new Blob([textContent], { type: 'text/plain' });
|
|
11566
|
+
const clipboardItem = new ClipboardItem({
|
|
11567
|
+
'text/html': htmlBlob,
|
|
11568
|
+
'text/plain': textBlob,
|
|
11569
|
+
});
|
|
11570
|
+
await navigator.clipboard.write([clipboardItem]);
|
|
11571
|
+
result = true;
|
|
11572
|
+
}
|
|
11573
|
+
catch (_a) {
|
|
11574
|
+
// Fallback for browsers without ClipboardItem support (e.g. Firefox 86-126)
|
|
11575
|
+
result = this.copyWithExecCommand(htmlContent, textContent);
|
|
11576
|
+
if (!result) {
|
|
11577
|
+
LogUtility.warn('Failed to copy the selected content to the clipboard. Use keyboard shortcuts instead.');
|
|
11578
|
+
}
|
|
11579
|
+
}
|
|
11580
|
+
return result;
|
|
8724
11581
|
}
|
|
8725
11582
|
/**
|
|
8726
|
-
*
|
|
11583
|
+
* Fallback copy method using `execCommand('copy')` for browsers that don't support the
|
|
11584
|
+
* Clipboard API (e.g. Firefox 86-126).
|
|
8727
11585
|
*
|
|
8728
|
-
*
|
|
8729
|
-
*
|
|
11586
|
+
* Instead of creating a temporary element and changing the selection (which would clear the
|
|
11587
|
+
* editor's Shadow DOM selection and steal focus), this intercepts the native `copy` event that
|
|
11588
|
+
* `execCommand('copy')` triggers and overrides its clipboard data. The user's original selection
|
|
11589
|
+
* and focus stay untouched.
|
|
11590
|
+
*
|
|
11591
|
+
* @param {string} htmlContent - The HTML content to copy.
|
|
11592
|
+
* @param {string} textContent - The plain text content to copy.
|
|
11593
|
+
* @returns {boolean} - True if the copy command succeeded.
|
|
8730
11594
|
*/
|
|
8731
|
-
|
|
8732
|
-
const
|
|
8733
|
-
|
|
8734
|
-
|
|
8735
|
-
if (lineHeight && customStyle && customStyle.lineHeight === lineHeight) {
|
|
8736
|
-
delete customStyle.lineHeight;
|
|
11595
|
+
copyWithExecCommand(htmlContent, textContent) {
|
|
11596
|
+
const doc = tryGetDocument();
|
|
11597
|
+
if (!doc) {
|
|
11598
|
+
return false;
|
|
8737
11599
|
}
|
|
8738
|
-
|
|
8739
|
-
|
|
11600
|
+
/**
|
|
11601
|
+
* Overrides the clipboard data of the native copy event triggered by `execCommand('copy')`.
|
|
11602
|
+
*
|
|
11603
|
+
* @param {ClipboardEvent} event - The native copy event.
|
|
11604
|
+
*/
|
|
11605
|
+
const onCopy = (event) => {
|
|
11606
|
+
if (event.clipboardData) {
|
|
11607
|
+
event.clipboardData.setData('text/html', htmlContent);
|
|
11608
|
+
event.clipboardData.setData('text/plain', textContent);
|
|
11609
|
+
}
|
|
11610
|
+
event.preventDefault();
|
|
11611
|
+
};
|
|
11612
|
+
doc.addEventListener('copy', onCopy);
|
|
11613
|
+
try {
|
|
11614
|
+
return doc.execCommand('copy');
|
|
11615
|
+
}
|
|
11616
|
+
finally {
|
|
11617
|
+
doc.removeEventListener('copy', onCopy);
|
|
8740
11618
|
}
|
|
8741
|
-
return annotations;
|
|
8742
11619
|
}
|
|
8743
11620
|
}
|
|
8744
11621
|
|
|
8745
11622
|
/**
|
|
8746
|
-
*
|
|
8747
|
-
*
|
|
8748
|
-
*/
|
|
8749
|
-
const allowedLineHeightsByClass = new WeakMap();
|
|
8750
|
-
/**
|
|
8751
|
-
* Tracks which block classes have had their JSON `lineHeight` annotation
|
|
8752
|
-
* handler installed, so we register it exactly once per class.
|
|
8753
|
-
*/
|
|
8754
|
-
const handlerInstalledClasses = new WeakSet();
|
|
8755
|
-
/**
|
|
8756
|
-
* Move an out-of-range `lineHeight` annotation into `customStyle.lineHeight`.
|
|
8757
|
-
*
|
|
8758
|
-
* @param {RTEBlock} block - The block to inspect.
|
|
8759
|
-
* @param {ReadonlySet<string>} allowed - Allowed line-height values.
|
|
11623
|
+
* Vega Rich Text Editor Copy Extension.
|
|
11624
|
+
* Adds a toolbar button that copies the currently selected editor content to the clipboard.
|
|
8760
11625
|
*/
|
|
8761
|
-
|
|
8762
|
-
|
|
8763
|
-
|
|
8764
|
-
|
|
8765
|
-
}
|
|
8766
|
-
const value = annotation.lineHeight;
|
|
8767
|
-
block.annotationMap.delete(RTELineHeightExtensionAnnotationTypeEnum.LINE_HEIGHT);
|
|
8768
|
-
const existing = block.annotationMap.get(CommonAnnotationTypeEnum.CUSTOM_STYLE);
|
|
8769
|
-
if (existing) {
|
|
8770
|
-
existing.customStyle = Object.assign(Object.assign({}, existing.customStyle), { lineHeight: value });
|
|
8771
|
-
}
|
|
8772
|
-
else {
|
|
8773
|
-
block.annotationMap.set(...CustomStyleAnnotation.from({ lineHeight: value }));
|
|
11626
|
+
class VegaRTECopyExtension extends VegaRTEExtension {
|
|
11627
|
+
/** @inheritDoc */
|
|
11628
|
+
prepareBeforeLoad(host) {
|
|
11629
|
+
this.registerToolbarButtonRenderer(VegaRTECopyExtension.TOOLBAR_BUTTON_KEY, new RTECopyToolbarButtonRenderer(), host);
|
|
8774
11630
|
}
|
|
8775
11631
|
}
|
|
11632
|
+
(() => {
|
|
11633
|
+
ActionHandleStrategyRegistry.register('COPY_SELECTED_CONTENT_ACTION', VegaRTEContent.name, new CopySelectedContentStrategy());
|
|
11634
|
+
})();
|
|
11635
|
+
// The unique key for the copy toolbar button, used for registration and retrieval.
|
|
11636
|
+
VegaRTECopyExtension.TOOLBAR_BUTTON_KEY = 'copy';
|
|
11637
|
+
|
|
11638
|
+
const cutIcon = {
|
|
11639
|
+
icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2026 Fonticons, Inc. --><path fill="currentColor" d="M48 112a64 64 0 1 1 128 0 64 64 0 1 1 -128 0zm176 0C224 50.1 173.9 0 112 0S0 50.1 0 112 50.1 224 112 224c24 0 46.3-7.6 64.5-20.4l57.3 52.4-57.3 52.4C158.3 295.6 136 288 112 288 50.1 288 0 338.1 0 400s50.1 112 112 112 112-50.1 112-112c0-20.5-5.5-39.6-15.1-56.2L504.2 73.7c9.8-8.9 10.5-24.1 1.5-33.9s-24.1-10.5-33.9-1.5l-202.4 185.2-60.5-55.3c9.6-16.5 15.1-35.7 15.1-56.2zM471.8 473.7c9.8 8.9 25 8.3 33.9-1.5s8.3-25-1.5-33.9L338.6 286.8 302.7 319 471.8 473.7zM48 400a64 64 0 1 1 128 0 64 64 0 1 1 -128 0z"/></svg>`,
|
|
11640
|
+
};
|
|
11641
|
+
|
|
8776
11642
|
/**
|
|
8777
|
-
*
|
|
11643
|
+
* Delete the currently selected content from the editor.
|
|
8778
11644
|
*
|
|
8779
|
-
* @
|
|
11645
|
+
* @example currentVegaRTEContent.apply(new DeleteSelectedContentAction(host, mergeFlag))
|
|
8780
11646
|
*/
|
|
8781
|
-
|
|
8782
|
-
|
|
8783
|
-
|
|
11647
|
+
class DeleteSelectedContentAction extends ModifyContentAction {
|
|
11648
|
+
constructor(host, mergeFlag, flushable = false) {
|
|
11649
|
+
super();
|
|
11650
|
+
this.type = ModifyContentActionType.DELETE_SELECTED_CONTENT;
|
|
11651
|
+
// mergeFlag indicates whether the action should merge adjacent text nodes after deletion.
|
|
11652
|
+
this.mergeFlag = false;
|
|
11653
|
+
this.host = host;
|
|
11654
|
+
this.mergeFlag = mergeFlag;
|
|
11655
|
+
this.isFlushable = flushable;
|
|
8784
11656
|
}
|
|
8785
|
-
handlerInstalledClasses.add(BlockClass);
|
|
8786
|
-
VegaRTEExtension.attachAnnotationFromJson(BlockClass, 'lineHeight', (instance, value) => {
|
|
8787
|
-
if (typeof value !== 'string' || value === '') {
|
|
8788
|
-
return;
|
|
8789
|
-
}
|
|
8790
|
-
instance.annotationMap.set(...LineHeightAnnotation.from(value));
|
|
8791
|
-
const allowed = allowedLineHeightsByClass.get(BlockClass);
|
|
8792
|
-
if (allowed && allowed.size > 0) {
|
|
8793
|
-
normalizeLineHeightStyle(instance, allowed);
|
|
8794
|
-
}
|
|
8795
|
-
});
|
|
8796
|
-
}
|
|
8797
|
-
/**
|
|
8798
|
-
* Update the set of allowed line-height values for a block class.
|
|
8799
|
-
*
|
|
8800
|
-
* @param {LineHeightBlockClass} BlockClass - Target block class.
|
|
8801
|
-
* @param {VegaRTELineHeightOption[]} options - Allowed line-height options.
|
|
8802
|
-
*/
|
|
8803
|
-
function setAllowedLineHeights(BlockClass, options) {
|
|
8804
|
-
allowedLineHeightsByClass.set(BlockClass, new Set(options.map((option) => option.value)));
|
|
8805
11657
|
}
|
|
8806
11658
|
|
|
8807
11659
|
/**
|
|
8808
|
-
*
|
|
8809
|
-
*
|
|
11660
|
+
* Toolbar button renderer for the Cut extension.
|
|
11661
|
+
* Reuses the Copy renderer to write the selected editor content to the clipboard,
|
|
11662
|
+
* then removes the selected content from the editor.
|
|
8810
11663
|
*/
|
|
8811
|
-
class
|
|
8812
|
-
|
|
8813
|
-
|
|
8814
|
-
|
|
8815
|
-
|
|
8816
|
-
|
|
8817
|
-
|
|
8818
|
-
|
|
11664
|
+
class RTECutToolbarButtonRenderer extends RTECopyToolbarButtonRenderer {
|
|
11665
|
+
/**
|
|
11666
|
+
* Renders the cut toolbar button.
|
|
11667
|
+
*
|
|
11668
|
+
* @param {VegaRTECreateElementFunction} h - Create element function.
|
|
11669
|
+
* @param {VegaRTEToolbarRenderContext} editorContext - The render context for the editor.
|
|
11670
|
+
* @returns {VegaRTERenderResult} - The toolbar button render result.
|
|
11671
|
+
*/
|
|
11672
|
+
render(h, editorContext) {
|
|
11673
|
+
return this.renderButton(h, editorContext, {
|
|
11674
|
+
icon: 'rte-cut',
|
|
11675
|
+
tooltip: { text: 'Cut' },
|
|
8819
11676
|
});
|
|
8820
11677
|
}
|
|
8821
11678
|
/**
|
|
8822
|
-
*
|
|
8823
|
-
*
|
|
11679
|
+
* Handles the cut button click event.
|
|
11680
|
+
* Copies the current editor selection to the clipboard and then deletes it from the editor.
|
|
8824
11681
|
*
|
|
8825
|
-
* @
|
|
8826
|
-
* @param {HTMLVegaRichTextEditorElement} host - The host element.
|
|
11682
|
+
* @param {VegaRTEToolbarRenderContext} editorContext - The render context for the editor.
|
|
8827
11683
|
*/
|
|
8828
|
-
|
|
8829
|
-
|
|
8830
|
-
|
|
11684
|
+
async handleButtonClick(editorContext) {
|
|
11685
|
+
const context = this.getExtensionContext(editorContext.host);
|
|
11686
|
+
if (context && context.host) {
|
|
11687
|
+
const { host } = context;
|
|
11688
|
+
const copySelectedContentAction = new CopySelectedContentAction(context.getSelection(), context.getSelectedNodes());
|
|
11689
|
+
host.value.apply(copySelectedContentAction);
|
|
11690
|
+
const isCopySuccessful = await copySelectedContentAction.copyResult;
|
|
11691
|
+
isCopySuccessful && host.value.apply(new DeleteSelectedContentAction(host, true, true));
|
|
11692
|
+
}
|
|
8831
11693
|
}
|
|
8832
11694
|
}
|
|
8833
11695
|
(() => {
|
|
8834
|
-
|
|
8835
|
-
VegaRTEExtension.registerActionHandleStrategy(BlockClass.name, LineHeightAnnotationAction.name, new BlockUpdateLineHeightStrategy());
|
|
8836
|
-
RTEFilterStylesStrategyRegistry.register(BlockClass.name, new LineHeightFilterStylesStrategy());
|
|
8837
|
-
attachLineHeightFromJson(BlockClass);
|
|
8838
|
-
});
|
|
11696
|
+
VegaInternalIconManager.register({ 'rte-cut': cutIcon });
|
|
8839
11697
|
})();
|
|
8840
11698
|
|
|
11699
|
+
/**
|
|
11700
|
+
* Vega Rich Text Editor Cut Extension.
|
|
11701
|
+
* Adds a toolbar button that copies the currently selected editor content to the clipboard
|
|
11702
|
+
* and removes it from the editor.
|
|
11703
|
+
*/
|
|
11704
|
+
class VegaRTECutExtension extends VegaRTEExtension {
|
|
11705
|
+
/** @inheritDoc */
|
|
11706
|
+
prepareBeforeLoad(host) {
|
|
11707
|
+
this.registerToolbarButtonRenderer(VegaRTECutExtension.TOOLBAR_BUTTON_KEY, new RTECutToolbarButtonRenderer(), host);
|
|
11708
|
+
}
|
|
11709
|
+
}
|
|
11710
|
+
// The unique key for the cut toolbar button, used for registration and retrieval.
|
|
11711
|
+
VegaRTECutExtension.TOOLBAR_BUTTON_KEY = 'cut';
|
|
11712
|
+
|
|
8841
11713
|
/**
|
|
8842
11714
|
* Insert a new children to the nearest position of the parent container
|
|
8843
11715
|
*
|
|
@@ -8956,7 +11828,7 @@ class VegaRTETokenExtension extends VegaRTEExtension {
|
|
|
8956
11828
|
}
|
|
8957
11829
|
/** @inheritDoc */
|
|
8958
11830
|
prepareBeforeLoad(host) {
|
|
8959
|
-
this.registerToolbarButtonRenderer(
|
|
11831
|
+
this.registerToolbarButtonRenderer(VegaRTETokenExtension.TOOLBAR_BUTTON_KEY, new RTETokenToolbarButtonRenderer(this.tokenList), host);
|
|
8960
11832
|
}
|
|
8961
11833
|
}
|
|
8962
11834
|
(() => {
|
|
@@ -8964,5 +11836,6 @@ class VegaRTETokenExtension extends VegaRTEExtension {
|
|
|
8964
11836
|
VegaRTEExtension.registerActionHandleStrategy(RTETextBlock.name, 'INSERT_TOKEN_NODE', insertTokenNodeStrategy);
|
|
8965
11837
|
VegaRTEExtension.registerActionHandleStrategy(RTEListItemBlock.name, 'INSERT_TOKEN_NODE', insertTokenNodeStrategy);
|
|
8966
11838
|
})();
|
|
11839
|
+
VegaRTETokenExtension.TOOLBAR_BUTTON_KEY = 'token';
|
|
8967
11840
|
|
|
8968
|
-
export {
|
|
11841
|
+
export { VegaRTEToolbarButtonRenderer as A, RTETokenNode as B, RTETokenNodeRenderer as C, RTELanguageToolbarButtonRenderer as D, RTEBlockquoteBlock as E, DeleteSelectedContentAction as F, ActionHandlerInterceptor as G, InsertNodeToNearestRootAction as I, RTETokenToolbarButtonRenderer as R, VegaLoader as V, VegaNotify as a, VegaEventManager$1 as b, VegaZIndexManager$1 as c, VegaSkeletonLoader as d, VegaTranslation as e, VegaRTETokenExtension as f, VegaRTELanguageExtension as g, VegaRTEDefaultLineHeightOptions as h, VegaRTEFunctionExtension as i, VegaRTETableExtension as j, VegaRTEHorizontalLineExtension as k, VegaRTEBlockquoteExtension as l, VegaRTESpecialCharactersExtension as m, VegaRTEFindReplaceExtension as n, VegaRTESpecialCharactersEssentials as o, VegaRTESpecialCharactersLatin as p, VegaRTESpecialCharactersMathematical as q, VegaRTESpecialCharactersArrows as r, VegaRTESpecialCharactersCurrency as s, VegaRTESpecialCharactersText as t, VegaRTELineHeightExtension as u, VegaRTEPasteExtension as v, waitForVega as w, VegaRTECopyExtension as x, VegaRTECutExtension as y, VegaCalendarPeriodFactory as z };
|