@caribeng/ui 22.2.0 → 22.5.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/README.md +311 -231
- package/fesm2022/caribeng-ui-accordion.mjs +2 -2
- package/fesm2022/caribeng-ui-account-picker.mjs +533 -0
- package/fesm2022/caribeng-ui-aging.mjs +488 -0
- package/fesm2022/{caribeng-ui-caribe-alert.mjs → caribeng-ui-alert.mjs} +45 -45
- package/fesm2022/caribeng-ui-amount.mjs +249 -0
- package/fesm2022/caribeng-ui-anchor.mjs +2 -2
- package/fesm2022/{caribeng-ui-caribe-animation-lottie.mjs → caribeng-ui-animation-lottie.mjs} +41 -41
- package/fesm2022/caribeng-ui-audit-log.mjs +323 -0
- package/fesm2022/caribeng-ui-avatar.mjs +4 -4
- package/fesm2022/caribeng-ui-badge.mjs +8 -2
- package/fesm2022/caribeng-ui-breadcrumb.mjs +26 -9
- package/fesm2022/{caribeng-ui-caribe-button-group.mjs → caribeng-ui-button-group.mjs} +15 -15
- package/fesm2022/caribeng-ui-button.mjs +2 -2
- package/fesm2022/{caribeng-ui-caribe-card-content.mjs → caribeng-ui-card-content.mjs} +39 -39
- package/fesm2022/{caribeng-ui-caribe-card-move.mjs → caribeng-ui-card-move.mjs} +65 -65
- package/fesm2022/{caribeng-ui-caribe-card-primary.mjs → caribeng-ui-card-primary.mjs} +31 -31
- package/fesm2022/{caribeng-ui-caribe-card-select.mjs → caribeng-ui-card-select.mjs} +67 -67
- package/fesm2022/caribeng-ui-carousel.mjs +2 -2
- package/fesm2022/caribeng-ui-cascadeselect.mjs +45 -9
- package/fesm2022/caribeng-ui-chart.mjs +8 -5
- package/fesm2022/caribeng-ui-chat.mjs +1303 -0
- package/fesm2022/{caribeng-ui-caribe-coachmark.mjs → caribeng-ui-coachmark.mjs} +85 -85
- package/fesm2022/caribeng-ui-code-block.mjs +332 -0
- package/fesm2022/caribeng-ui-colorpicker.mjs +99 -5
- package/fesm2022/caribeng-ui-command-palette.mjs +476 -0
- package/fesm2022/caribeng-ui-comments.mjs +864 -0
- package/fesm2022/caribeng-ui-confirmdialog.mjs +78 -13
- package/fesm2022/caribeng-ui-confirmpopup.mjs +91 -16
- package/fesm2022/caribeng-ui-contextmenu.mjs +8 -4
- package/fesm2022/caribeng-ui-copy-button.mjs +230 -0
- package/fesm2022/caribeng-ui-datagrid.mjs +1635 -0
- package/fesm2022/caribeng-ui-dataview.mjs +6 -3
- package/fesm2022/caribeng-ui-datepicker.mjs +2 -2
- package/fesm2022/caribeng-ui-dock.mjs +52 -12
- package/fesm2022/caribeng-ui-document.mjs +495 -0
- package/fesm2022/{caribeng-ui-caribe-dots-indicator.mjs → caribeng-ui-dots-indicator.mjs} +41 -41
- package/fesm2022/caribeng-ui-drawer.mjs +9 -4
- package/fesm2022/caribeng-ui-dynamic-form.mjs +1581 -0
- package/fesm2022/caribeng-ui-editor.mjs +2324 -1311
- package/fesm2022/{caribeng-ui-caribe-expansion-panel.mjs → caribeng-ui-expansion-panel.mjs} +39 -39
- package/fesm2022/{caribeng-ui-caribe-fab-menu.mjs → caribeng-ui-fab-menu.mjs} +75 -75
- package/fesm2022/caribeng-ui-fieldset.mjs +2 -2
- package/fesm2022/caribeng-ui-file-card.mjs +134 -17
- package/fesm2022/{caribeng-ui-caribe-file-download.mjs → caribeng-ui-file-download.mjs} +63 -63
- package/fesm2022/caribeng-ui-financial-report.mjs +380 -0
- package/fesm2022/{caribeng-ui-caribe-float-menu.mjs → caribeng-ui-float-menu.mjs} +31 -31
- package/fesm2022/caribeng-ui-floatlabel.mjs +2 -2
- package/fesm2022/caribeng-ui-flow.mjs +1508 -0
- package/fesm2022/caribeng-ui-folder-card.mjs +122 -12
- package/fesm2022/{caribeng-ui-caribe-footer.mjs → caribeng-ui-footer.mjs} +19 -19
- package/fesm2022/caribeng-ui-gantt.mjs +1094 -0
- package/fesm2022/{caribeng-ui-caribe-header.mjs → caribeng-ui-header.mjs} +49 -49
- package/fesm2022/{caribeng-ui-caribe-icon-button.mjs → caribeng-ui-icon-button.mjs} +23 -23
- package/fesm2022/caribeng-ui-image-cropper.mjs +671 -0
- package/fesm2022/caribeng-ui-import-wizard.mjs +746 -0
- package/fesm2022/caribeng-ui-infinite-scroll.mjs +226 -0
- package/fesm2022/caribeng-ui-inlinemessage.mjs +2 -2
- package/fesm2022/caribeng-ui-input-number.mjs +22 -6
- package/fesm2022/{caribeng-ui-caribe-input-search.mjs → caribeng-ui-input-search.mjs} +65 -65
- package/fesm2022/{caribeng-ui-caribe-input-token.mjs → caribeng-ui-input-token.mjs} +59 -59
- package/fesm2022/caribeng-ui-inputmask.mjs +8 -2
- package/fesm2022/caribeng-ui-inputotp.mjs +2 -2
- package/fesm2022/caribeng-ui-journal-entry.mjs +340 -0
- package/fesm2022/caribeng-ui-kanban.mjs +4612 -0
- package/fesm2022/caribeng-ui-kbd.mjs +122 -0
- package/fesm2022/caribeng-ui-kpi.mjs +189 -0
- package/fesm2022/{caribeng-ui-caribe-link.mjs → caribeng-ui-link.mjs} +37 -37
- package/fesm2022/caribeng-ui-masonry.mjs +93 -0
- package/fesm2022/caribeng-ui-media-player.mjs +821 -0
- package/fesm2022/caribeng-ui-megamenu.mjs +13 -9
- package/fesm2022/caribeng-ui-mentions.mjs +495 -0
- package/fesm2022/caribeng-ui-menu.mjs +8 -4
- package/fesm2022/caribeng-ui-menubar.mjs +9 -5
- package/fesm2022/caribeng-ui-message.mjs +6 -3
- package/fesm2022/caribeng-ui-notification-center.mjs +636 -0
- package/fesm2022/{caribeng-ui-caribe-off-canvas.mjs → caribeng-ui-off-canvas.mjs} +55 -55
- package/fesm2022/{caribeng-ui-caribe-onboarding.mjs → caribeng-ui-onboarding.mjs} +105 -105
- package/fesm2022/caribeng-ui-orderlist.mjs +2 -2
- package/fesm2022/caribeng-ui-organizationchart.mjs +6 -10
- package/fesm2022/caribeng-ui-panel.mjs +6 -6
- package/fesm2022/caribeng-ui-panelmenu.mjs +9 -6
- package/fesm2022/caribeng-ui-pdfviewer.mjs +1755 -25
- package/fesm2022/caribeng-ui-period-picker.mjs +637 -0
- package/fesm2022/caribeng-ui-phone-input.mjs +688 -0
- package/fesm2022/caribeng-ui-picklist.mjs +2 -2
- package/fesm2022/caribeng-ui-pivot.mjs +419 -0
- package/fesm2022/caribeng-ui-pricing.mjs +263 -69
- package/fesm2022/caribeng-ui-qrcode.mjs +564 -0
- package/fesm2022/caribeng-ui-rating.mjs +14 -17
- package/fesm2022/caribeng-ui-reconciliation.mjs +386 -0
- package/fesm2022/caribeng-ui-ripple.mjs +2 -0
- package/fesm2022/caribeng-ui-scheduler.mjs +1027 -0
- package/fesm2022/caribeng-ui-scrolltop.mjs +3 -1
- package/fesm2022/caribeng-ui-select.mjs +2 -2
- package/fesm2022/{caribeng-ui-caribe-shortcut.mjs → caribeng-ui-shortcut.mjs} +29 -29
- package/fesm2022/caribeng-ui-sidebar.mjs +573 -0
- package/fesm2022/caribeng-ui-signature-pad.mjs +654 -0
- package/fesm2022/{caribeng-ui-caribe-status.mjs → caribeng-ui-status.mjs} +11 -11
- package/fesm2022/{caribeng-ui-caribe-step-indicator.mjs → caribeng-ui-step-indicator.mjs} +53 -53
- package/fesm2022/caribeng-ui-stepper.mjs +12 -5
- package/fesm2022/caribeng-ui-steps.mjs +2 -2
- package/fesm2022/caribeng-ui-table.mjs +333 -19
- package/fesm2022/caribeng-ui-tabmenu.mjs +10 -6
- package/fesm2022/caribeng-ui-tabs.mjs +2 -2
- package/fesm2022/{caribeng-ui-caribe-tag-button.mjs → caribeng-ui-tag-button.mjs} +25 -25
- package/fesm2022/caribeng-ui-tag.mjs +2 -2
- package/fesm2022/caribeng-ui-tieredmenu.mjs +111 -21
- package/fesm2022/{caribeng-ui-caribe-tracker.mjs → caribeng-ui-tracker.mjs} +29 -29
- package/fesm2022/{caribeng-ui-caribe-transaction-status.mjs → caribeng-ui-transaction-status.mjs} +11 -11
- package/fesm2022/caribeng-ui-treeselect.mjs +44 -9
- package/fesm2022/caribeng-ui-treetable.mjs +236 -17
- package/fesm2022/{caribeng-ui-caribe-voucher.mjs → caribeng-ui-voucher.mjs} +45 -45
- package/fesm2022/caribeng-ui-xlsx.mjs +486 -0
- package/fesm2022/caribeng-ui.mjs +1568 -1
- package/package.json +258 -178
- package/schematics/collection.json +5 -0
- package/schematics/migrate-from-primeng/core-icons.json +1 -0
- package/schematics/migrate-from-primeng/icons.js +269 -0
- package/schematics/migrate-from-primeng/index.js +290 -0
- package/schematics/migrate-from-primeng/mappings/button.js +93 -0
- package/schematics/migrate-from-primeng/mappings/card.js +33 -0
- package/schematics/migrate-from-primeng/mappings/checkbox.js +44 -0
- package/schematics/migrate-from-primeng/mappings/confirm.js +221 -0
- package/schematics/migrate-from-primeng/mappings/datepicker.js +121 -0
- package/schematics/migrate-from-primeng/mappings/dialog.js +84 -0
- package/schematics/migrate-from-primeng/mappings/index.js +88 -0
- package/schematics/migrate-from-primeng/mappings/inputnumber.js +62 -0
- package/schematics/migrate-from-primeng/mappings/inputtext.js +60 -0
- package/schematics/migrate-from-primeng/mappings/menu.js +468 -0
- package/schematics/migrate-from-primeng/mappings/multiselect.js +49 -0
- package/schematics/migrate-from-primeng/mappings/radiobutton.js +22 -0
- package/schematics/migrate-from-primeng/mappings/rules.js +206 -0
- package/schematics/migrate-from-primeng/mappings/select.js +97 -0
- package/schematics/migrate-from-primeng/mappings/service-calls.js +100 -0
- package/schematics/migrate-from-primeng/mappings/table.js +613 -0
- package/schematics/migrate-from-primeng/mappings/tabs.js +225 -0
- package/schematics/migrate-from-primeng/mappings/tag.js +29 -0
- package/schematics/migrate-from-primeng/mappings/textarea.js +44 -0
- package/schematics/migrate-from-primeng/mappings/toast.js +173 -0
- package/schematics/migrate-from-primeng/mappings/toggleswitch.js +37 -0
- package/schematics/migrate-from-primeng/mappings/tooltip.js +44 -0
- package/schematics/migrate-from-primeng/mappings/types.js +10 -0
- package/schematics/migrate-from-primeng/report.js +79 -0
- package/schematics/migrate-from-primeng/schema.js +2 -0
- package/schematics/migrate-from-primeng/schema.json +34 -0
- package/schematics/migrate-from-primeng/template.js +387 -0
- package/schematics/migrate-from-primeng/todo.js +5 -0
- package/schematics/migrate-from-primeng/typescript.js +442 -0
- package/schematics/migrate-from-primeng/workspace.js +117 -0
- package/schematics/ng-add/index.js +16 -1
- package/types/caribeng-ui-account-picker.d.ts +142 -0
- package/types/caribeng-ui-aging.d.ts +123 -0
- package/types/{caribeng-ui-caribe-alert.d.ts → caribeng-ui-alert.d.ts} +2 -2
- package/types/caribeng-ui-amount.d.ts +82 -0
- package/types/{caribeng-ui-caribe-animation-lottie.d.ts → caribeng-ui-animation-lottie.d.ts} +2 -2
- package/types/caribeng-ui-audit-log.d.ts +86 -0
- package/types/caribeng-ui-badge.d.ts +3 -1
- package/types/caribeng-ui-breadcrumb.d.ts +9 -1
- package/types/{caribeng-ui-caribe-button-group.d.ts → caribeng-ui-button-group.d.ts} +2 -2
- package/types/{caribeng-ui-caribe-card-content.d.ts → caribeng-ui-card-content.d.ts} +2 -2
- package/types/{caribeng-ui-caribe-card-move.d.ts → caribeng-ui-card-move.d.ts} +1 -1
- package/types/{caribeng-ui-caribe-card-primary.d.ts → caribeng-ui-card-primary.d.ts} +2 -2
- package/types/{caribeng-ui-caribe-card-select.d.ts → caribeng-ui-card-select.d.ts} +2 -2
- package/types/caribeng-ui-cascadeselect.d.ts +6 -0
- package/types/caribeng-ui-chart.d.ts +3 -1
- package/types/caribeng-ui-chat.d.ts +299 -0
- package/types/{caribeng-ui-caribe-coachmark.d.ts → caribeng-ui-coachmark.d.ts} +2 -2
- package/types/caribeng-ui-code-block.d.ts +65 -0
- package/types/caribeng-ui-colorpicker.d.ts +5 -1
- package/types/caribeng-ui-command-palette.d.ts +133 -0
- package/types/caribeng-ui-comments.d.ts +214 -0
- package/types/caribeng-ui-confirmdialog.d.ts +34 -2
- package/types/caribeng-ui-confirmpopup.d.ts +39 -2
- package/types/caribeng-ui-contextmenu.d.ts +7 -0
- package/types/caribeng-ui-copy-button.d.ts +90 -0
- package/types/caribeng-ui-datagrid.d.ts +269 -0
- package/types/caribeng-ui-dataview.d.ts +2 -0
- package/types/caribeng-ui-dock.d.ts +13 -1
- package/types/caribeng-ui-document.d.ts +138 -0
- package/types/{caribeng-ui-caribe-dots-indicator.d.ts → caribeng-ui-dots-indicator.d.ts} +2 -2
- package/types/caribeng-ui-drawer.d.ts +3 -1
- package/types/caribeng-ui-dynamic-form.d.ts +409 -0
- package/types/caribeng-ui-editor.d.ts +110 -12
- package/types/{caribeng-ui-caribe-expansion-panel.d.ts → caribeng-ui-expansion-panel.d.ts} +1 -1
- package/types/{caribeng-ui-caribe-fab-menu.d.ts → caribeng-ui-fab-menu.d.ts} +2 -2
- package/types/caribeng-ui-file-card.d.ts +27 -2
- package/types/{caribeng-ui-caribe-file-download.d.ts → caribeng-ui-file-download.d.ts} +1 -1
- package/types/caribeng-ui-financial-report.d.ts +102 -0
- package/types/{caribeng-ui-caribe-float-menu.d.ts → caribeng-ui-float-menu.d.ts} +2 -2
- package/types/caribeng-ui-flow.d.ts +228 -0
- package/types/caribeng-ui-folder-card.d.ts +27 -5
- package/types/{caribeng-ui-caribe-footer.d.ts → caribeng-ui-footer.d.ts} +2 -2
- package/types/caribeng-ui-gantt.d.ts +244 -0
- package/types/{caribeng-ui-caribe-header.d.ts → caribeng-ui-header.d.ts} +2 -2
- package/types/{caribeng-ui-caribe-icon-button.d.ts → caribeng-ui-icon-button.d.ts} +2 -2
- package/types/{caribeng-ui-caribe-illustration.d.ts → caribeng-ui-illustration.d.ts} +1 -1
- package/types/caribeng-ui-image-cropper.d.ts +169 -0
- package/types/caribeng-ui-import-wizard.d.ts +149 -0
- package/types/caribeng-ui-infinite-scroll.d.ts +75 -0
- package/types/caribeng-ui-input-number.d.ts +1 -1
- package/types/{caribeng-ui-caribe-input-search.d.ts → caribeng-ui-input-search.d.ts} +2 -2
- package/types/{caribeng-ui-caribe-input-token.d.ts → caribeng-ui-input-token.d.ts} +2 -2
- package/types/caribeng-ui-inputmask.d.ts +3 -1
- package/types/caribeng-ui-journal-entry.d.ts +110 -0
- package/types/caribeng-ui-kanban.d.ts +783 -0
- package/types/caribeng-ui-kbd.d.ts +36 -0
- package/types/caribeng-ui-kpi.d.ts +64 -0
- package/types/{caribeng-ui-caribe-link.d.ts → caribeng-ui-link.d.ts} +2 -2
- package/types/{caribeng-ui-caribe-logo.d.ts → caribeng-ui-logo.d.ts} +1 -1
- package/types/caribeng-ui-masonry.d.ts +32 -0
- package/types/caribeng-ui-media-player.d.ts +140 -0
- package/types/caribeng-ui-megamenu.d.ts +11 -0
- package/types/caribeng-ui-mentions.d.ts +127 -0
- package/types/caribeng-ui-menu.d.ts +7 -0
- package/types/caribeng-ui-menubar.d.ts +7 -0
- package/types/caribeng-ui-message.d.ts +2 -0
- package/types/caribeng-ui-notification-center.d.ts +153 -0
- package/types/{caribeng-ui-caribe-off-canvas.d.ts → caribeng-ui-off-canvas.d.ts} +2 -2
- package/types/{caribeng-ui-caribe-onboarding.d.ts → caribeng-ui-onboarding.d.ts} +2 -2
- package/types/caribeng-ui-panelmenu.d.ts +7 -0
- package/types/caribeng-ui-pdfviewer.d.ts +245 -5
- package/types/caribeng-ui-period-picker.d.ts +148 -0
- package/types/caribeng-ui-phone-input.d.ts +168 -0
- package/types/caribeng-ui-pivot.d.ts +116 -0
- package/types/caribeng-ui-pricing.d.ts +41 -4
- package/types/caribeng-ui-qrcode.d.ts +88 -0
- package/types/caribeng-ui-reconciliation.d.ts +109 -0
- package/types/caribeng-ui-scheduler.d.ts +188 -0
- package/types/{caribeng-ui-caribe-shortcut.d.ts → caribeng-ui-shortcut.d.ts} +2 -2
- package/types/caribeng-ui-sidebar.d.ts +167 -0
- package/types/caribeng-ui-signature-pad.d.ts +151 -0
- package/types/{caribeng-ui-caribe-status.d.ts → caribeng-ui-status.d.ts} +2 -2
- package/types/{caribeng-ui-caribe-step-indicator.d.ts → caribeng-ui-step-indicator.d.ts} +2 -2
- package/types/caribeng-ui-stepper.d.ts +2 -0
- package/types/caribeng-ui-table.d.ts +67 -3
- package/types/caribeng-ui-tabmenu.d.ts +7 -0
- package/types/{caribeng-ui-caribe-tag-button.d.ts → caribeng-ui-tag-button.d.ts} +2 -2
- package/types/caribeng-ui-tieredmenu.d.ts +17 -1
- package/types/{caribeng-ui-caribe-tracker.d.ts → caribeng-ui-tracker.d.ts} +2 -2
- package/types/{caribeng-ui-caribe-transaction-status.d.ts → caribeng-ui-transaction-status.d.ts} +2 -2
- package/types/caribeng-ui-treeselect.d.ts +6 -0
- package/types/caribeng-ui-treetable.d.ts +67 -2
- package/types/{caribeng-ui-caribe-voucher.d.ts → caribeng-ui-voucher.d.ts} +2 -2
- package/types/caribeng-ui-xlsx.d.ts +79 -0
- package/types/caribeng-ui.d.ts +985 -2
- package/fesm2022/caribeng-ui-caribe-calendar.mjs +0 -92
- package/fesm2022/caribeng-ui-caribe-circle-loading.mjs +0 -130
- package/fesm2022/caribeng-ui-caribe-data-table.mjs +0 -94
- package/fesm2022/caribeng-ui-caribe-file-select.mjs +0 -88
- package/fesm2022/caribeng-ui-caribe-inline-alert.mjs +0 -100
- package/fesm2022/caribeng-ui-caribe-input-date.mjs +0 -138
- package/fesm2022/caribeng-ui-caribe-input-select.mjs +0 -154
- package/fesm2022/caribeng-ui-caribe-loader.mjs +0 -48
- package/fesm2022/caribeng-ui-caribe-modal.mjs +0 -119
- package/fesm2022/caribeng-ui-caribe-pictogram.mjs +0 -52
- package/fesm2022/caribeng-ui-caribe-pop-over.mjs +0 -104
- package/fesm2022/caribeng-ui-caribe-preloader.mjs +0 -57
- package/fesm2022/caribeng-ui-caribe-progress-bar.mjs +0 -84
- package/fesm2022/caribeng-ui-caribe-stroke.mjs +0 -69
- package/fesm2022/caribeng-ui-caribe-switch.mjs +0 -182
- package/styles/caribeng.css +0 -380
- package/styles/scss/_mixins.scss +0 -46
- package/styles/scss/_overlay.scss +0 -15
- package/styles/scss/_tokens.scss +0 -267
- package/styles/scss/_utilities.scss +0 -73
- package/styles/scss/_variables.scss +0 -33
- package/styles/scss/index.scss +0 -16
- package/types/caribeng-ui-caribe-calendar.d.ts +0 -27
- package/types/caribeng-ui-caribe-circle-loading.d.ts +0 -28
- package/types/caribeng-ui-caribe-data-table.d.ts +0 -26
- package/types/caribeng-ui-caribe-file-select.d.ts +0 -31
- package/types/caribeng-ui-caribe-inline-alert.d.ts +0 -28
- package/types/caribeng-ui-caribe-input-date.d.ts +0 -40
- package/types/caribeng-ui-caribe-input-select.d.ts +0 -47
- package/types/caribeng-ui-caribe-loader.d.ts +0 -22
- package/types/caribeng-ui-caribe-modal.d.ts +0 -21
- package/types/caribeng-ui-caribe-pictogram.d.ts +0 -15
- package/types/caribeng-ui-caribe-pop-over.d.ts +0 -17
- package/types/caribeng-ui-caribe-preloader.d.ts +0 -24
- package/types/caribeng-ui-caribe-progress-bar.d.ts +0 -28
- package/types/caribeng-ui-caribe-stroke.d.ts +0 -17
- package/types/caribeng-ui-caribe-switch.d.ts +0 -55
- /package/fesm2022/{caribeng-ui-caribe-illustration.mjs → caribeng-ui-illustration.mjs} +0 -0
- /package/fesm2022/{caribeng-ui-caribe-logo.mjs → caribeng-ui-logo.mjs} +0 -0
|
@@ -3,8 +3,10 @@ import { inject, Injector, input, booleanAttribute, numberAttribute, model, outp
|
|
|
3
3
|
import { DOCUMENT } from '@angular/common';
|
|
4
4
|
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
5
5
|
import { CbIconRegistry, CbIcon } from '@caribeng/ui/icon';
|
|
6
|
-
import { heading3, heading2, heading1, pilcrow, wholeWord, caseSensitive, x, chevronUp, textSearch, omega, captions, ratio, alignEndVertical, alignCenterHorizontal, alignStartVertical, type, trash, panelTop, crop, columns3, rows3, betweenVerticalEnd, betweenVerticalStart, betweenHorizontalEnd, betweenHorizontalStart, table, minimize, maximize, superscript, subscript, textAlignJustify, listIndentDecrease, listIndentIncrease, listChecks, chevronDown, highlighter, baseline, aLargeSmall, codeXml, squareCode, redo, undo, removeFormatting, minus, image, unlink, link, textAlignEnd, textAlignCenter, textAlignStart, textQuote, listOrdered, list, code, strikethrough, underline, italic, bold } from '@caribeng/ui/icons';
|
|
6
|
+
import { copy, atSign, tableCellsSplit, tableCellsMerge, tableOfContents, listCollapse, heading3, heading2, heading1, pilcrow, wholeWord, caseSensitive, x, chevronUp, textSearch, omega, captions, ratio, alignEndVertical, alignCenterHorizontal, alignStartVertical, type, trash, panelTop, crop, columns3, rows3, betweenVerticalEnd, betweenVerticalStart, betweenHorizontalEnd, betweenHorizontalStart, table, minimize, maximize, superscript, subscript, textAlignJustify, listIndentDecrease, listIndentIncrease, listChecks, chevronDown, highlighter, baseline, aLargeSmall, codeXml, squareCode, redo, undo, removeFormatting, minus, image, unlink, link, textAlignEnd, textAlignCenter, textAlignStart, textQuote, listOrdered, list, code, strikethrough, underline, italic, bold } from '@caribeng/ui/icons';
|
|
7
7
|
import { injectCbTranslation, cbFieldState, cbFormat } from '@caribeng/ui';
|
|
8
|
+
import { initialRect, fitRatio, resizeRect, clampRect, cropImage } from '@caribeng/ui/image-cropper';
|
|
9
|
+
import { cbCodeLanguageLabel, cbCodeSupports, cbHighlight } from '@caribeng/ui/code-block';
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* Tipografías por defecto, todas libres (Google Fonts). Las "clásicas de Office" son
|
|
@@ -103,12 +105,17 @@ let allowedFonts = DEFAULT_FONTS;
|
|
|
103
105
|
/** Etiquetas permitidas y sus atributos. */
|
|
104
106
|
const ALLOWED = {
|
|
105
107
|
p: ['style'],
|
|
106
|
-
h1: ['style'],
|
|
107
|
-
h2: ['style'],
|
|
108
|
-
h3: ['style'],
|
|
109
|
-
h4: ['style'],
|
|
108
|
+
h1: ['style', 'id'],
|
|
109
|
+
h2: ['style', 'id'],
|
|
110
|
+
h3: ['style', 'id'],
|
|
111
|
+
h4: ['style', 'id'],
|
|
110
112
|
blockquote: ['style'],
|
|
111
|
-
pre: ['style', 'data-language'],
|
|
113
|
+
pre: ['style', 'data-language', 'data-highlight'],
|
|
114
|
+
figure: ['style'],
|
|
115
|
+
figcaption: [],
|
|
116
|
+
details: ['open'],
|
|
117
|
+
summary: [],
|
|
118
|
+
nav: ['data-type'],
|
|
112
119
|
ul: ['data-type'],
|
|
113
120
|
ol: ['start'],
|
|
114
121
|
li: ['style', 'data-checked'],
|
|
@@ -149,7 +156,7 @@ const DROP = new Set([
|
|
|
149
156
|
'form', 'input', 'button', 'select', 'textarea', 'head', 'title', 'meta', 'link', 'xml',
|
|
150
157
|
'colgroup', 'col',
|
|
151
158
|
]);
|
|
152
|
-
const BLOCKS = new Set(['p', 'h1', 'h2', 'h3', 'h4', 'blockquote', 'pre', 'ul', 'ol', 'hr', 'table']);
|
|
159
|
+
const BLOCKS = new Set(['p', 'h1', 'h2', 'h3', 'h4', 'blockquote', 'pre', 'ul', 'ol', 'hr', 'table', 'figure', 'details', 'nav']);
|
|
153
160
|
const ALIGN = new Set(['left', 'center', 'right', 'justify']);
|
|
154
161
|
const TABLE_PARTS = new Set(['thead', 'tbody', 'tfoot', 'tr', 'th', 'td', 'caption']);
|
|
155
162
|
const INLINE = new Set(['strong', 'em', 'u', 's', 'code', 'sub', 'sup']);
|
|
@@ -413,6 +420,9 @@ function appendClean(parent, node, images) {
|
|
|
413
420
|
const language = source.getAttribute('data-language');
|
|
414
421
|
if (language && /^[\w+#.-]{1,30}$/.test(language))
|
|
415
422
|
pre.setAttribute('data-language', language);
|
|
423
|
+
// Bloque coloreado por el propio editor (se vuelve a colorear al editarlo).
|
|
424
|
+
if (source.getAttribute('data-highlight') === 'auto')
|
|
425
|
+
pre.setAttribute('data-highlight', 'auto');
|
|
416
426
|
const code = document.createElement('code');
|
|
417
427
|
pre.append(code);
|
|
418
428
|
appendCode(code, source, true);
|
|
@@ -423,6 +433,15 @@ function appendClean(parent, node, images) {
|
|
|
423
433
|
parent.append(pre);
|
|
424
434
|
return;
|
|
425
435
|
}
|
|
436
|
+
// Mención (@persona): un <span data-mention> con solo su texto.
|
|
437
|
+
if (tag === 'span' && /^[\w.@:-]{1,64}$/.test(source.getAttribute('data-mention') ?? '') && !inCode) {
|
|
438
|
+
const mention = document.createElement('span');
|
|
439
|
+
mention.setAttribute('data-mention', source.getAttribute('data-mention'));
|
|
440
|
+
mention.textContent = (source.textContent ?? '').replace(/\s+/g, ' ').trim();
|
|
441
|
+
if (mention.textContent)
|
|
442
|
+
parent.append(mention);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
426
445
|
// Google Docs envuelve todo en <b style="font-weight:normal">: no es negrita.
|
|
427
446
|
if (tag === 'b' && /font-weight\s*:\s*(normal|[1-4]00)/i.test(style))
|
|
428
447
|
tag = 'span';
|
|
@@ -480,20 +499,32 @@ function appendClean(parent, node, images) {
|
|
|
480
499
|
return;
|
|
481
500
|
}
|
|
482
501
|
// Un bloque dentro de un párrafo (p. ej. <div> dentro de <p>) se aplana.
|
|
483
|
-
if (tag === 'p' && (
|
|
502
|
+
if (tag === 'p' && /^(P|TD|TH|LI|FIGCAPTION|SUMMARY)$/.test(parent.tagName)) {
|
|
484
503
|
if (parent.childNodes.length)
|
|
485
504
|
parent.append(document.createElement('br'));
|
|
486
505
|
for (const child of Array.from(source.childNodes))
|
|
487
506
|
appendClean(parent, child, images);
|
|
488
507
|
return;
|
|
489
508
|
}
|
|
509
|
+
if ((tag === 'figcaption' && parent.tagName !== 'FIGURE') || (tag === 'summary' && parent.tagName !== 'DETAILS')) {
|
|
510
|
+
const p = document.createElement('p');
|
|
511
|
+
for (const child of Array.from(source.childNodes))
|
|
512
|
+
appendClean(p, child, images);
|
|
513
|
+
parent.append(p);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
if (tag === 'nav' && source.getAttribute('data-type') !== 'toc') {
|
|
517
|
+
for (const child of Array.from(source.childNodes))
|
|
518
|
+
appendClean(parent, child, images);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
490
521
|
const el = document.createElement(tag);
|
|
491
522
|
for (const name of ALLOWED[tag]) {
|
|
492
523
|
const value = source.getAttribute(name);
|
|
493
524
|
if (value === null)
|
|
494
525
|
continue;
|
|
495
526
|
if (name === 'style') {
|
|
496
|
-
if (tag === 'img') {
|
|
527
|
+
if (tag === 'img' || tag === 'figure') {
|
|
497
528
|
const { width, align } = readImageStyle(value);
|
|
498
529
|
const css = imageStyle(width, align);
|
|
499
530
|
if (css)
|
|
@@ -532,8 +563,19 @@ function appendClean(parent, node, images) {
|
|
|
532
563
|
el.setAttribute(name, value);
|
|
533
564
|
}
|
|
534
565
|
else if (name === 'data-type') {
|
|
535
|
-
if (value === 'taskList')
|
|
536
|
-
el.setAttribute('data-type',
|
|
566
|
+
if ((tag === 'ul' && value === 'taskList') || (tag === 'nav' && value === 'toc'))
|
|
567
|
+
el.setAttribute('data-type', value);
|
|
568
|
+
}
|
|
569
|
+
else if (name === 'data-highlight') {
|
|
570
|
+
if (value === 'auto')
|
|
571
|
+
el.setAttribute('data-highlight', 'auto');
|
|
572
|
+
}
|
|
573
|
+
else if (name === 'id') {
|
|
574
|
+
if (/^[a-z][a-z0-9-]{0,79}$/.test(value))
|
|
575
|
+
el.setAttribute('id', value);
|
|
576
|
+
}
|
|
577
|
+
else if (name === 'open') {
|
|
578
|
+
el.setAttribute('open', '');
|
|
537
579
|
}
|
|
538
580
|
else if (name === 'data-checked') {
|
|
539
581
|
if (value === 'true' || value === 'false')
|
|
@@ -585,6 +627,19 @@ function appendClean(parent, node, images) {
|
|
|
585
627
|
appendClean(el, child, images);
|
|
586
628
|
if (tag === 'table')
|
|
587
629
|
normalizeTable(el);
|
|
630
|
+
// Una figura sin imagen no es una figura: se queda su contenido.
|
|
631
|
+
if (tag === 'figure' && !el.querySelector('img')) {
|
|
632
|
+
for (const child of Array.from(el.childNodes)) {
|
|
633
|
+
if (child.tagName === 'FIGCAPTION') {
|
|
634
|
+
const p = document.createElement('p');
|
|
635
|
+
p.append(...Array.from(child.childNodes));
|
|
636
|
+
el.before(p);
|
|
637
|
+
}
|
|
638
|
+
else
|
|
639
|
+
el.before(child);
|
|
640
|
+
}
|
|
641
|
+
el.remove();
|
|
642
|
+
}
|
|
588
643
|
if (tag === 'li' && parent.getAttribute('data-type') === 'taskList' && !el.hasAttribute('data-checked'))
|
|
589
644
|
el.setAttribute('data-checked', 'false');
|
|
590
645
|
if (tag === 'li' && parent.getAttribute('data-type') !== 'taskList')
|
|
@@ -659,17 +714,28 @@ function normalizeTable(table) {
|
|
|
659
714
|
table.remove();
|
|
660
715
|
return;
|
|
661
716
|
}
|
|
662
|
-
|
|
717
|
+
// Texto suelto en la fila: se descarta (no es una celda).
|
|
663
718
|
for (const row of rows) {
|
|
664
|
-
// Texto suelto en la fila: se descarta (no es una celda).
|
|
665
719
|
for (const child of Array.from(row.childNodes))
|
|
666
720
|
if (child.nodeType === Node.TEXT_NODE || !/^(TD|TH)$/.test(child.tagName))
|
|
667
721
|
child.remove();
|
|
668
|
-
|
|
722
|
+
}
|
|
723
|
+
// Columnas ocupadas en cada fila, contando las celdas que bajan desde arriba (rowspan).
|
|
724
|
+
const used = rows.map(() => 0);
|
|
725
|
+
rows.forEach((row, r) => {
|
|
726
|
+
for (const cell of Array.from(row.children)) {
|
|
727
|
+
const cs = Number(cell.getAttribute('colspan')) || 1;
|
|
728
|
+
const rs = Math.min(Number(cell.getAttribute('rowspan')) || 1, rows.length - r);
|
|
729
|
+
for (let i = 0; i < rs; i++)
|
|
730
|
+
used[r + i] += cs;
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
const width = Math.max(...used);
|
|
734
|
+
rows.forEach((row, r) => {
|
|
669
735
|
const inHead = row.parentElement?.tagName === 'THEAD';
|
|
670
|
-
for (let i =
|
|
736
|
+
for (let i = used[r]; i < width; i++)
|
|
671
737
|
row.append(document.createElement(inHead ? 'th' : 'td'));
|
|
672
|
-
}
|
|
738
|
+
});
|
|
673
739
|
// Las celdas de cabecera de <thead> se marcan como de columna.
|
|
674
740
|
for (const th of Array.from(table.querySelectorAll('thead th')))
|
|
675
741
|
if (!th.hasAttribute('scope'))
|
|
@@ -697,7 +763,7 @@ function wrapInlineRuns(root) {
|
|
|
697
763
|
}
|
|
698
764
|
/** Bloques sin texto ni imagen (p. ej. `<p><br></p>` al final) se quitan, salvo `<hr>` y celdas. */
|
|
699
765
|
function removeEmptyBlocks(root) {
|
|
700
|
-
for (const block of Array.from(root.querySelectorAll('p, h1, h2, h3, h4, blockquote, li, pre, strong, em, u, s, code, caption, span'))) {
|
|
766
|
+
for (const block of Array.from(root.querySelectorAll('p, h1, h2, h3, h4, blockquote, li, pre, strong, em, u, s, code, caption, span, figcaption'))) {
|
|
701
767
|
if (block.closest('pre') && block.tagName !== 'PRE')
|
|
702
768
|
continue;
|
|
703
769
|
if (!block.textContent?.replace(/ /g, ' ').trim() && !block.querySelector('img, hr, table')) {
|
|
@@ -880,105 +946,7 @@ function toggleTask(li) {
|
|
|
880
946
|
li.setAttribute('data-checked', li.getAttribute('data-checked') === 'true' ? 'false' : 'true');
|
|
881
947
|
}
|
|
882
948
|
|
|
883
|
-
/**
|
|
884
|
-
const MIN = 0.05;
|
|
885
|
-
const clamp = (n, min, max) => Math.min(max, Math.max(min, n));
|
|
886
|
-
/** Mantiene el recuadro dentro de la imagen y con un tamaño mínimo. */
|
|
887
|
-
function clampRect(rect) {
|
|
888
|
-
const w = clamp(rect.w, MIN, 1);
|
|
889
|
-
const h = clamp(rect.h, MIN, 1);
|
|
890
|
-
return { x: clamp(rect.x, 0, 1 - w), y: clamp(rect.y, 0, 1 - h), w, h };
|
|
891
|
-
}
|
|
892
|
-
/**
|
|
893
|
-
* Ajusta el recuadro a una proporción (ancho / alto en píxeles), lo más grande
|
|
894
|
-
* posible dentro del actual y centrado en él. `ratio` null = libre.
|
|
895
|
-
*/
|
|
896
|
-
function fitRatio(rect, ratio, imageWidth, imageHeight) {
|
|
897
|
-
if (!ratio || !imageWidth || !imageHeight)
|
|
898
|
-
return clampRect(rect);
|
|
899
|
-
// Proporción expresada en fracciones de la imagen.
|
|
900
|
-
const r = ratio * (imageHeight / imageWidth);
|
|
901
|
-
let w = rect.w;
|
|
902
|
-
let h = w / r;
|
|
903
|
-
if (h > rect.h) {
|
|
904
|
-
h = rect.h;
|
|
905
|
-
w = h * r;
|
|
906
|
-
}
|
|
907
|
-
if (w > 1) {
|
|
908
|
-
w = 1;
|
|
909
|
-
h = w / r;
|
|
910
|
-
}
|
|
911
|
-
if (h > 1) {
|
|
912
|
-
h = 1;
|
|
913
|
-
w = h * r;
|
|
914
|
-
}
|
|
915
|
-
const cx = rect.x + rect.w / 2;
|
|
916
|
-
const cy = rect.y + rect.h / 2;
|
|
917
|
-
return clampRect({ x: cx - w / 2, y: cy - h / 2, w, h });
|
|
918
|
-
}
|
|
919
|
-
/** Recuadro inicial: el mayor posible con la proporción, centrado. */
|
|
920
|
-
function initialRect(ratio, imageWidth, imageHeight) {
|
|
921
|
-
return fitRatio({ x: 0, y: 0, w: 1, h: 1 }, ratio, imageWidth, imageHeight);
|
|
922
|
-
}
|
|
923
|
-
/**
|
|
924
|
-
* Redimensiona arrastrando una esquina (`nw`, `ne`, `sw`, `se`) un desplazamiento
|
|
925
|
-
* (dx, dy) en fracciones; con proporción, el alto sigue al ancho.
|
|
926
|
-
*/
|
|
927
|
-
function resizeRect(rect, corner, dx, dy, ratio, imageWidth, imageHeight) {
|
|
928
|
-
let { x, y, w, h } = rect;
|
|
929
|
-
const right = x + w;
|
|
930
|
-
const bottom = y + h;
|
|
931
|
-
if (corner.includes('w'))
|
|
932
|
-
x = clamp(x + dx, 0, right - MIN);
|
|
933
|
-
const newRight = corner.includes('e') ? clamp(right + dx, x + MIN, 1) : right;
|
|
934
|
-
if (corner.includes('n'))
|
|
935
|
-
y = clamp(y + dy, 0, bottom - MIN);
|
|
936
|
-
const newBottom = corner.includes('s') ? clamp(bottom + dy, y + MIN, 1) : bottom;
|
|
937
|
-
w = newRight - x;
|
|
938
|
-
h = newBottom - y;
|
|
939
|
-
if (ratio && imageWidth && imageHeight) {
|
|
940
|
-
const r = ratio * (imageHeight / imageWidth);
|
|
941
|
-
h = w / r;
|
|
942
|
-
if (corner.includes('n'))
|
|
943
|
-
y = bottom - h;
|
|
944
|
-
if (y < 0 || y + h > 1) {
|
|
945
|
-
// No cabe: se limita por el alto.
|
|
946
|
-
h = corner.includes('n') ? bottom : 1 - y;
|
|
947
|
-
w = h * r;
|
|
948
|
-
if (corner.includes('w'))
|
|
949
|
-
x = right - w;
|
|
950
|
-
if (corner.includes('n'))
|
|
951
|
-
y = bottom - h;
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
return clampRect({ x, y, w, h });
|
|
955
|
-
}
|
|
956
|
-
/**
|
|
957
|
-
* Recorta los píxeles de la imagen. Las imágenes de otro dominio necesitan que
|
|
958
|
-
* su servidor permita CORS; si no, el navegador bloquea la lectura y se rechaza.
|
|
959
|
-
*/
|
|
960
|
-
async function cropImage(src, rect, doc) {
|
|
961
|
-
const image = new Image();
|
|
962
|
-
if (!/^(data|blob):/i.test(src))
|
|
963
|
-
image.crossOrigin = 'anonymous';
|
|
964
|
-
await new Promise((resolve, reject) => {
|
|
965
|
-
image.onload = () => resolve();
|
|
966
|
-
image.onerror = () => reject(new Error('No se pudo cargar la imagen'));
|
|
967
|
-
image.src = src;
|
|
968
|
-
});
|
|
969
|
-
const sw = Math.max(1, Math.round(rect.w * image.naturalWidth));
|
|
970
|
-
const sh = Math.max(1, Math.round(rect.h * image.naturalHeight));
|
|
971
|
-
const canvas = doc.createElement('canvas');
|
|
972
|
-
canvas.width = sw;
|
|
973
|
-
canvas.height = sh;
|
|
974
|
-
const ctx = canvas.getContext('2d');
|
|
975
|
-
if (!ctx)
|
|
976
|
-
throw new Error('Canvas 2D no disponible');
|
|
977
|
-
ctx.drawImage(image, Math.round(rect.x * image.naturalWidth), Math.round(rect.y * image.naturalHeight), sw, sh, 0, 0, sw, sh);
|
|
978
|
-
const type = /^data:image\/jpe?g|\.jpe?g(\?|$)/i.test(src) ? 'image/jpeg' : 'image/png';
|
|
979
|
-
// toBlob lanza SecurityError si el canvas quedó "contaminado" (imagen sin CORS).
|
|
980
|
-
return new Promise((resolve, reject) => canvas.toBlob((blob) => (blob ? resolve(blob) : reject(new Error('Sin datos'))), type, 0.92));
|
|
981
|
-
}
|
|
949
|
+
/** El cálculo y el recorte viven en `@caribeng/ui/image-cropper`, compartidos con `cb-image-cropper`. */
|
|
982
950
|
|
|
983
951
|
// --- Tipografía al escribir ------------------------------------------------------------------
|
|
984
952
|
/** Sustituciones al escribir: el patrón termina en el carácter recién tecleado. */
|
|
@@ -1105,6 +1073,366 @@ function fold(text) {
|
|
|
1105
1073
|
return text.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase();
|
|
1106
1074
|
}
|
|
1107
1075
|
|
|
1076
|
+
/**
|
|
1077
|
+
* Bloques de la fase 3 del editor: código coloreado por lenguaje, celdas
|
|
1078
|
+
* combinadas, pie de imagen e índice del documento. Son funciones sobre el
|
|
1079
|
+
* DOM del área editable (sin estado); el componente decide cuándo llamarlas.
|
|
1080
|
+
*/
|
|
1081
|
+
// --- Código ------------------------------------------------------------------------------
|
|
1082
|
+
/** Lenguajes del selector del bloque de código (`''` = texto sin colorear). */
|
|
1083
|
+
const CODE_LANGUAGES = ['', 'typescript', 'javascript', 'html', 'css', 'scss', 'json', 'bash', 'python', 'sql'];
|
|
1084
|
+
function codeLanguageLabel(language) {
|
|
1085
|
+
return cbCodeLanguageLabel(language);
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Colores de un tema oscuro (como VS Code): van en línea en el HTML, así el
|
|
1089
|
+
* bloque se ve igual dentro y fuera del editor, en tema claro u oscuro.
|
|
1090
|
+
*/
|
|
1091
|
+
const CODE_FG = '#d4d4d4';
|
|
1092
|
+
const CODE_BG = '#1e1e1e';
|
|
1093
|
+
const PALETTE = {
|
|
1094
|
+
keyword: '#569cd6',
|
|
1095
|
+
builtin: '#4ec9b0',
|
|
1096
|
+
string: '#ce9178',
|
|
1097
|
+
number: '#b5cea8',
|
|
1098
|
+
comment: '#6a9955',
|
|
1099
|
+
function: '#dcdcaa',
|
|
1100
|
+
tag: '#569cd6',
|
|
1101
|
+
attr: '#9cdcfe',
|
|
1102
|
+
property: '#9cdcfe',
|
|
1103
|
+
variable: '#9cdcfe',
|
|
1104
|
+
};
|
|
1105
|
+
/**
|
|
1106
|
+
* Cambia el lenguaje de un bloque de código y lo colorea (si el lenguaje se
|
|
1107
|
+
* conoce). El texto no cambia; el cursor se conserva si estaba dentro.
|
|
1108
|
+
*/
|
|
1109
|
+
function highlightCodeBlock(pre, language, doc) {
|
|
1110
|
+
const sel = doc.getSelection();
|
|
1111
|
+
const caret = sel?.rangeCount && pre.contains(sel.anchorNode) ? textOffset(pre, sel.anchorNode, sel.anchorOffset) : null;
|
|
1112
|
+
const text = (pre.textContent ?? '').replace(//g, '');
|
|
1113
|
+
if (language)
|
|
1114
|
+
pre.setAttribute('data-language', language);
|
|
1115
|
+
else
|
|
1116
|
+
pre.removeAttribute('data-language');
|
|
1117
|
+
const code = doc.createElement('code');
|
|
1118
|
+
if (language && cbCodeSupports(language)) {
|
|
1119
|
+
pre.setAttribute('data-highlight', 'auto');
|
|
1120
|
+
pre.setAttribute('style', `color: ${CODE_FG}; background-color: ${CODE_BG}`);
|
|
1121
|
+
cbHighlight(text, language).forEach((line, i) => {
|
|
1122
|
+
if (i)
|
|
1123
|
+
code.append('\n');
|
|
1124
|
+
for (const token of line) {
|
|
1125
|
+
const color = PALETTE[token.type];
|
|
1126
|
+
if (!color) {
|
|
1127
|
+
code.append(token.text);
|
|
1128
|
+
continue;
|
|
1129
|
+
}
|
|
1130
|
+
const span = doc.createElement('span');
|
|
1131
|
+
span.setAttribute('style', token.type === 'comment' ? `color: ${color}; font-style: italic` : `color: ${color}`);
|
|
1132
|
+
span.textContent = token.text;
|
|
1133
|
+
code.append(span);
|
|
1134
|
+
}
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
else {
|
|
1138
|
+
// Sin colores propios: el bloque vuelve al estilo del editor.
|
|
1139
|
+
if (pre.getAttribute('data-highlight') === 'auto')
|
|
1140
|
+
pre.removeAttribute('style');
|
|
1141
|
+
pre.removeAttribute('data-highlight');
|
|
1142
|
+
code.textContent = text;
|
|
1143
|
+
}
|
|
1144
|
+
pre.replaceChildren(code);
|
|
1145
|
+
if (caret !== null)
|
|
1146
|
+
placeAtOffset(pre, caret, doc);
|
|
1147
|
+
}
|
|
1148
|
+
/** Posición del cursor como desplazamiento en el texto de `root`. */
|
|
1149
|
+
function textOffset(root, node, offset) {
|
|
1150
|
+
const range = root.ownerDocument.createRange();
|
|
1151
|
+
range.selectNodeContents(root);
|
|
1152
|
+
try {
|
|
1153
|
+
range.setEnd(node, offset);
|
|
1154
|
+
}
|
|
1155
|
+
catch {
|
|
1156
|
+
return 0;
|
|
1157
|
+
}
|
|
1158
|
+
return range.toString().length;
|
|
1159
|
+
}
|
|
1160
|
+
/** Pone el cursor a `offset` caracteres del inicio de `root`. */
|
|
1161
|
+
function placeAtOffset(root, offset, doc) {
|
|
1162
|
+
const walker = doc.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
1163
|
+
let left = offset;
|
|
1164
|
+
let node = walker.nextNode();
|
|
1165
|
+
let last = null;
|
|
1166
|
+
while (node) {
|
|
1167
|
+
if (left <= node.length)
|
|
1168
|
+
break;
|
|
1169
|
+
left -= node.length;
|
|
1170
|
+
last = node;
|
|
1171
|
+
node = walker.nextNode();
|
|
1172
|
+
}
|
|
1173
|
+
const range = doc.createRange();
|
|
1174
|
+
if (node)
|
|
1175
|
+
range.setStart(node, left);
|
|
1176
|
+
else if (last)
|
|
1177
|
+
range.setStart(last, last.length);
|
|
1178
|
+
else
|
|
1179
|
+
range.setStart(root, 0);
|
|
1180
|
+
range.collapse(true);
|
|
1181
|
+
const sel = doc.getSelection();
|
|
1182
|
+
sel?.removeAllRanges();
|
|
1183
|
+
sel?.addRange(range);
|
|
1184
|
+
}
|
|
1185
|
+
const span = (cell, attr) => Math.max(1, Number(cell.getAttribute(attr)) || 1);
|
|
1186
|
+
/** Rejilla de la tabla: qué celda ocupa cada posición (contando colspan y rowspan). */
|
|
1187
|
+
function tableGrid(table) {
|
|
1188
|
+
const rows = Array.from(table.rows);
|
|
1189
|
+
const grid = rows.map(() => []);
|
|
1190
|
+
rows.forEach((row, r) => {
|
|
1191
|
+
let c = 0;
|
|
1192
|
+
for (const cell of Array.from(row.cells)) {
|
|
1193
|
+
while (grid[r][c])
|
|
1194
|
+
c++;
|
|
1195
|
+
const cs = span(cell, 'colspan');
|
|
1196
|
+
const rs = Math.min(span(cell, 'rowspan'), rows.length - r);
|
|
1197
|
+
for (let i = 0; i < rs; i++)
|
|
1198
|
+
for (let j = 0; j < cs; j++)
|
|
1199
|
+
grid[r + i][c + j] = cell;
|
|
1200
|
+
c += cs;
|
|
1201
|
+
}
|
|
1202
|
+
});
|
|
1203
|
+
return grid;
|
|
1204
|
+
}
|
|
1205
|
+
function position(grid, cell) {
|
|
1206
|
+
for (let r = 0; r < grid.length; r++) {
|
|
1207
|
+
const c = grid[r].indexOf(cell);
|
|
1208
|
+
if (c >= 0)
|
|
1209
|
+
return { row: r, col: c };
|
|
1210
|
+
}
|
|
1211
|
+
return null;
|
|
1212
|
+
}
|
|
1213
|
+
/** Celda vecina con la que se puede combinar (misma altura a la derecha, mismo ancho abajo). */
|
|
1214
|
+
function mergeTarget(cell, direction) {
|
|
1215
|
+
const table = cell.closest('table');
|
|
1216
|
+
if (!table)
|
|
1217
|
+
return null;
|
|
1218
|
+
const grid = tableGrid(table);
|
|
1219
|
+
const pos = position(grid, cell);
|
|
1220
|
+
if (!pos)
|
|
1221
|
+
return null;
|
|
1222
|
+
const cs = span(cell, 'colspan');
|
|
1223
|
+
const rs = span(cell, 'rowspan');
|
|
1224
|
+
const next = direction === 'right' ? grid[pos.row]?.[pos.col + cs] : grid[pos.row + rs]?.[pos.col];
|
|
1225
|
+
if (!next || next === cell)
|
|
1226
|
+
return null;
|
|
1227
|
+
const nextPos = position(grid, next);
|
|
1228
|
+
// Encabezado y cuerpo no se mezclan.
|
|
1229
|
+
if (next.parentElement?.parentElement !== cell.parentElement?.parentElement)
|
|
1230
|
+
return null;
|
|
1231
|
+
if (direction === 'right')
|
|
1232
|
+
return nextPos.row === pos.row && span(next, 'rowspan') === rs ? next : null;
|
|
1233
|
+
return nextPos.col === pos.col && span(next, 'colspan') === cs ? next : null;
|
|
1234
|
+
}
|
|
1235
|
+
/** Combina la celda con la de la derecha o la de abajo (el contenido se une). */
|
|
1236
|
+
function mergeCells(cell, direction) {
|
|
1237
|
+
const next = mergeTarget(cell, direction);
|
|
1238
|
+
if (!next)
|
|
1239
|
+
return false;
|
|
1240
|
+
const attr = direction === 'right' ? 'colspan' : 'rowspan';
|
|
1241
|
+
cell.setAttribute(attr, String(span(cell, attr) + span(next, attr)));
|
|
1242
|
+
if (next.textContent?.trim() || next.querySelector('img')) {
|
|
1243
|
+
if (cell.textContent?.trim())
|
|
1244
|
+
cell.append(cell.ownerDocument.createElement('br'));
|
|
1245
|
+
else
|
|
1246
|
+
cell.replaceChildren();
|
|
1247
|
+
cell.append(...Array.from(next.childNodes));
|
|
1248
|
+
}
|
|
1249
|
+
next.remove();
|
|
1250
|
+
// Una fila vacía (sus celdas pasaron a la de arriba) se quita y se corrige el rowspan.
|
|
1251
|
+
for (const row of Array.from(cell.closest('table').rows)) {
|
|
1252
|
+
if (row.cells.length)
|
|
1253
|
+
continue;
|
|
1254
|
+
const index = row.rowIndex;
|
|
1255
|
+
const grid = tableGrid(row.closest('table'));
|
|
1256
|
+
for (const above of new Set(grid[index - 1] ?? [])) {
|
|
1257
|
+
if (above && span(above, 'rowspan') > 1)
|
|
1258
|
+
above.setAttribute('rowspan', String(span(above, 'rowspan') - 1));
|
|
1259
|
+
}
|
|
1260
|
+
row.remove();
|
|
1261
|
+
}
|
|
1262
|
+
for (const c of [cell]) {
|
|
1263
|
+
if (span(c, 'colspan') === 1)
|
|
1264
|
+
c.removeAttribute('colspan');
|
|
1265
|
+
if (span(c, 'rowspan') === 1)
|
|
1266
|
+
c.removeAttribute('rowspan');
|
|
1267
|
+
}
|
|
1268
|
+
return true;
|
|
1269
|
+
}
|
|
1270
|
+
/** ¿La celda está combinada? */
|
|
1271
|
+
function isMerged(cell) {
|
|
1272
|
+
return span(cell, 'colspan') > 1 || span(cell, 'rowspan') > 1;
|
|
1273
|
+
}
|
|
1274
|
+
/** Separa una celda combinada en celdas sueltas (el contenido queda en la primera). */
|
|
1275
|
+
function splitCell(cell) {
|
|
1276
|
+
const table = cell.closest('table');
|
|
1277
|
+
if (!table || !isMerged(cell))
|
|
1278
|
+
return false;
|
|
1279
|
+
const grid = tableGrid(table);
|
|
1280
|
+
const pos = position(grid, cell);
|
|
1281
|
+
const cs = span(cell, 'colspan');
|
|
1282
|
+
const rs = span(cell, 'rowspan');
|
|
1283
|
+
const make = () => {
|
|
1284
|
+
const c = cell.ownerDocument.createElement(cell.tagName.toLowerCase());
|
|
1285
|
+
if (cell.tagName === 'TH')
|
|
1286
|
+
c.setAttribute('scope', cell.getAttribute('scope') ?? 'col');
|
|
1287
|
+
const style = cell.getAttribute('style');
|
|
1288
|
+
if (style)
|
|
1289
|
+
c.setAttribute('style', style);
|
|
1290
|
+
c.innerHTML = '<br>';
|
|
1291
|
+
return c;
|
|
1292
|
+
};
|
|
1293
|
+
cell.removeAttribute('colspan');
|
|
1294
|
+
cell.removeAttribute('rowspan');
|
|
1295
|
+
for (let j = cs - 1; j >= 1; j--)
|
|
1296
|
+
cell.after(make());
|
|
1297
|
+
const rows = Array.from(table.rows);
|
|
1298
|
+
for (let i = 1; i < rs; i++) {
|
|
1299
|
+
const row = rows[pos.row + i];
|
|
1300
|
+
if (!row)
|
|
1301
|
+
break;
|
|
1302
|
+
// Se inserta antes de la primera celda propia de la fila que empieza a la derecha.
|
|
1303
|
+
const before = Array.from(row.cells).find((c) => (position(grid, c)?.col ?? 0) > pos.col) ?? null;
|
|
1304
|
+
for (let j = 0; j < cs; j++)
|
|
1305
|
+
row.insertBefore(make(), before);
|
|
1306
|
+
}
|
|
1307
|
+
return true;
|
|
1308
|
+
}
|
|
1309
|
+
// --- Imagen con pie -----------------------------------------------------------------------------
|
|
1310
|
+
/** Figura de la imagen (si tiene pie). */
|
|
1311
|
+
function figureOf(img) {
|
|
1312
|
+
return img.parentElement?.tagName === 'FIGURE' ? img.parentElement : null;
|
|
1313
|
+
}
|
|
1314
|
+
/**
|
|
1315
|
+
* Añade un pie a la imagen: la envuelve en `<figure>` (que se lleva su ancho y
|
|
1316
|
+
* alineación) con un `<figcaption>` vacío. Devuelve el pie.
|
|
1317
|
+
*/
|
|
1318
|
+
function addCaption(img, doc) {
|
|
1319
|
+
const existing = figureOf(img)?.querySelector('figcaption');
|
|
1320
|
+
if (existing)
|
|
1321
|
+
return existing;
|
|
1322
|
+
const { width, align } = readImageStyle(img.getAttribute('style') ?? '');
|
|
1323
|
+
const figure = doc.createElement('figure');
|
|
1324
|
+
const css = imageStyle(width ?? '100%', align);
|
|
1325
|
+
figure.setAttribute('style', css);
|
|
1326
|
+
// La imagen ocupa toda la figura.
|
|
1327
|
+
img.setAttribute('style', imageStyle('100%', null));
|
|
1328
|
+
img.removeAttribute('width');
|
|
1329
|
+
img.removeAttribute('height');
|
|
1330
|
+
const caption = doc.createElement('figcaption');
|
|
1331
|
+
caption.innerHTML = '<br>';
|
|
1332
|
+
// La figura es un bloque: el párrafo de la imagen se parte en dos y la figura queda en medio.
|
|
1333
|
+
const block = img.parentElement?.tagName === 'P' ? img.parentElement : null;
|
|
1334
|
+
if (block) {
|
|
1335
|
+
const after = doc.createElement('p');
|
|
1336
|
+
while (img.nextSibling)
|
|
1337
|
+
after.append(img.nextSibling);
|
|
1338
|
+
block.after(figure);
|
|
1339
|
+
if (after.textContent?.trim() || after.querySelector('img'))
|
|
1340
|
+
figure.after(after);
|
|
1341
|
+
img.remove();
|
|
1342
|
+
if (!block.textContent?.trim() && !block.querySelector('img'))
|
|
1343
|
+
block.remove();
|
|
1344
|
+
}
|
|
1345
|
+
else {
|
|
1346
|
+
img.before(figure);
|
|
1347
|
+
}
|
|
1348
|
+
figure.append(img, caption);
|
|
1349
|
+
return caption;
|
|
1350
|
+
}
|
|
1351
|
+
/** Quita el pie: la imagen recupera el ancho y la alineación de la figura. */
|
|
1352
|
+
function removeCaption(img, doc) {
|
|
1353
|
+
const figure = figureOf(img);
|
|
1354
|
+
if (!figure)
|
|
1355
|
+
return;
|
|
1356
|
+
const { width, align } = readImageStyle(figure.getAttribute('style') ?? '');
|
|
1357
|
+
const css = imageStyle(width === '100%' ? null : width, align);
|
|
1358
|
+
if (css)
|
|
1359
|
+
img.setAttribute('style', css);
|
|
1360
|
+
else
|
|
1361
|
+
img.removeAttribute('style');
|
|
1362
|
+
const p = doc.createElement('p');
|
|
1363
|
+
p.append(img);
|
|
1364
|
+
figure.replaceWith(p);
|
|
1365
|
+
}
|
|
1366
|
+
// --- Índice del documento -----------------------------------------------------------------------
|
|
1367
|
+
/** Identificador legible para un título ("Condiciones del crédito" → "condiciones-del-credito"). */
|
|
1368
|
+
function slugify(text) {
|
|
1369
|
+
const slug = text
|
|
1370
|
+
.normalize('NFD')
|
|
1371
|
+
.replace(/[̀-ͯ]/g, '')
|
|
1372
|
+
.toLowerCase()
|
|
1373
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
1374
|
+
// Sin la numeración inicial ("1. Resumen" → "resumen").
|
|
1375
|
+
.replace(/^[^a-z]+|-+$/g, '')
|
|
1376
|
+
.slice(0, 60);
|
|
1377
|
+
return slug || 'seccion';
|
|
1378
|
+
}
|
|
1379
|
+
/** Da un `id` único a cada título (los que ya tienen uno válido lo conservan). */
|
|
1380
|
+
function ensureHeadingIds(root) {
|
|
1381
|
+
const headings = Array.from(root.querySelectorAll('h1, h2, h3, h4')).filter((h) => !h.closest('nav[data-type="toc"]'));
|
|
1382
|
+
const used = new Set();
|
|
1383
|
+
for (const h of headings) {
|
|
1384
|
+
let id = /^[a-z][a-z0-9-]{0,79}$/.test(h.id) && !used.has(h.id) ? h.id : '';
|
|
1385
|
+
if (!id) {
|
|
1386
|
+
const base = slugify(h.textContent ?? '');
|
|
1387
|
+
id = base;
|
|
1388
|
+
for (let n = 2; used.has(id); n++)
|
|
1389
|
+
id = `${base}-${n}`;
|
|
1390
|
+
h.id = id;
|
|
1391
|
+
}
|
|
1392
|
+
used.add(id);
|
|
1393
|
+
}
|
|
1394
|
+
return headings;
|
|
1395
|
+
}
|
|
1396
|
+
/** Contenido del índice: enlaces a los títulos, anidados por nivel. */
|
|
1397
|
+
function buildToc(nav, headings, title, doc) {
|
|
1398
|
+
const heading = doc.createElement('p');
|
|
1399
|
+
const strong = doc.createElement('strong');
|
|
1400
|
+
strong.textContent = title;
|
|
1401
|
+
heading.append(strong);
|
|
1402
|
+
const root = doc.createElement('ol');
|
|
1403
|
+
// Pila de listas abiertas: un nivel más profundo abre una sublista en el último elemento.
|
|
1404
|
+
const stack = [{ level: 0, list: root, last: null }];
|
|
1405
|
+
for (const h of headings) {
|
|
1406
|
+
const text = (h.textContent ?? '').trim();
|
|
1407
|
+
if (!text)
|
|
1408
|
+
continue;
|
|
1409
|
+
const level = Number(h.tagName[1]);
|
|
1410
|
+
while (stack.length > 1 && stack[stack.length - 1].level > level)
|
|
1411
|
+
stack.pop();
|
|
1412
|
+
let top = stack[stack.length - 1];
|
|
1413
|
+
if (!top.last)
|
|
1414
|
+
top.level = level;
|
|
1415
|
+
else if (level > top.level) {
|
|
1416
|
+
const sub = doc.createElement('ol');
|
|
1417
|
+
top.last.append(sub);
|
|
1418
|
+
top = { level, list: sub, last: null };
|
|
1419
|
+
stack.push(top);
|
|
1420
|
+
}
|
|
1421
|
+
const li = doc.createElement('li');
|
|
1422
|
+
const a = doc.createElement('a');
|
|
1423
|
+
a.href = `#${h.id}`;
|
|
1424
|
+
a.textContent = text;
|
|
1425
|
+
li.append(a);
|
|
1426
|
+
top.list.append(li);
|
|
1427
|
+
top.last = li;
|
|
1428
|
+
}
|
|
1429
|
+
nav.replaceChildren(heading, ...(root.children.length ? [root] : []));
|
|
1430
|
+
}
|
|
1431
|
+
/** Firma de los títulos (para no reescribir el índice si no cambiaron). */
|
|
1432
|
+
function headingsSignature(headings) {
|
|
1433
|
+
return headings.map((h) => `${h.tagName}:${h.id}:${(h.textContent ?? '').trim()}`).join('|');
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1108
1436
|
/**
|
|
1109
1437
|
* Barra por defecto, en dos filas: arriba el texto (bloque, fuente, tamaño, estilos y color);
|
|
1110
1438
|
* abajo la estructura (listas, alineación, inserciones, buscar y vistas).
|
|
@@ -1117,7 +1445,7 @@ const CB_EDITOR_DEFAULT_TOOLBAR = [
|
|
|
1117
1445
|
'subscript', 'superscript', 'code', 'clear',
|
|
1118
1446
|
'-',
|
|
1119
1447
|
'bulletList', 'orderedList', 'taskList', 'outdent', 'indent', 'alignment', '|',
|
|
1120
|
-
'link', 'image', 'table', 'codeBlock', 'quote', 'horizontalRule', 'specialChars', '|',
|
|
1448
|
+
'link', 'image', 'table', 'codeBlock', 'quote', 'details', 'toc', 'horizontalRule', 'specialChars', '|',
|
|
1121
1449
|
'findReplace', '|',
|
|
1122
1450
|
'source', 'fullscreen',
|
|
1123
1451
|
];
|
|
@@ -1138,6 +1466,8 @@ const SLASH_ITEMS = [
|
|
|
1138
1466
|
{ id: 'table', icon: 'table', label: (t) => t.insertTable, keywords: 'tabla table' },
|
|
1139
1467
|
{ id: 'image', icon: 'image', label: (t) => t.insertImage, keywords: 'imagen image foto picture' },
|
|
1140
1468
|
{ id: 'horizontalRule', icon: 'minus', label: (t) => t.horizontalLine, keywords: 'hr linea line separador divider' },
|
|
1469
|
+
{ id: 'details', icon: 'list-collapse', label: (t) => t.editorCollapsible, keywords: 'desplegable details acordeon collapsible toggle' },
|
|
1470
|
+
{ id: 'toc', icon: 'table-of-contents', label: (t) => t.editorToc, keywords: 'indice toc contenido contents' },
|
|
1141
1471
|
];
|
|
1142
1472
|
const EMPTY_STATE = {
|
|
1143
1473
|
bold: false,
|
|
@@ -1194,6 +1524,8 @@ const TOOLS = {
|
|
|
1194
1524
|
fullscreen: { icon: 'maximize', label: (t) => t.fullscreen },
|
|
1195
1525
|
specialChars: { icon: 'omega', label: (t) => t.specialCharacters },
|
|
1196
1526
|
findReplace: { icon: 'text-search', label: (t) => t.findReplace, keys: 'Ctrl+F' },
|
|
1527
|
+
details: { icon: 'list-collapse', label: (t) => t.editorCollapsible },
|
|
1528
|
+
toc: { icon: 'table-of-contents', label: (t) => t.editorToc },
|
|
1197
1529
|
};
|
|
1198
1530
|
/** Opciones del menú de alineación. */
|
|
1199
1531
|
const ALIGNMENTS = ['alignLeft', 'alignCenter', 'alignRight', 'alignJustify'];
|
|
@@ -1256,6 +1588,12 @@ const EDITOR_ICONS = {
|
|
|
1256
1588
|
'heading-1': heading1,
|
|
1257
1589
|
'heading-2': heading2,
|
|
1258
1590
|
'heading-3': heading3,
|
|
1591
|
+
'list-collapse': listCollapse,
|
|
1592
|
+
'table-of-contents': tableOfContents,
|
|
1593
|
+
'table-cells-merge': tableCellsMerge,
|
|
1594
|
+
'table-cells-split': tableCellsSplit,
|
|
1595
|
+
'at-sign': atSign,
|
|
1596
|
+
copy,
|
|
1259
1597
|
};
|
|
1260
1598
|
/** Nombres de los resaltados de búsqueda (CSS Custom Highlight API). */
|
|
1261
1599
|
const FIND_HIGHLIGHT = 'cb-editor-find';
|
|
@@ -1342,9 +1680,25 @@ class CbEditor {
|
|
|
1342
1680
|
this.autolink = input(true, { ...(ngDevMode ? { debugName: "autolink" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1343
1681
|
/** Corrección tipográfica al escribir: `(c)` → ©, `...` → …, `->` → →, ` -- ` → –, `1/2` → ½. */
|
|
1344
1682
|
this.typography = input(true, { ...(ngDevMode ? { debugName: "typography" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1683
|
+
/**
|
|
1684
|
+
* Menciones con `@`: una lista fija o una función que busca (síncrona o con
|
|
1685
|
+
* promesa). Se guardan como `<span data-mention="id">@Nombre</span>`.
|
|
1686
|
+
*/
|
|
1687
|
+
this.mentions = input(null, /* @ts-ignore */
|
|
1688
|
+
...(ngDevMode ? [{ debugName: "mentions" }] : /* istanbul ignore next */ []));
|
|
1689
|
+
/**
|
|
1690
|
+
* Guarda un borrador en este navegador (`localStorage`) mientras se escribe;
|
|
1691
|
+
* al volver, ofrece restaurarlo. Usa una clave por documento (p. ej. `'acta-42'`).
|
|
1692
|
+
*/
|
|
1693
|
+
this.autosaveKey = input('', /* @ts-ignore */
|
|
1694
|
+
...(ngDevMode ? [{ debugName: "autosaveKey" }] : /* istanbul ignore next */ []));
|
|
1695
|
+
/** Espera (ms) tras la última tecla antes de guardar el borrador. */
|
|
1696
|
+
this.autosaveDelay = input(1000, { ...(ngDevMode ? { debugName: "autosaveDelay" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
1345
1697
|
/** HTML del contenido (saneado y normalizado; `''` si está vacío). */
|
|
1346
1698
|
this.value = model('', /* @ts-ignore */
|
|
1347
1699
|
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
1700
|
+
/** Se insertó una mención. */
|
|
1701
|
+
this.mentionAdd = output();
|
|
1348
1702
|
/** Una imagen no se pudo insertar (tipo, tamaño o error de subida). */
|
|
1349
1703
|
this.imageError = output();
|
|
1350
1704
|
/** Estado de validación: inputs manuales + estado de Angular Forms. */
|
|
@@ -1545,7 +1899,9 @@ class CbEditor {
|
|
|
1545
1899
|
...(ngDevMode ? [{ debugName: "imageTick" }] : /* istanbul ignore next */ []));
|
|
1546
1900
|
this.imageState = computed(() => {
|
|
1547
1901
|
this.imageTick();
|
|
1548
|
-
|
|
1902
|
+
const img = this.selectedImage();
|
|
1903
|
+
const holder = img ? (figureOf(img) ?? img) : null;
|
|
1904
|
+
return readImageStyle(holder?.getAttribute('style') ?? '');
|
|
1549
1905
|
}, /* @ts-ignore */
|
|
1550
1906
|
...(ngDevMode ? [{ debugName: "imageState" }] : /* istanbul ignore next */ []));
|
|
1551
1907
|
this.imageFrame = signal(null, /* @ts-ignore */
|
|
@@ -1560,6 +1916,50 @@ class CbEditor {
|
|
|
1560
1916
|
{ align: 'center', icon: 'align-center-horizontal', label: (t) => t.imageAlignCenter },
|
|
1561
1917
|
{ align: 'right', icon: 'align-end-vertical', label: (t) => t.imageAlignRight },
|
|
1562
1918
|
];
|
|
1919
|
+
/** Bloque de código donde está el cursor (muestra su barra). */
|
|
1920
|
+
this.codeBlock = signal(null, /* @ts-ignore */
|
|
1921
|
+
...(ngDevMode ? [{ debugName: "codeBlock" }] : /* istanbul ignore next */ []));
|
|
1922
|
+
this.codeTick = signal(0, /* @ts-ignore */
|
|
1923
|
+
...(ngDevMode ? [{ debugName: "codeTick" }] : /* istanbul ignore next */ []));
|
|
1924
|
+
this.codeLanguage = computed(() => {
|
|
1925
|
+
this.codeTick();
|
|
1926
|
+
return this.codeBlock()?.getAttribute('data-language') ?? '';
|
|
1927
|
+
}, /* @ts-ignore */
|
|
1928
|
+
...(ngDevMode ? [{ debugName: "codeLanguage" }] : /* istanbul ignore next */ []));
|
|
1929
|
+
this.codeLanguages = CODE_LANGUAGES;
|
|
1930
|
+
this.codeCopied = signal(false, /* @ts-ignore */
|
|
1931
|
+
...(ngDevMode ? [{ debugName: "codeCopied" }] : /* istanbul ignore next */ []));
|
|
1932
|
+
this.languageLabel = codeLanguageLabel;
|
|
1933
|
+
this.fmt = cbFormat;
|
|
1934
|
+
/** Qué se puede hacer con la celda actual: combinar a la derecha / abajo, separar. */
|
|
1935
|
+
this.cellActions = computed(() => {
|
|
1936
|
+
this.tableTick();
|
|
1937
|
+
const cell = this.tableCell();
|
|
1938
|
+
return { right: !!cell && !!mergeTarget(cell, 'right'), down: !!cell && !!mergeTarget(cell, 'down'), split: !!cell && isMerged(cell) };
|
|
1939
|
+
}, /* @ts-ignore */
|
|
1940
|
+
...(ngDevMode ? [{ debugName: "cellActions" }] : /* istanbul ignore next */ []));
|
|
1941
|
+
this.imageHasCaption = computed(() => {
|
|
1942
|
+
this.imageTick();
|
|
1943
|
+
const img = this.selectedImage();
|
|
1944
|
+
return !!img && !!figureOf(img);
|
|
1945
|
+
}, /* @ts-ignore */
|
|
1946
|
+
...(ngDevMode ? [{ debugName: "imageHasCaption" }] : /* istanbul ignore next */ []));
|
|
1947
|
+
/** Menú de menciones: texto tras la `@`, opción activa y posición. */
|
|
1948
|
+
this.mention = signal(null, /* @ts-ignore */
|
|
1949
|
+
...(ngDevMode ? [{ debugName: "mention" }] : /* istanbul ignore next */ []));
|
|
1950
|
+
this.mentionResults = signal([], /* @ts-ignore */
|
|
1951
|
+
...(ngDevMode ? [{ debugName: "mentionResults" }] : /* istanbul ignore next */ []));
|
|
1952
|
+
this.mentionAnchor = null;
|
|
1953
|
+
this.mentionToken = 0;
|
|
1954
|
+
this.mentionListId = `${this.surfaceId}-mentions`;
|
|
1955
|
+
/** Índice: firma de los títulos con la que se generó (no se reescribe si no cambian). */
|
|
1956
|
+
this.tocSignature = '';
|
|
1957
|
+
/** Borrador guardado en este navegador que difiere del valor actual. */
|
|
1958
|
+
this.draft = signal(null, /* @ts-ignore */
|
|
1959
|
+
...(ngDevMode ? [{ debugName: "draft" }] : /* istanbul ignore next */ []));
|
|
1960
|
+
/** Hora del último borrador guardado (se muestra al pie). */
|
|
1961
|
+
this.savedAt = signal(null, /* @ts-ignore */
|
|
1962
|
+
...(ngDevMode ? [{ debugName: "savedAt" }] : /* istanbul ignore next */ []));
|
|
1563
1963
|
/** Último HTML emitido o cargado: evita reescribir el DOM (y mover el cursor) con su propio valor. */
|
|
1564
1964
|
this.lastHtml = null;
|
|
1565
1965
|
this.onChange = () => { };
|
|
@@ -1585,6 +1985,11 @@ class CbEditor {
|
|
|
1585
1985
|
return;
|
|
1586
1986
|
const clean = untracked(() => this.sanitize(val));
|
|
1587
1987
|
el.innerHTML = clean;
|
|
1988
|
+
decorate(el);
|
|
1989
|
+
untracked(() => {
|
|
1990
|
+
this.tocSignature = '';
|
|
1991
|
+
this.refreshToc(el);
|
|
1992
|
+
});
|
|
1588
1993
|
this.lastHtml = val;
|
|
1589
1994
|
untracked(() => this.length.set(this.textLength(el)));
|
|
1590
1995
|
});
|
|
@@ -1603,7 +2008,13 @@ class CbEditor {
|
|
|
1603
2008
|
};
|
|
1604
2009
|
this.document.addEventListener('mousedown', onPointer);
|
|
1605
2010
|
this.document.addEventListener('mouseup', onPointerUp);
|
|
2011
|
+
// Borrador guardado de una sesión anterior.
|
|
2012
|
+
afterNextRender(() => this.loadDraft());
|
|
1606
2013
|
inject(DestroyRef).onDestroy(() => {
|
|
2014
|
+
if (this.draftTimer) {
|
|
2015
|
+
clearTimeout(this.draftTimer);
|
|
2016
|
+
this.saveDraft();
|
|
2017
|
+
}
|
|
1607
2018
|
this.document.removeEventListener('mousedown', onPointer);
|
|
1608
2019
|
this.document.removeEventListener('mouseup', onPointerUp);
|
|
1609
2020
|
this.clearFindHighlight();
|
|
@@ -1817,6 +2228,8 @@ class CbEditor {
|
|
|
1817
2228
|
case 'redo': return this.exec('redo');
|
|
1818
2229
|
case 'source': return this.toggleSource();
|
|
1819
2230
|
case 'findReplace': return this.openFind();
|
|
2231
|
+
case 'details': return this.insertDetails();
|
|
2232
|
+
case 'toc': return this.insertToc();
|
|
1820
2233
|
}
|
|
1821
2234
|
}
|
|
1822
2235
|
// --- Comandos ----------------------------------------------------------------------
|
|
@@ -2025,6 +2438,11 @@ class CbEditor {
|
|
|
2025
2438
|
this.exec('insertHTML', `<a href="${escapeAttr(url)}"${target}>${escapeHtml(url)}</a>`);
|
|
2026
2439
|
}
|
|
2027
2440
|
else {
|
|
2441
|
+
// El doble clic en Windows selecciona también el espacio siguiente: se deja fuera del enlace
|
|
2442
|
+
// (insertHTML lo descartaría y las palabras quedarían pegadas).
|
|
2443
|
+
trimRangeWhitespace(range);
|
|
2444
|
+
sel.removeAllRanges();
|
|
2445
|
+
sel.addRange(range);
|
|
2028
2446
|
this.exec('insertHTML', `<a href="${escapeAttr(url)}"${target}>${escapeHtml(range.toString())}</a>`);
|
|
2029
2447
|
}
|
|
2030
2448
|
}
|
|
@@ -2180,9 +2598,13 @@ class CbEditor {
|
|
|
2180
2598
|
this.fixTypography();
|
|
2181
2599
|
if (event.data === '/')
|
|
2182
2600
|
this.openSlash();
|
|
2601
|
+
if (event.data === '@')
|
|
2602
|
+
this.openMention();
|
|
2183
2603
|
}
|
|
2184
2604
|
if (this.slash())
|
|
2185
2605
|
this.updateSlash();
|
|
2606
|
+
if (this.mention())
|
|
2607
|
+
this.updateMention();
|
|
2186
2608
|
this.emit();
|
|
2187
2609
|
if (this.findOpen())
|
|
2188
2610
|
this.refreshFind(this.findIndex());
|
|
@@ -2304,6 +2726,10 @@ class CbEditor {
|
|
|
2304
2726
|
return this.openTablePanel();
|
|
2305
2727
|
case 'image':
|
|
2306
2728
|
return this.pickImage();
|
|
2729
|
+
case 'details':
|
|
2730
|
+
return this.insertDetails();
|
|
2731
|
+
case 'toc':
|
|
2732
|
+
return this.insertToc();
|
|
2307
2733
|
}
|
|
2308
2734
|
}
|
|
2309
2735
|
/** Flechas, Enter / Tab y Escape mientras el menú `/` está abierto. */
|
|
@@ -2510,6 +2936,12 @@ class CbEditor {
|
|
|
2510
2936
|
onSurfaceKeydown(event) {
|
|
2511
2937
|
if (this.slash() && this.onSlashKeydown(event))
|
|
2512
2938
|
return;
|
|
2939
|
+
if (this.mention() && this.onMentionKeydown(event))
|
|
2940
|
+
return;
|
|
2941
|
+
if (event.key === 'Enter' && !event.shiftKey && !event.ctrlKey && !event.metaKey && this.exitInlineBlock()) {
|
|
2942
|
+
event.preventDefault();
|
|
2943
|
+
return;
|
|
2944
|
+
}
|
|
2513
2945
|
// Alt + F10: a la barra de herramientas (como en los editores de escritorio).
|
|
2514
2946
|
if (event.altKey && event.key === 'F10') {
|
|
2515
2947
|
event.preventDefault();
|
|
@@ -2790,6 +3222,15 @@ class CbEditor {
|
|
|
2790
3222
|
break;
|
|
2791
3223
|
case 'deleteTable':
|
|
2792
3224
|
return this.deleteTable(table);
|
|
3225
|
+
case 'mergeRight':
|
|
3226
|
+
case 'mergeDown':
|
|
3227
|
+
if (!mergeCells(cell, id === 'mergeRight' ? 'right' : 'down'))
|
|
3228
|
+
return;
|
|
3229
|
+
break;
|
|
3230
|
+
case 'split':
|
|
3231
|
+
if (!splitCell(cell))
|
|
3232
|
+
return;
|
|
3233
|
+
break;
|
|
2793
3234
|
}
|
|
2794
3235
|
this.tableTick.update((n) => n + 1);
|
|
2795
3236
|
this.emit();
|
|
@@ -2833,6 +3274,28 @@ class CbEditor {
|
|
|
2833
3274
|
// --- Imagen seleccionada ----------------------------------------------------------------
|
|
2834
3275
|
onSurfaceClick(event) {
|
|
2835
3276
|
const target = event.target;
|
|
3277
|
+
// Enlaces del índice: llevan al título dentro del editor.
|
|
3278
|
+
const tocLink = target.closest?.('nav[data-type="toc"] a');
|
|
3279
|
+
if (tocLink) {
|
|
3280
|
+
event.preventDefault();
|
|
3281
|
+
const id = decodeURIComponent(tocLink.getAttribute('href')?.slice(1) ?? '');
|
|
3282
|
+
const heading = id ? this.surfaceRef()?.nativeElement.querySelector(`[id="${id.replace(/"/g, '')}"]`) : null;
|
|
3283
|
+
heading?.scrollIntoView?.({ block: 'start', behavior: 'smooth' });
|
|
3284
|
+
if (heading)
|
|
3285
|
+
this.placeCaret(heading);
|
|
3286
|
+
return;
|
|
3287
|
+
}
|
|
3288
|
+
// El título del desplegable se edita: solo la flecha (a la izquierda) lo abre o cierra.
|
|
3289
|
+
const summary = target.closest?.('summary');
|
|
3290
|
+
if (summary && !this.readonly() && !this.isDisabled()) {
|
|
3291
|
+
event.preventDefault();
|
|
3292
|
+
if (event.clientX - summary.getBoundingClientRect().left < 22) {
|
|
3293
|
+
const details = summary.parentElement;
|
|
3294
|
+
details.open = !details.open;
|
|
3295
|
+
this.emit();
|
|
3296
|
+
}
|
|
3297
|
+
return;
|
|
3298
|
+
}
|
|
2836
3299
|
// Clic en la casilla de una tarea (la zona a la izquierda del texto).
|
|
2837
3300
|
if (target.tagName === 'LI' && target.parentElement?.getAttribute('data-type') === 'taskList' && !this.readonly() && !this.isDisabled()) {
|
|
2838
3301
|
const box = target.getBoundingClientRect();
|
|
@@ -2876,11 +3339,20 @@ class CbEditor {
|
|
|
2876
3339
|
const img = this.selectedImage();
|
|
2877
3340
|
if (!img)
|
|
2878
3341
|
return;
|
|
3342
|
+
const figure = figureOf(img);
|
|
2879
3343
|
const css = imageStyle(width, align);
|
|
3344
|
+
// Con pie, el ancho y la alineación son de la figura; la imagen la llena.
|
|
3345
|
+
const target = figure ?? img;
|
|
2880
3346
|
if (css)
|
|
2881
|
-
|
|
3347
|
+
target.setAttribute('style', css);
|
|
2882
3348
|
else
|
|
2883
|
-
|
|
3349
|
+
target.removeAttribute('style');
|
|
3350
|
+
if (figure) {
|
|
3351
|
+
if (width)
|
|
3352
|
+
img.setAttribute('style', imageStyle('100%', null));
|
|
3353
|
+
else
|
|
3354
|
+
img.removeAttribute('style');
|
|
3355
|
+
}
|
|
2884
3356
|
if (width) {
|
|
2885
3357
|
img.removeAttribute('width');
|
|
2886
3358
|
img.removeAttribute('height');
|
|
@@ -2901,8 +3373,9 @@ class CbEditor {
|
|
|
2901
3373
|
const img = this.selectedImage();
|
|
2902
3374
|
if (!img)
|
|
2903
3375
|
return;
|
|
2904
|
-
const
|
|
2905
|
-
img.
|
|
3376
|
+
const figure = figureOf(img);
|
|
3377
|
+
const after = figure ? figure.nextElementSibling ?? figure.parentElement : img.parentElement;
|
|
3378
|
+
(figure ?? img).remove();
|
|
2906
3379
|
this.deselectImage();
|
|
2907
3380
|
this.emit();
|
|
2908
3381
|
if (after)
|
|
@@ -3022,14 +3495,18 @@ class CbEditor {
|
|
|
3022
3495
|
if (!img || !surface)
|
|
3023
3496
|
return;
|
|
3024
3497
|
event.preventDefault();
|
|
3025
|
-
|
|
3498
|
+
// Con pie se redimensiona la figura (la imagen la llena).
|
|
3499
|
+
const target = figureOf(img) ?? img;
|
|
3500
|
+
this.resizing = { x: event.clientX, width: target.getBoundingClientRect().width, max: surface.clientWidth || 1, align: this.imageState().align };
|
|
3501
|
+
if (target !== img)
|
|
3502
|
+
img.setAttribute('style', imageStyle('100%', null));
|
|
3026
3503
|
const move = (e) => {
|
|
3027
3504
|
const r = this.resizing;
|
|
3028
3505
|
if (!r)
|
|
3029
3506
|
return;
|
|
3030
3507
|
const px = Math.min(r.max, Math.max(40, r.width + (e.clientX - r.x)));
|
|
3031
3508
|
const css = imageStyle(`${Math.round((px / r.max) * 100)}%`, r.align);
|
|
3032
|
-
|
|
3509
|
+
target.setAttribute('style', css);
|
|
3033
3510
|
this.refreshFrame();
|
|
3034
3511
|
};
|
|
3035
3512
|
const up = () => {
|
|
@@ -3066,6 +3543,357 @@ class CbEditor {
|
|
|
3066
3543
|
buttons.forEach((b, i) => (b.tabIndex = i === next ? 0 : -1));
|
|
3067
3544
|
buttons[next].focus();
|
|
3068
3545
|
}
|
|
3546
|
+
// --- Bloque de código: lenguaje y copiar --------------------------------------------------
|
|
3547
|
+
/** Lenguaje del bloque de código actual; si el lenguaje se conoce, lo colorea. */
|
|
3548
|
+
setCodeLanguage(language) {
|
|
3549
|
+
const pre = this.codeBlock();
|
|
3550
|
+
if (!pre)
|
|
3551
|
+
return;
|
|
3552
|
+
highlightCodeBlock(pre, language, this.document);
|
|
3553
|
+
this.codeTick.update((n) => n + 1);
|
|
3554
|
+
this.emit();
|
|
3555
|
+
}
|
|
3556
|
+
async copyCode() {
|
|
3557
|
+
const text = this.codeBlock()?.textContent ?? '';
|
|
3558
|
+
try {
|
|
3559
|
+
await globalThis.navigator?.clipboard?.writeText(text);
|
|
3560
|
+
this.codeCopied.set(true);
|
|
3561
|
+
}
|
|
3562
|
+
catch {
|
|
3563
|
+
this.codeCopied.set(false);
|
|
3564
|
+
}
|
|
3565
|
+
}
|
|
3566
|
+
// --- Pie de imagen ---------------------------------------------------------------------------
|
|
3567
|
+
/** Añade o quita el pie de la imagen seleccionada. */
|
|
3568
|
+
toggleCaption() {
|
|
3569
|
+
const img = this.selectedImage();
|
|
3570
|
+
if (!img)
|
|
3571
|
+
return;
|
|
3572
|
+
if (figureOf(img)) {
|
|
3573
|
+
removeCaption(img, this.document);
|
|
3574
|
+
this.emit();
|
|
3575
|
+
}
|
|
3576
|
+
else {
|
|
3577
|
+
const caption = addCaption(img, this.document);
|
|
3578
|
+
caption.setAttribute('data-placeholder', this.t().editorImageCaption);
|
|
3579
|
+
this.emit();
|
|
3580
|
+
this.deselectImage();
|
|
3581
|
+
this.placeCaret(caption);
|
|
3582
|
+
}
|
|
3583
|
+
this.imageTick.update((n) => n + 1);
|
|
3584
|
+
afterNextRender(() => this.refreshFrame(), { injector: this.injector });
|
|
3585
|
+
}
|
|
3586
|
+
/** Enter en un pie de imagen o en el título de un desplegable: sigue en el bloque siguiente. */
|
|
3587
|
+
exitInlineBlock() {
|
|
3588
|
+
const sel = this.document.getSelection();
|
|
3589
|
+
const node = sel?.anchorNode ?? null;
|
|
3590
|
+
const caption = this.closest(node, 'FIGCAPTION');
|
|
3591
|
+
const summary = this.closest(node, 'SUMMARY');
|
|
3592
|
+
if (!caption && !summary)
|
|
3593
|
+
return false;
|
|
3594
|
+
if (summary) {
|
|
3595
|
+
const details = summary.parentElement;
|
|
3596
|
+
details.open = true;
|
|
3597
|
+
let first = summary.nextElementSibling;
|
|
3598
|
+
if (!first) {
|
|
3599
|
+
first = this.document.createElement('p');
|
|
3600
|
+
first.innerHTML = '<br>';
|
|
3601
|
+
details.append(first);
|
|
3602
|
+
}
|
|
3603
|
+
this.placeCaret(first);
|
|
3604
|
+
}
|
|
3605
|
+
else {
|
|
3606
|
+
const figure = caption.parentElement;
|
|
3607
|
+
let next = figure.nextElementSibling;
|
|
3608
|
+
if (!next || next.tagName !== 'P') {
|
|
3609
|
+
next = this.document.createElement('p');
|
|
3610
|
+
next.innerHTML = '<br>';
|
|
3611
|
+
figure.after(next);
|
|
3612
|
+
}
|
|
3613
|
+
this.placeCaret(next);
|
|
3614
|
+
}
|
|
3615
|
+
this.emit();
|
|
3616
|
+
this.updateState();
|
|
3617
|
+
return true;
|
|
3618
|
+
}
|
|
3619
|
+
// --- Desplegables e índice ---------------------------------------------------------------------
|
|
3620
|
+
/** Inserta un desplegable (`<details>`) con su título seleccionado para escribirlo. */
|
|
3621
|
+
insertDetails() {
|
|
3622
|
+
const details = this.document.createElement('details');
|
|
3623
|
+
details.open = true;
|
|
3624
|
+
const summary = this.document.createElement('summary');
|
|
3625
|
+
summary.textContent = this.t().editorCollapsibleTitle;
|
|
3626
|
+
const body = this.document.createElement('p');
|
|
3627
|
+
body.innerHTML = '<br>';
|
|
3628
|
+
details.append(summary, body);
|
|
3629
|
+
if (!this.insertBlock(details))
|
|
3630
|
+
return;
|
|
3631
|
+
const range = this.document.createRange();
|
|
3632
|
+
range.selectNodeContents(summary);
|
|
3633
|
+
const sel = this.document.getSelection();
|
|
3634
|
+
sel?.removeAllRanges();
|
|
3635
|
+
sel?.addRange(range);
|
|
3636
|
+
this.emit();
|
|
3637
|
+
this.updateState();
|
|
3638
|
+
}
|
|
3639
|
+
/** Inserta el índice del documento: enlaces a los títulos, que se actualizan solos. */
|
|
3640
|
+
insertToc() {
|
|
3641
|
+
const nav = this.document.createElement('nav');
|
|
3642
|
+
nav.setAttribute('data-type', 'toc');
|
|
3643
|
+
nav.setAttribute('contenteditable', 'false');
|
|
3644
|
+
if (!this.insertBlock(nav))
|
|
3645
|
+
return;
|
|
3646
|
+
this.tocSignature = '';
|
|
3647
|
+
const next = nav.nextElementSibling;
|
|
3648
|
+
if (next)
|
|
3649
|
+
this.placeCaret(next);
|
|
3650
|
+
this.emit();
|
|
3651
|
+
}
|
|
3652
|
+
/**
|
|
3653
|
+
* Inserta un bloque tras el bloque del cursor (o en su lugar, si es un párrafo
|
|
3654
|
+
* vacío), con un párrafo detrás para seguir escribiendo. `insertHTML` del
|
|
3655
|
+
* navegador parte los párrafos y descoloca estos bloques.
|
|
3656
|
+
*/
|
|
3657
|
+
insertBlock(block) {
|
|
3658
|
+
const surface = this.surfaceRef()?.nativeElement;
|
|
3659
|
+
if (!surface || this.isDisabled() || this.readonly())
|
|
3660
|
+
return false;
|
|
3661
|
+
const sel = this.document.getSelection();
|
|
3662
|
+
let top = sel?.rangeCount && surface.contains(sel.anchorNode) ? sel.anchorNode : null;
|
|
3663
|
+
while (top && top.parentNode !== surface)
|
|
3664
|
+
top = top.parentNode;
|
|
3665
|
+
const current = top;
|
|
3666
|
+
const empty = current?.tagName === 'P' && !current.textContent?.trim() && !current.querySelector('img');
|
|
3667
|
+
if (!current)
|
|
3668
|
+
surface.append(block);
|
|
3669
|
+
else if (empty)
|
|
3670
|
+
current.replaceWith(block);
|
|
3671
|
+
else
|
|
3672
|
+
current.after(block);
|
|
3673
|
+
const next = block.nextElementSibling;
|
|
3674
|
+
if (!next || next.tagName !== 'P') {
|
|
3675
|
+
const p = this.document.createElement('p');
|
|
3676
|
+
p.innerHTML = '<br>';
|
|
3677
|
+
block.after(p);
|
|
3678
|
+
}
|
|
3679
|
+
surface.focus();
|
|
3680
|
+
return true;
|
|
3681
|
+
}
|
|
3682
|
+
/** Mantiene al día los índices: ids en los títulos y enlaces a ellos. */
|
|
3683
|
+
refreshToc(el) {
|
|
3684
|
+
const navs = Array.from(el.querySelectorAll('nav[data-type="toc"]'));
|
|
3685
|
+
if (!navs.length)
|
|
3686
|
+
return;
|
|
3687
|
+
const headings = ensureHeadingIds(el);
|
|
3688
|
+
const signature = `${navs.length}|${headingsSignature(headings)}`;
|
|
3689
|
+
if (signature === this.tocSignature && navs.every((n) => n.childElementCount))
|
|
3690
|
+
return;
|
|
3691
|
+
this.tocSignature = signature;
|
|
3692
|
+
for (const nav of navs) {
|
|
3693
|
+
nav.setAttribute('contenteditable', 'false');
|
|
3694
|
+
buildToc(nav, headings, this.t().editorTocTitle, this.document);
|
|
3695
|
+
}
|
|
3696
|
+
}
|
|
3697
|
+
// --- Menciones ---------------------------------------------------------------------------------
|
|
3698
|
+
/** Tras escribir `@` al inicio de una palabra, abre la lista de menciones. */
|
|
3699
|
+
openMention() {
|
|
3700
|
+
const caret = this.mentions() ? this.caretText() : null;
|
|
3701
|
+
if (!caret || caret.offset < 1)
|
|
3702
|
+
return;
|
|
3703
|
+
const before = caret.node.data[caret.offset - 2];
|
|
3704
|
+
if (before !== undefined && !/[\s(]/.test(before))
|
|
3705
|
+
return;
|
|
3706
|
+
this.mentionAnchor = { node: caret.node, offset: caret.offset - 1 };
|
|
3707
|
+
const pos = this.caretPosition();
|
|
3708
|
+
this.mention.set({ query: '', active: 0, left: pos.left, top: pos.bottom });
|
|
3709
|
+
void this.searchMentions('');
|
|
3710
|
+
}
|
|
3711
|
+
updateMention() {
|
|
3712
|
+
const anchor = this.mentionAnchor;
|
|
3713
|
+
const sel = this.document.getSelection();
|
|
3714
|
+
if (!anchor || !sel?.isCollapsed || sel.anchorNode !== anchor.node || sel.anchorOffset <= anchor.offset || anchor.node.data[anchor.offset] !== '@') {
|
|
3715
|
+
this.closeMention();
|
|
3716
|
+
return;
|
|
3717
|
+
}
|
|
3718
|
+
const query = anchor.node.data.slice(anchor.offset + 1, sel.anchorOffset);
|
|
3719
|
+
if (/\s{2}|\n/.test(query) || query.length > 30) {
|
|
3720
|
+
this.closeMention();
|
|
3721
|
+
return;
|
|
3722
|
+
}
|
|
3723
|
+
if (query === this.mention()?.query)
|
|
3724
|
+
return;
|
|
3725
|
+
this.mention.update((m) => (m ? { ...m, query, active: 0 } : m));
|
|
3726
|
+
void this.searchMentions(query);
|
|
3727
|
+
}
|
|
3728
|
+
async searchMentions(query) {
|
|
3729
|
+
const source = this.mentions();
|
|
3730
|
+
if (!source)
|
|
3731
|
+
return;
|
|
3732
|
+
const token = ++this.mentionToken;
|
|
3733
|
+
let found;
|
|
3734
|
+
if (typeof source === 'function') {
|
|
3735
|
+
try {
|
|
3736
|
+
found = await source(query);
|
|
3737
|
+
}
|
|
3738
|
+
catch {
|
|
3739
|
+
found = [];
|
|
3740
|
+
}
|
|
3741
|
+
}
|
|
3742
|
+
else {
|
|
3743
|
+
const q = fold(query.trim());
|
|
3744
|
+
found = source.filter((m) => !q || fold(m.label).includes(q) || fold(m.description ?? '').includes(q));
|
|
3745
|
+
}
|
|
3746
|
+
if (token !== this.mentionToken || !this.mention())
|
|
3747
|
+
return;
|
|
3748
|
+
this.mentionResults.set(found.slice(0, 8));
|
|
3749
|
+
}
|
|
3750
|
+
closeMention() {
|
|
3751
|
+
this.mention.set(null);
|
|
3752
|
+
this.mentionAnchor = null;
|
|
3753
|
+
this.mentionResults.set([]);
|
|
3754
|
+
this.mentionToken++;
|
|
3755
|
+
}
|
|
3756
|
+
mentionOptionId(index) {
|
|
3757
|
+
return `${this.mentionListId}-${index}`;
|
|
3758
|
+
}
|
|
3759
|
+
/** Cambia `@texto` por la mención (un bloque no editable) seguida de un espacio. */
|
|
3760
|
+
applyMention(item) {
|
|
3761
|
+
const anchor = this.mentionAnchor;
|
|
3762
|
+
const sel = this.document.getSelection();
|
|
3763
|
+
this.closeMention();
|
|
3764
|
+
if (!anchor || sel?.anchorNode !== anchor.node)
|
|
3765
|
+
return;
|
|
3766
|
+
const range = this.document.createRange();
|
|
3767
|
+
range.setStart(anchor.node, anchor.offset);
|
|
3768
|
+
range.setEnd(anchor.node, Math.max(anchor.offset + 1, sel.anchorOffset));
|
|
3769
|
+
range.deleteContents();
|
|
3770
|
+
const span = this.document.createElement('span');
|
|
3771
|
+
span.setAttribute('data-mention', item.id);
|
|
3772
|
+
span.setAttribute('contenteditable', 'false');
|
|
3773
|
+
span.textContent = `@${item.label}`;
|
|
3774
|
+
const space = this.document.createTextNode('\u00a0');
|
|
3775
|
+
range.insertNode(space);
|
|
3776
|
+
range.insertNode(span);
|
|
3777
|
+
this.setCaret(space, 1);
|
|
3778
|
+
this.emit();
|
|
3779
|
+
this.mentionAdd.emit(item);
|
|
3780
|
+
}
|
|
3781
|
+
onMentionKeydown(event) {
|
|
3782
|
+
const m = this.mention();
|
|
3783
|
+
const items = this.mentionResults();
|
|
3784
|
+
if (!m)
|
|
3785
|
+
return false;
|
|
3786
|
+
if (event.key === 'Escape') {
|
|
3787
|
+
this.closeMention();
|
|
3788
|
+
event.preventDefault();
|
|
3789
|
+
return true;
|
|
3790
|
+
}
|
|
3791
|
+
if (!items.length)
|
|
3792
|
+
return false;
|
|
3793
|
+
switch (event.key) {
|
|
3794
|
+
case 'ArrowDown':
|
|
3795
|
+
case 'ArrowUp': {
|
|
3796
|
+
const step = event.key === 'ArrowDown' ? 1 : -1;
|
|
3797
|
+
this.mention.set({ ...m, active: (m.active + step + items.length) % items.length });
|
|
3798
|
+
break;
|
|
3799
|
+
}
|
|
3800
|
+
case 'Enter':
|
|
3801
|
+
case 'Tab':
|
|
3802
|
+
this.applyMention(items[m.active]);
|
|
3803
|
+
break;
|
|
3804
|
+
default:
|
|
3805
|
+
return false;
|
|
3806
|
+
}
|
|
3807
|
+
event.preventDefault();
|
|
3808
|
+
return true;
|
|
3809
|
+
}
|
|
3810
|
+
/** Ids de las personas mencionadas en el contenido (sin repetir). */
|
|
3811
|
+
getMentions() {
|
|
3812
|
+
const el = this.surfaceRef()?.nativeElement;
|
|
3813
|
+
if (!el)
|
|
3814
|
+
return [];
|
|
3815
|
+
return [...new Set(Array.from(el.querySelectorAll('[data-mention]')).map((m) => m.getAttribute('data-mention')))];
|
|
3816
|
+
}
|
|
3817
|
+
// --- Autoguardado ------------------------------------------------------------------------------
|
|
3818
|
+
draftStorageKey() {
|
|
3819
|
+
const key = this.autosaveKey();
|
|
3820
|
+
return key ? `cb-editor-draft:${key}` : null;
|
|
3821
|
+
}
|
|
3822
|
+
loadDraft() {
|
|
3823
|
+
const key = this.draftStorageKey();
|
|
3824
|
+
if (!key)
|
|
3825
|
+
return;
|
|
3826
|
+
try {
|
|
3827
|
+
const raw = globalThis.localStorage?.getItem(key);
|
|
3828
|
+
const draft = raw ? JSON.parse(raw) : null;
|
|
3829
|
+
if (draft && typeof draft.html === 'string' && typeof draft.savedAt === 'number') {
|
|
3830
|
+
const html = this.sanitize(draft.html);
|
|
3831
|
+
if (html && html !== this.value())
|
|
3832
|
+
this.draft.set({ html, savedAt: draft.savedAt });
|
|
3833
|
+
}
|
|
3834
|
+
}
|
|
3835
|
+
catch {
|
|
3836
|
+
// Sin acceso al almacenamiento (modo privado, cuota): no hay borrador.
|
|
3837
|
+
}
|
|
3838
|
+
}
|
|
3839
|
+
scheduleDraft() {
|
|
3840
|
+
if (!this.draftStorageKey())
|
|
3841
|
+
return;
|
|
3842
|
+
clearTimeout(this.draftTimer);
|
|
3843
|
+
this.draftTimer = setTimeout(() => this.saveDraft(), this.autosaveDelay());
|
|
3844
|
+
}
|
|
3845
|
+
saveDraft() {
|
|
3846
|
+
this.draftTimer = undefined;
|
|
3847
|
+
const key = this.draftStorageKey();
|
|
3848
|
+
if (!key)
|
|
3849
|
+
return;
|
|
3850
|
+
try {
|
|
3851
|
+
const savedAt = Date.now();
|
|
3852
|
+
globalThis.localStorage?.setItem(key, JSON.stringify({ html: this.value(), savedAt }));
|
|
3853
|
+
this.savedAt.set(savedAt);
|
|
3854
|
+
// Lo escrito ahora sustituye al borrador anterior.
|
|
3855
|
+
this.draft.set(null);
|
|
3856
|
+
}
|
|
3857
|
+
catch {
|
|
3858
|
+
// Almacenamiento lleno o bloqueado: se sigue sin borrador.
|
|
3859
|
+
}
|
|
3860
|
+
}
|
|
3861
|
+
/** Recupera el borrador guardado en este navegador. */
|
|
3862
|
+
restoreDraft() {
|
|
3863
|
+
const draft = this.draft();
|
|
3864
|
+
if (!draft)
|
|
3865
|
+
return;
|
|
3866
|
+
this.draft.set(null);
|
|
3867
|
+
this.value.set(draft.html);
|
|
3868
|
+
this.onChange(draft.html);
|
|
3869
|
+
afterNextRender(() => this.surfaceRef()?.nativeElement.focus(), { injector: this.injector });
|
|
3870
|
+
}
|
|
3871
|
+
/** Descarta el borrador guardado. */
|
|
3872
|
+
discardDraft() {
|
|
3873
|
+
this.draft.set(null);
|
|
3874
|
+
this.clearDraft();
|
|
3875
|
+
}
|
|
3876
|
+
/** Borra el borrador de este navegador (llámalo tras guardar en tu servidor). */
|
|
3877
|
+
clearDraft() {
|
|
3878
|
+
clearTimeout(this.draftTimer);
|
|
3879
|
+
this.draftTimer = undefined;
|
|
3880
|
+
this.savedAt.set(null);
|
|
3881
|
+
const key = this.draftStorageKey();
|
|
3882
|
+
if (!key)
|
|
3883
|
+
return;
|
|
3884
|
+
try {
|
|
3885
|
+
globalThis.localStorage?.removeItem(key);
|
|
3886
|
+
}
|
|
3887
|
+
catch {
|
|
3888
|
+
// Nada que borrar.
|
|
3889
|
+
}
|
|
3890
|
+
}
|
|
3891
|
+
draftDate(at) {
|
|
3892
|
+
return new Intl.DateTimeFormat(this.t().intlLocale, { dateStyle: 'medium', timeStyle: 'short' }).format(at);
|
|
3893
|
+
}
|
|
3894
|
+
draftTime(at) {
|
|
3895
|
+
return new Intl.DateTimeFormat(this.t().intlLocale, { timeStyle: 'short' }).format(at);
|
|
3896
|
+
}
|
|
3069
3897
|
// --- Vista de HTML -----------------------------------------------------------------
|
|
3070
3898
|
toggleSource() {
|
|
3071
3899
|
const el = this.surfaceRef()?.nativeElement;
|
|
@@ -3076,8 +3904,10 @@ class CbEditor {
|
|
|
3076
3904
|
}
|
|
3077
3905
|
// Al volver, el HTML escrito a mano se sanea.
|
|
3078
3906
|
const clean = this.sanitize(this.sourceText());
|
|
3079
|
-
if (el)
|
|
3907
|
+
if (el) {
|
|
3080
3908
|
el.innerHTML = clean;
|
|
3909
|
+
decorate(el);
|
|
3910
|
+
}
|
|
3081
3911
|
this.sourceMode.set(false);
|
|
3082
3912
|
this.emit();
|
|
3083
3913
|
}
|
|
@@ -3091,6 +3921,7 @@ class CbEditor {
|
|
|
3091
3921
|
const el = this.surfaceRef()?.nativeElement;
|
|
3092
3922
|
if (!el)
|
|
3093
3923
|
return;
|
|
3924
|
+
this.refreshToc(el);
|
|
3094
3925
|
const html = this.sanitize(el.innerHTML);
|
|
3095
3926
|
this.length.set(this.textLength(el));
|
|
3096
3927
|
if (html === this.value())
|
|
@@ -3098,6 +3929,7 @@ class CbEditor {
|
|
|
3098
3929
|
this.lastHtml = html;
|
|
3099
3930
|
this.value.set(html);
|
|
3100
3931
|
this.onChange(html);
|
|
3932
|
+
this.scheduleDraft();
|
|
3101
3933
|
}
|
|
3102
3934
|
onBlur() {
|
|
3103
3935
|
this.bubble.set(null);
|
|
@@ -3200,609 +4032,696 @@ class CbEditor {
|
|
|
3200
4032
|
});
|
|
3201
4033
|
const cell = (this.closest(sel.anchorNode, 'TD') ?? this.closest(sel.anchorNode, 'TH'));
|
|
3202
4034
|
this.tableCell.set(cell);
|
|
4035
|
+
const pre = this.closest(sel.anchorNode, 'PRE');
|
|
4036
|
+
const previous = this.codeBlock();
|
|
4037
|
+
if (previous !== pre) {
|
|
4038
|
+
// Al salir de un bloque coloreado por el editor, se vuelve a colorear con lo escrito.
|
|
4039
|
+
const lang = previous?.getAttribute('data-language');
|
|
4040
|
+
if (previous?.isConnected && lang && previous.getAttribute('data-highlight') === 'auto') {
|
|
4041
|
+
highlightCodeBlock(previous, lang, this.document);
|
|
4042
|
+
this.emit();
|
|
4043
|
+
}
|
|
4044
|
+
this.codeBlock.set(pre);
|
|
4045
|
+
this.codeCopied.set(false);
|
|
4046
|
+
}
|
|
4047
|
+
if (this.mention() && (sel.anchorNode !== this.mentionAnchor?.node || sel.anchorOffset <= this.mentionAnchor.offset))
|
|
4048
|
+
this.closeMention();
|
|
3203
4049
|
if (this.slash() && (sel.anchorNode !== this.slashAnchor?.node || sel.anchorOffset <= this.slashAnchor.offset))
|
|
3204
4050
|
this.closeSlash();
|
|
3205
4051
|
this.updateBubble();
|
|
3206
4052
|
}
|
|
3207
4053
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: CbEditor, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3208
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: CbEditor, isStandalone: true, selector: "cb-editor", inputs: { placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, wordCount: { classPropertyName: "wordCount", publicName: "wordCount", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, toolbar: { classPropertyName: "toolbar", publicName: "toolbar", isSignal: true, isRequired: false, transformFunction: null }, maxLength: { classPropertyName: "maxLength", publicName: "maxLength", isSignal: true, isRequired: false, transformFunction: null }, images: { classPropertyName: "images", publicName: "images", isSignal: true, isRequired: false, transformFunction: null }, imageUpload: { classPropertyName: "imageUpload", publicName: "imageUpload", isSignal: true, isRequired: false, transformFunction: null }, maxImageSize: { classPropertyName: "maxImageSize", publicName: "maxImageSize", isSignal: true, isRequired: false, transformFunction: null }, pastePlainText: { classPropertyName: "pastePlainText", publicName: "pastePlainText", isSignal: true, isRequired: false, transformFunction: null }, fonts: { classPropertyName: "fonts", publicName: "fonts", isSignal: true, isRequired: false, transformFunction: null }, fontsHref: { classPropertyName: "fontsHref", publicName: "fontsHref", isSignal: true, isRequired: false, transformFunction: null }, bubbleMenu: { classPropertyName: "bubbleMenu", publicName: "bubbleMenu", isSignal: true, isRequired: false, transformFunction: null }, slashCommands: { classPropertyName: "slashCommands", publicName: "slashCommands", isSignal: true, isRequired: false, transformFunction: null }, autolink: { classPropertyName: "autolink", publicName: "autolink", isSignal: true, isRequired: false, transformFunction: null }, typography: { classPropertyName: "typography", publicName: "typography", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", imageError: "imageError" }, host: { properties: { "class.cb-is-invalid": "invalidState()", "class.cb-is-disabled": "isDisabled()", "class.cb-is-readonly": "readonly()", "class.cb-is-fullscreen": "fullscreen()" }, classAttribute: "cb-editor" }, providers: [
|
|
4054
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: CbEditor, isStandalone: true, selector: "cb-editor", inputs: { placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, wordCount: { classPropertyName: "wordCount", publicName: "wordCount", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, toolbar: { classPropertyName: "toolbar", publicName: "toolbar", isSignal: true, isRequired: false, transformFunction: null }, maxLength: { classPropertyName: "maxLength", publicName: "maxLength", isSignal: true, isRequired: false, transformFunction: null }, images: { classPropertyName: "images", publicName: "images", isSignal: true, isRequired: false, transformFunction: null }, imageUpload: { classPropertyName: "imageUpload", publicName: "imageUpload", isSignal: true, isRequired: false, transformFunction: null }, maxImageSize: { classPropertyName: "maxImageSize", publicName: "maxImageSize", isSignal: true, isRequired: false, transformFunction: null }, pastePlainText: { classPropertyName: "pastePlainText", publicName: "pastePlainText", isSignal: true, isRequired: false, transformFunction: null }, fonts: { classPropertyName: "fonts", publicName: "fonts", isSignal: true, isRequired: false, transformFunction: null }, fontsHref: { classPropertyName: "fontsHref", publicName: "fontsHref", isSignal: true, isRequired: false, transformFunction: null }, bubbleMenu: { classPropertyName: "bubbleMenu", publicName: "bubbleMenu", isSignal: true, isRequired: false, transformFunction: null }, slashCommands: { classPropertyName: "slashCommands", publicName: "slashCommands", isSignal: true, isRequired: false, transformFunction: null }, autolink: { classPropertyName: "autolink", publicName: "autolink", isSignal: true, isRequired: false, transformFunction: null }, typography: { classPropertyName: "typography", publicName: "typography", isSignal: true, isRequired: false, transformFunction: null }, mentions: { classPropertyName: "mentions", publicName: "mentions", isSignal: true, isRequired: false, transformFunction: null }, autosaveKey: { classPropertyName: "autosaveKey", publicName: "autosaveKey", isSignal: true, isRequired: false, transformFunction: null }, autosaveDelay: { classPropertyName: "autosaveDelay", publicName: "autosaveDelay", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", mentionAdd: "mentionAdd", imageError: "imageError" }, host: { properties: { "class.cb-is-invalid": "invalidState()", "class.cb-is-disabled": "isDisabled()", "class.cb-is-readonly": "readonly()", "class.cb-is-fullscreen": "fullscreen()" }, classAttribute: "cb-editor" }, providers: [
|
|
3209
4055
|
{
|
|
3210
4056
|
provide: NG_VALUE_ACCESSOR,
|
|
3211
4057
|
useExisting: forwardRef(() => CbEditor),
|
|
3212
4058
|
multi: true,
|
|
3213
4059
|
},
|
|
3214
|
-
], viewQueries: [{ propertyName: "findInputRef", first: true, predicate: ["findInput"], descendants: true, isSignal: true }, { propertyName: "surfaceRef", first: true, predicate: ["surface"], descendants: true, isSignal: true }, { propertyName: "imageInputRef", first: true, predicate: ["imageInput"], descendants: true, isSignal: true }, { propertyName: "toolbarRef", first: true, predicate: ["toolbar"], descendants: true, isSignal: true }, { propertyName: "linkInputRef", first: true, predicate: ["linkInput"], descendants: true, isSignal: true }, { propertyName: "bodyRef", first: true, predicate: ["body"], descendants: true, isSignal: true }, { propertyName: "tableRowsRef", first: true, predicate: ["tableRows"], descendants: true, isSignal: true }, { propertyName: "tableColsRef", first: true, predicate: ["tableCols"], descendants: true, isSignal: true }, { propertyName: "tableHeaderRef", first: true, predicate: ["tableHeader"], descendants: true, isSignal: true }, { propertyName: "altInputRef", first: true, predicate: ["altInput"], descendants: true, isSignal: true }, { propertyName: "cropStageRef", first: true, predicate: ["cropStage"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
3215
|
-
<div class="cb-editor__header">
|
|
3216
|
-
@if (!readonly()) {
|
|
3217
|
-
<div
|
|
3218
|
-
#toolbar
|
|
3219
|
-
class="cb-editor__toolbar"
|
|
3220
|
-
role="toolbar"
|
|
3221
|
-
[attr.aria-label]="t().textFormatting"
|
|
3222
|
-
[attr.aria-controls]="surfaceId"
|
|
3223
|
-
(keydown)="onToolbarKeydown($event)"
|
|
3224
|
-
(focusin)="setActiveTool($any($event.target))"
|
|
3225
|
-
>
|
|
3226
|
-
@for (row of toolbarRows(); track $index) {
|
|
3227
|
-
<div class="cb-editor__row">
|
|
3228
|
-
@for (tool of row; track $index) {
|
|
3229
|
-
@if (tool === '|') {
|
|
3230
|
-
<span class="cb-editor__sep" aria-hidden="true"></span>
|
|
3231
|
-
} @else if (tool === 'heading') {
|
|
3232
|
-
<select
|
|
3233
|
-
class="cb-editor__select"
|
|
3234
|
-
[disabled]="isDisabled() || sourceMode()"
|
|
3235
|
-
[attr.aria-label]="t().paragraphStyle"
|
|
3236
|
-
[title]="t().paragraphStyle"
|
|
3237
|
-
(change)="format($any($event.target).value)"
|
|
3238
|
-
>
|
|
3239
|
-
<option value="P" [selected]="state().block === 'P'">{{ t().normal }}</option>
|
|
3240
|
-
<option value="H1" [selected]="state().block === 'H1'">{{ t().heading1 }}</option>
|
|
3241
|
-
<option value="H2" [selected]="state().block === 'H2'">{{ t().heading2 }}</option>
|
|
3242
|
-
<option value="H3" [selected]="state().block === 'H3'">{{ t().heading3 }}</option>
|
|
3243
|
-
<option value="H4" [selected]="state().block === 'H4'">{{ t().heading4 }}</option>
|
|
3244
|
-
</select>
|
|
3245
|
-
} @else if (tool === 'fontFamily') {
|
|
3246
|
-
<select
|
|
3247
|
-
class="cb-editor__select cb-editor__select--font"
|
|
3248
|
-
[disabled]="isDisabled() || sourceMode()"
|
|
3249
|
-
[attr.aria-label]="t().fontFamily"
|
|
3250
|
-
[title]="t().fontFamily"
|
|
3251
|
-
(change)="setFontFamily($any($event.target).value)"
|
|
3252
|
-
>
|
|
3253
|
-
@for (group of fonts(); track $index) {
|
|
3254
|
-
<optgroup [label]="groupLabel(group)">
|
|
3255
|
-
@for (font of group.fonts; track font.value) {
|
|
3256
|
-
<option [value]="font.value" [style.font-family]="font.value || null" [selected]="state().fontFamily === font.value">{{ font.name }}</option>
|
|
3257
|
-
}
|
|
3258
|
-
</optgroup>
|
|
3259
|
-
}
|
|
3260
|
-
</select>
|
|
3261
|
-
} @else if (tool === 'fontSize') {
|
|
3262
|
-
<select
|
|
3263
|
-
class="cb-editor__select cb-editor__select--size"
|
|
3264
|
-
[disabled]="isDisabled() || sourceMode()"
|
|
3265
|
-
[attr.aria-label]="t().fontSize"
|
|
3266
|
-
[title]="t().fontSize"
|
|
3267
|
-
(change)="setFontSize($any($event.target).value)"
|
|
3268
|
-
>
|
|
3269
|
-
<option value="" [selected]="!state().fontSize">{{ t().fontNormal }}</option>
|
|
3270
|
-
@for (size of fontSizes; track size) {
|
|
3271
|
-
<option [value]="size + 'px'" [selected]="state().fontSize === size + 'px'">{{ size }} px</option>
|
|
3272
|
-
}
|
|
3273
|
-
</select>
|
|
3274
|
-
} @else if (isMenu(tool)) {
|
|
3275
|
-
<div class="cb-editor__menu-wrap">
|
|
3276
|
-
<button
|
|
3277
|
-
type="button"
|
|
3278
|
-
class="cb-editor__tool cb-editor__menu-trigger"
|
|
3279
|
-
[disabled]="isDisabled() || sourceMode()"
|
|
3280
|
-
aria-haspopup="menu"
|
|
3281
|
-
[attr.aria-expanded]="openMenu() === tool"
|
|
3282
|
-
[attr.aria-label]="toolLabel(tool)"
|
|
3283
|
-
[attr.title]="toolLabel(tool)"
|
|
3284
|
-
(mousedown)="hold($event)"
|
|
3285
|
-
(click)="toggleMenu(tool)"
|
|
3286
|
-
(keydown)="onMenuTriggerKeydown($event, tool)"
|
|
3287
|
-
>
|
|
3288
|
-
<cb-icon [name]="menuIcon(tool)" [size]="16" aria-hidden="true" />
|
|
3289
|
-
@if (tool === 'textColor' || tool === 'highlight') {
|
|
3290
|
-
<span class="cb-editor__color-bar" [style.background]="tool === 'textColor' ? lastTextColor() : lastHighlight()"></span>
|
|
3291
|
-
}
|
|
3292
|
-
<cb-icon name="chevron-down" [size]="11" class="cb-editor__caret" aria-hidden="true" />
|
|
3293
|
-
</button>
|
|
3294
|
-
@if (openMenu() === tool) {
|
|
3295
|
-
<div
|
|
3296
|
-
class="cb-editor__menu"
|
|
3297
|
-
[class.cb-editor__menu--palette]="tool === 'textColor' || tool === 'highlight'"
|
|
3298
|
-
[class.cb-editor__menu--chars]="tool === 'specialChars'"
|
|
3299
|
-
role="menu"
|
|
3300
|
-
[attr.aria-label]="toolLabel(tool)"
|
|
3301
|
-
(keydown)="onMenuKeydown($event)"
|
|
3302
|
-
>
|
|
3303
|
-
@if (tool === 'alignment') {
|
|
3304
|
-
@for (option of alignments; track option) {
|
|
3305
|
-
<button
|
|
3306
|
-
type="button"
|
|
3307
|
-
class="cb-editor__menu-item"
|
|
3308
|
-
role="menuitemradio"
|
|
3309
|
-
[attr.aria-checked]="state()[option]"
|
|
3310
|
-
[tabindex]="-1"
|
|
3311
|
-
[attr.aria-keyshortcuts]="ariaKeys(option)"
|
|
3312
|
-
(mousedown)="hold($event)"
|
|
3313
|
-
(click)="applyMenu(tool, option)"
|
|
3314
|
-
>
|
|
3315
|
-
<cb-icon [name]="toolIcon(option)" [size]="16" aria-hidden="true" />
|
|
3316
|
-
<span>{{ toolLabel(option) }}</span>
|
|
3317
|
-
<kbd class="cb-editor__kbd">{{ toolKeys(option) }}</kbd>
|
|
3318
|
-
</button>
|
|
3319
|
-
}
|
|
3320
|
-
} @else if (tool === 'specialChars') {
|
|
3321
|
-
@for (group of specialChars; track $index) {
|
|
3322
|
-
<div class="cb-editor__chars-group" role="group" [attr.aria-label]="group.label(t())">
|
|
3323
|
-
<span class="cb-editor__chars-label" aria-hidden="true">{{ group.label(t()) }}</span>
|
|
3324
|
-
<div class="cb-editor__chars">
|
|
3325
|
-
@for (ch of group.chars; track ch) {
|
|
3326
|
-
<button type="button" class="cb-editor__char" role="menuitem" [tabindex]="-1" (mousedown)="hold($event)" (click)="applyMenu(tool, ch)">{{ ch }}</button>
|
|
3327
|
-
}
|
|
3328
|
-
</div>
|
|
3329
|
-
</div>
|
|
3330
|
-
}
|
|
3331
|
-
} @else {
|
|
3332
|
-
<button
|
|
3333
|
-
type="button"
|
|
3334
|
-
class="cb-editor__menu-item cb-editor__menu-item--wide"
|
|
3335
|
-
role="menuitem"
|
|
3336
|
-
[tabindex]="-1"
|
|
3337
|
-
(mousedown)="hold($event)"
|
|
3338
|
-
(click)="applyMenu(tool, null)"
|
|
3339
|
-
>
|
|
3340
|
-
<span class="cb-editor__swatch cb-editor__swatch--none" aria-hidden="true"></span>
|
|
3341
|
-
{{ tool === 'textColor' ? t().automaticColor : t().noHighlight }}
|
|
3342
|
-
</button>
|
|
3343
|
-
<div class="cb-editor__swatches">
|
|
3344
|
-
@for (color of tool === 'textColor' ? textColors : highlightColors; track color.value) {
|
|
3345
|
-
<button
|
|
3346
|
-
type="button"
|
|
3347
|
-
class="cb-editor__swatch"
|
|
3348
|
-
role="menuitem"
|
|
3349
|
-
[style.background]="color.value"
|
|
3350
|
-
[attr.aria-label]="color.label(t())"
|
|
3351
|
-
[attr.title]="color.label(t())"
|
|
3352
|
-
[tabindex]="-1"
|
|
3353
|
-
(mousedown)="hold($event)"
|
|
3354
|
-
(click)="applyMenu(tool, color.value)"
|
|
3355
|
-
></button>
|
|
3356
|
-
}
|
|
3357
|
-
</div>
|
|
3358
|
-
}
|
|
3359
|
-
</div>
|
|
3360
|
-
}
|
|
3361
|
-
</div>
|
|
3362
|
-
} @else {
|
|
3363
|
-
<button
|
|
3364
|
-
type="button"
|
|
3365
|
-
class="cb-editor__tool"
|
|
3366
|
-
[class.cb-is-active]="isActive(tool)"
|
|
3367
|
-
[disabled]="isDisabled() || (sourceMode() && tool !== 'source' && tool !== 'fullscreen')"
|
|
3368
|
-
[attr.aria-label]="toolLabel(tool)"
|
|
3369
|
-
[attr.title]="toolTitle(tool)"
|
|
3370
|
-
[attr.aria-keyshortcuts]="ariaKeys(tool)"
|
|
3371
|
-
[attr.aria-pressed]="pressable(tool) ? isActive(tool) : null"
|
|
3372
|
-
(mousedown)="hold($event)"
|
|
3373
|
-
(click)="run(tool)"
|
|
3374
|
-
>
|
|
3375
|
-
<cb-icon [name]="tool === 'fullscreen' && fullscreen() ? 'minimize' : toolIcon(tool)" [size]="16" aria-hidden="true" />
|
|
3376
|
-
</button>
|
|
3377
|
-
}
|
|
3378
|
-
}
|
|
3379
|
-
</div>
|
|
3380
|
-
}
|
|
3381
|
-
</div>
|
|
3382
|
-
}
|
|
3383
|
-
|
|
3384
|
-
@if (findOpen() && !readonly()) {
|
|
3385
|
-
<div class="cb-editor__link cb-editor__find" role="search" [attr.aria-label]="t().findReplace" (keydown)="onFindKeydown($event)">
|
|
3386
|
-
<label class="cb-editor__link-field">
|
|
3387
|
-
<span class="cb-editor__link-label">{{ t().findText }}</span>
|
|
3388
|
-
<input #findInput class="cb-editor__link-input" type="text" autocomplete="off" [value]="findQuery()" (input)="setFindQuery($any($event.target).value)" />
|
|
3389
|
-
</label>
|
|
3390
|
-
<label class="cb-editor__link-field">
|
|
3391
|
-
<span class="cb-editor__link-label">{{ t().replaceWith }}</span>
|
|
3392
|
-
<input class="cb-editor__link-input cb-editor__replace-input" type="text" autocomplete="off" [value]="replaceText()" (input)="replaceText.set($any($event.target).value)" />
|
|
3393
|
-
</label>
|
|
3394
|
-
<div class="cb-editor__find-options">
|
|
3395
|
-
<button
|
|
3396
|
-
type="button"
|
|
3397
|
-
class="cb-editor__tool"
|
|
3398
|
-
[class.cb-is-active]="findCase()"
|
|
3399
|
-
[attr.aria-pressed]="findCase()"
|
|
3400
|
-
[attr.aria-label]="t().matchCase"
|
|
3401
|
-
[attr.title]="t().matchCase"
|
|
3402
|
-
(click)="setFindOption('case')"
|
|
3403
|
-
>
|
|
3404
|
-
<cb-icon name="case-sensitive" [size]="16" aria-hidden="true" />
|
|
3405
|
-
</button>
|
|
3406
|
-
<button
|
|
3407
|
-
type="button"
|
|
3408
|
-
class="cb-editor__tool"
|
|
3409
|
-
[class.cb-is-active]="findWhole()"
|
|
3410
|
-
[attr.aria-pressed]="findWhole()"
|
|
3411
|
-
[attr.aria-label]="t().wholeWords"
|
|
3412
|
-
[attr.title]="t().wholeWords"
|
|
3413
|
-
(click)="setFindOption('whole')"
|
|
3414
|
-
>
|
|
3415
|
-
<cb-icon name="whole-word" [size]="16" aria-hidden="true" />
|
|
3416
|
-
</button>
|
|
3417
|
-
<span class="cb-editor__find-count" role="status">{{ findStatus() }}</span>
|
|
3418
|
-
<button type="button" class="cb-editor__tool" [disabled]="!findTotal()" [attr.aria-label]="t().findPrevious" [attr.title]="t().findPrevious + ' (Shift+Enter)'" (click)="findStep(-1)">
|
|
3419
|
-
<cb-icon name="chevron-up" [size]="16" aria-hidden="true" />
|
|
3420
|
-
</button>
|
|
3421
|
-
<button type="button" class="cb-editor__tool" [disabled]="!findTotal()" [attr.aria-label]="t().findNext" [attr.title]="t().findNext + ' (Enter)'" (click)="findStep(1)">
|
|
3422
|
-
<cb-icon name="chevron-down" [size]="16" aria-hidden="true" />
|
|
3423
|
-
</button>
|
|
3424
|
-
</div>
|
|
3425
|
-
<div class="cb-editor__link-actions">
|
|
3426
|
-
<button type="button" class="cb-editor__btn" [disabled]="!findTotal()" (click)="replaceCurrent()">{{ t().replaceOne }}</button>
|
|
3427
|
-
<button type="button" class="cb-editor__btn" [disabled]="!findTotal()" (click)="replaceAllMatches()">{{ t().replaceAll }}</button>
|
|
3428
|
-
<button type="button" class="cb-editor__tool" [attr.aria-label]="t().close" [attr.title]="t().close + ' (Esc)'" (click)="closeFind()">
|
|
3429
|
-
<cb-icon name="x" [size]="16" aria-hidden="true" />
|
|
3430
|
-
</button>
|
|
3431
|
-
</div>
|
|
3432
|
-
</div>
|
|
3433
|
-
}
|
|
3434
|
-
|
|
3435
|
-
@if (
|
|
3436
|
-
<div class="cb-
|
|
3437
|
-
<
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
<
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
</
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
type="button"
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
>
|
|
3473
|
-
<
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
<
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
>
|
|
3513
|
-
|
|
3514
|
-
</
|
|
3515
|
-
|
|
3516
|
-
<
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
[
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
<
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
[attr.aria-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
}
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
[
|
|
3786
|
-
[style.
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
(
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
`, isInline: true, styles: [":host{display:block;width:100%;border:var(--cb-border-width) solid var(--cb-color-border);border-radius:var(--cb-radius-lg);background:var(--cb-color-surface);color:var(--cb-color-text);font-family:var(--cb-font-sans);overflow:clip}.cb-editor__header{position:sticky;top:var(--cb-editor-sticky-top, 0px);z-index:3;background:var(--cb-color-surface)}.cb-editor__toolbar{display:grid;gap:.25rem;padding:var(--cb-space-2) var(--cb-space-3);border-bottom:var(--cb-border-width) solid var(--cb-color-border);background:var(--cb-color-surface-subtle, var(--cb-color-surface))}.cb-editor__row{display:flex;align-items:center;flex-wrap:wrap;gap:.2rem}.cb-editor__row+.cb-editor__row{padding-top:.25rem;border-top:1px dashed var(--cb-color-border)}.cb-editor__select--font{width:10.5rem}.cb-editor__select--size{width:5.5rem}.cb-editor__select{height:1.75rem;padding:0 .4rem;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-sm);background:var(--cb-color-bg);color:var(--cb-color-text);font:inherit;font-size:var(--cb-text-sm);cursor:pointer}.cb-editor__sep{width:1px;height:1.1rem;margin:0 .25rem;background:var(--cb-color-border)}.cb-editor__tool{display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;border:none;border-radius:var(--cb-radius-sm);background:transparent;color:var(--cb-color-text);cursor:pointer}.cb-editor__tool:hover{background:color-mix(in srgb,var(--cb-color-primary-500) 12%,transparent)}.cb-editor__tool:disabled{opacity:.4;cursor:not-allowed}.cb-editor__tool:focus-visible,.cb-editor__select:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:1px}.cb-editor__tool.cb-is-active{color:var(--cb-color-primary-700);background:color-mix(in srgb,var(--cb-color-primary-500) 18%,transparent)}:host-context([data-theme=\"dark\"]) .cb-editor__tool.cb-is-active{color:var(--cb-color-primary-300)}@media(forced-colors:active){.cb-editor__tool.cb-is-active{outline:2px solid Highlight}}.cb-editor__link{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--cb-space-2) var(--cb-space-3);padding:var(--cb-space-2) var(--cb-space-3);border-bottom:var(--cb-border-width) solid var(--cb-color-border);background:var(--cb-color-surface-subtle, var(--cb-color-surface));font-size:var(--cb-text-sm)}.cb-editor__link-field{display:grid;flex:1 1 14rem;gap:.2rem}.cb-editor__link-label{font-size:var(--cb-text-xs);color:var(--cb-color-text-muted)}.cb-editor__link-input{height:2rem;padding:0 .5rem;border:1px solid var(--cb-color-border-strong, var(--cb-color-border));border-radius:var(--cb-radius-sm);background:var(--cb-color-bg, var(--cb-color-surface));color:var(--cb-color-text);font:inherit}.cb-editor__link-input[aria-invalid=true]{border-color:var(--cb-color-danger-500)}.cb-editor__link-input:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:0}.cb-editor__link-check{display:inline-flex;align-items:center;gap:.35rem;min-height:2rem}.cb-editor__link-actions{display:inline-flex;gap:var(--cb-space-2)}.cb-editor__btn{display:inline-flex;align-items:center;gap:.3rem;height:2rem;padding:0 .7rem;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-sm);background:var(--cb-color-surface);color:var(--cb-color-text);font:inherit;cursor:pointer}.cb-editor__btn:hover{background:var(--cb-color-surface-hover)}.cb-editor__btn:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:1px}.cb-editor__btn--primary{color:var(--cb-color-primary-contrast);background:var(--cb-color-primary-600);border-color:var(--cb-color-primary-600)}.cb-editor__btn--primary:hover{background:var(--cb-color-primary-700)}.cb-editor__error{flex-basis:100%;margin:0;font-size:var(--cb-text-xs);color:var(--cb-color-danger-600)}.cb-editor__surface{padding:var(--cb-space-3);font-family:var(--cb-editor-font-family, Carlito, Calibri, var(--cb-font-sans), sans-serif);font-size:var(--cb-editor-font-size, 16px);line-height:1.6;outline:none;overflow-y:auto}.cb-editor__surface:empty:before,.cb-editor__surface.cb-is-empty:not(:focus):before{content:attr(data-placeholder);color:var(--cb-color-text-muted);pointer-events:none}.cb-editor__surface.cb-is-empty:not(:empty):before{position:absolute}.cb-editor__surface{position:relative}.cb-editor__source{display:block;box-sizing:border-box;width:100%;padding:var(--cb-space-3);border:none;resize:vertical;background:var(--cb-color-surface-subtle, var(--cb-color-surface));color:var(--cb-color-text);font-family:var(--cb-font-mono, monospace);font-size:var(--cb-text-xs);line-height:1.6;outline:none}.cb-editor__footer{display:flex;align-items:center;justify-content:space-between;gap:var(--cb-space-3);padding:var(--cb-space-1) var(--cb-space-3) var(--cb-space-2);border-top:var(--cb-border-width) solid var(--cb-color-border)}.cb-editor__count{margin-left:auto;font-size:var(--cb-text-xs);color:var(--cb-color-text-muted);font-variant-numeric:tabular-nums}.cb-editor__count.cb-is-over{color:var(--cb-color-danger-600);font-weight:var(--cb-font-weight-semibold)}:host(.cb-is-readonly){border-color:transparent;background:transparent}:host(.cb-is-readonly) .cb-editor__surface{padding:0}.cb-editor__surface h1{font-size:1.5rem;margin:.4em 0}.cb-editor__surface h2{font-size:1.25rem;margin:.4em 0}.cb-editor__surface h3{font-size:1.1rem;margin:.4em 0}.cb-editor__surface p{margin:.4em 0}.cb-editor__surface ul,.cb-editor__surface ol{margin:.4em 0;padding-left:1.5rem}.cb-editor__surface blockquote{margin:.5em 0;padding:.25em .9em;border-left:3px solid var(--cb-color-primary-400, var(--cb-color-border));color:var(--cb-color-text-muted)}.cb-editor__surface a{color:var(--cb-color-primary-600, #059669);text-decoration:underline}.cb-editor__surface code{padding:.1em .35em;border-radius:var(--cb-radius-sm);background:var(--cb-color-surface-subtle, rgb(0 0 0 / .06));font-family:var(--cb-font-mono, monospace);font-size:.9em}.cb-editor__surface pre{padding:var(--cb-space-3);border-radius:var(--cb-radius-md);background:var(--cb-color-surface-subtle, rgb(0 0 0 / .06));font-family:var(--cb-font-mono, monospace);white-space:pre-wrap}.cb-editor__surface hr{margin:var(--cb-space-3) 0;border:none;border-top:1px solid var(--cb-color-border-strong, var(--cb-color-border))}.cb-editor__surface img{max-width:100%;height:auto;border-radius:var(--cb-radius-sm, 4px)}:host(.cb-is-invalid){border-color:var(--cb-color-danger)}.cb-editor__context{display:flex;align-items:center;flex-wrap:wrap;gap:.2rem;padding:var(--cb-space-1) var(--cb-space-3);border-bottom:var(--cb-border-width) solid var(--cb-color-border);background:color-mix(in srgb,var(--cb-color-primary-500) 6%,var(--cb-color-surface))}.cb-editor__tool--text{width:auto;padding:0 .4rem;font:inherit;font-size:var(--cb-text-xs);font-variant-numeric:tabular-nums}.cb-editor__tool--danger:hover{color:var(--cb-color-danger-600);background:color-mix(in srgb,var(--cb-color-danger-500) 12%,transparent)}.cb-editor__link-field--narrow{flex:0 0 6rem}.cb-editor__body{position:relative}.cb-editor__surface img{cursor:pointer}.cb-editor__img-frame{position:absolute;box-sizing:border-box;border:2px solid var(--cb-color-primary-500);border-radius:2px;pointer-events:none}.cb-editor__img-handle{position:absolute;right:-6px;bottom:-6px;width:12px;height:12px;border:2px solid var(--cb-color-surface);border-radius:2px;background:var(--cb-color-primary-500);cursor:nwse-resize;pointer-events:auto;touch-action:none}.cb-editor__surface table{width:100%;margin:var(--cb-space-2) 0;border-collapse:collapse;table-layout:fixed}.cb-editor__surface th,.cb-editor__surface td{min-width:2rem;padding:.35rem .5rem;vertical-align:top;word-break:break-word;outline:1px dashed color-mix(in srgb,var(--cb-color-text-muted) 45%,transparent);outline-offset:-1px}.cb-editor__surface th[style*=border],.cb-editor__surface td[style*=border]{outline:none}.cb-editor__surface th{font-weight:var(--cb-font-weight-semibold, 600);text-align:left}:host(.cb-is-readonly) .cb-editor__surface th,:host(.cb-is-readonly) .cb-editor__surface td{outline:none}.cb-editor__surface pre code{padding:0;background:transparent;color:inherit;font-size:inherit}.cb-editor__surface pre[data-language]:before{content:attr(data-language);float:right;margin:-.25rem -.25rem 0 .5rem;font-family:var(--cb-font-sans);font-size:.7rem;opacity:.6}.cb-editor__group{display:inline-flex;align-items:center;gap:.2rem}.cb-editor__group-label{margin-right:.15rem;font-size:var(--cb-text-xs);font-weight:var(--cb-font-weight-semibold, 600);color:var(--cb-color-text-muted)}.cb-editor__tool--labeled{width:auto;gap:.3rem;padding:0 .45rem;font:inherit;font-size:var(--cb-text-xs);white-space:nowrap}.cb-editor__picker-panel{align-items:flex-start}.cb-editor__picker{display:grid;gap:3px}.cb-editor__picker-row{display:flex;gap:3px}.cb-editor__picker-cell{width:1.1rem;height:1.1rem;padding:0;border:1px solid var(--cb-color-border-strong, var(--cb-color-border));border-radius:2px;background:var(--cb-color-surface);cursor:pointer}.cb-editor__picker-cell.cb-is-on{border-color:var(--cb-color-primary-600);background:color-mix(in srgb,var(--cb-color-primary-500) 35%,var(--cb-color-surface))}.cb-editor__picker-cell:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:1px}.cb-editor__picker-side{display:grid;gap:var(--cb-space-2);align-content:start}.cb-editor__picker-size{font-weight:var(--cb-font-weight-semibold, 600);font-variant-numeric:tabular-nums}.cb-editor__crop{display:grid;gap:var(--cb-space-2);padding:var(--cb-space-3);border-bottom:var(--cb-border-width) solid var(--cb-color-border);background:var(--cb-color-surface-subtle, var(--cb-color-surface))}.cb-editor__crop-stage{position:relative;justify-self:center;max-width:100%;overflow:hidden;-webkit-user-select:none;user-select:none;touch-action:none}.cb-editor__crop-img{display:block;max-width:100%;max-height:22rem}.cb-editor__crop-box{position:absolute;box-sizing:border-box;border:2px solid #fff;box-shadow:0 0 0 9999px #0000008c;cursor:move;outline:none}.cb-editor__crop-box:focus-visible{border-color:var(--cb-color-focus-ring)}.cb-editor__crop-handle{position:absolute;width:14px;height:14px;border:2px solid var(--cb-color-primary-600);border-radius:2px;background:#fff}.cb-editor__crop-handle--nw{left:-8px;top:-8px;cursor:nwse-resize}.cb-editor__crop-handle--ne{right:-8px;top:-8px;cursor:nesw-resize}.cb-editor__crop-handle--sw{left:-8px;bottom:-8px;cursor:nesw-resize}.cb-editor__crop-handle--se{right:-8px;bottom:-8px;cursor:nwse-resize}.cb-editor__crop-help{margin:0;font-size:var(--cb-text-xs);color:var(--cb-color-text-muted)}.cb-editor__crop-spacer{flex:1}.cb-editor__btn.cb-is-active{border-color:var(--cb-color-primary-600);color:var(--cb-color-primary-700);background:color-mix(in srgb,var(--cb-color-primary-500) 14%,var(--cb-color-surface))}.cb-editor__crop .cb-editor__link-actions{display:flex;flex-wrap:wrap}.cb-editor__select--size{width:7.5rem}.cb-editor__menu-wrap{position:relative;display:inline-flex}.cb-editor__menu-trigger{position:relative;width:auto;gap:1px;padding:0 .25rem}.cb-editor__color-bar{position:absolute;left:.35rem;right:1rem;bottom:.2rem;height:3px;border-radius:1px}.cb-editor__caret{opacity:.7}.cb-editor__menu{position:absolute;top:calc(100% + 4px);left:0;z-index:10;display:grid;gap:2px;min-width:12rem;padding:.35rem;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-md);background:var(--cb-color-surface-raised, var(--cb-color-surface));box-shadow:var(--cb-shadow-md, 0 8px 24px rgb(0 0 0 / .12))}.cb-editor__menu-item{display:flex;align-items:center;gap:.5rem;width:100%;padding:.4rem .5rem;border:none;border-radius:var(--cb-radius-sm);background:transparent;color:var(--cb-color-text);font:inherit;font-size:var(--cb-text-sm);text-align:left;cursor:pointer}.cb-editor__menu-item:hover,.cb-editor__menu-item:focus-visible{background:var(--cb-color-surface-hover);outline:none}.cb-editor__menu-item:focus-visible{box-shadow:inset 0 0 0 2px var(--cb-color-focus-ring)}.cb-editor__menu-item[aria-checked=true]{font-weight:var(--cb-font-weight-semibold, 600);color:var(--cb-color-primary-700)}.cb-editor__kbd{margin-left:auto;font-family:var(--cb-font-sans);font-size:var(--cb-text-xs);color:var(--cb-color-text-muted)}.cb-editor__swatches{display:grid;grid-template-columns:repeat(5,1.6rem);gap:.35rem;padding:.25rem}.cb-editor__swatch{width:1.6rem;height:1.6rem;padding:0;border:1px solid rgb(0 0 0 / .15);border-radius:var(--cb-radius-sm);cursor:pointer}.cb-editor__swatch:hover{transform:scale(1.1)}.cb-editor__swatch:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:2px}.cb-editor__swatch--none{display:inline-block;width:1rem;height:1rem;background:linear-gradient(135deg,transparent 45%,var(--cb-color-danger-500) 45% 55%,transparent 55%),var(--cb-color-surface)}.cb-editor__surface{resize:vertical}:host(.cb-is-readonly) .cb-editor__surface{resize:none}:host(.cb-is-fullscreen){position:fixed;inset:0;z-index:1000;display:flex;flex-direction:column;border-radius:0}:host(.cb-is-fullscreen) .cb-editor__body{flex:1;min-height:0;display:flex;flex-direction:column}:host(.cb-is-fullscreen) .cb-editor__surface{flex:1;resize:none;max-width:60rem;width:100%;margin:0 auto}.cb-editor__surface h4{margin:.75rem 0 .35rem;font-size:1rem}.cb-editor__surface ul[data-type=taskList]{padding-left:.25rem;list-style:none}.cb-editor__surface ul[data-type=taskList]>li{position:relative;padding-left:1.75rem}.cb-editor__surface ul[data-type=taskList]>li:before{content:\"\";position:absolute;left:0;top:.3em;box-sizing:border-box;width:1.05rem;height:1.05rem;border:1.5px solid var(--cb-color-border-strong, var(--cb-color-border));border-radius:3px;background:var(--cb-color-surface);cursor:pointer}.cb-editor__surface ul[data-type=taskList]>li[data-checked=true]:before{content:\"\\2713\";display:grid;place-items:center;border-color:var(--cb-color-primary-600);background:var(--cb-color-primary-600);color:var(--cb-color-primary-contrast, #fff);font-size:.75rem;font-weight:700;line-height:1}.cb-editor__surface ul[data-type=taskList]>li[data-checked=true]{color:var(--cb-color-text-muted);text-decoration:line-through}.cb-editor__menu--chars{left:auto;right:0;width:max-content;max-height:18rem;overflow-y:auto}.cb-editor__chars-label{display:block;padding:.35rem .25rem .15rem;font-size:var(--cb-text-xs);font-weight:var(--cb-font-weight-semibold, 600);color:var(--cb-color-text-muted)}.cb-editor__chars{display:grid;grid-template-columns:repeat(8,2rem);gap:2px}.cb-editor__char{width:2rem;height:2rem;padding:0;border:none;border-radius:var(--cb-radius-sm);background:transparent;color:var(--cb-color-text);font-size:1.05rem;line-height:1;cursor:pointer}.cb-editor__char:hover,.cb-editor__char:focus-visible{background:var(--cb-color-surface-hover);outline:none}.cb-editor__char:focus-visible{box-shadow:inset 0 0 0 2px var(--cb-color-focus-ring)}.cb-editor__find{align-items:flex-end}.cb-editor__find-options{display:inline-flex;align-items:center;gap:.2rem;min-height:2rem}.cb-editor__find-count{min-width:5.5rem;padding:0 .35rem;font-size:var(--cb-text-xs);color:var(--cb-color-text-muted);font-variant-numeric:tabular-nums;text-align:center}.cb-editor__find .cb-editor__link-actions{align-items:center}.cb-editor__bubble{position:absolute;z-index:4;display:flex;gap:2px;padding:.2rem;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-md);background:var(--cb-color-surface-raised, var(--cb-color-surface));box-shadow:var(--cb-shadow-md, 0 8px 24px rgb(0 0 0 / .12));transform:translate(-50%,calc(-100% - 6px))}.cb-editor__bubble--below{transform:translate(-50%,6px)}.cb-editor__slash{position:absolute;z-index:4;display:grid;gap:1px;width:15rem;max-height:17rem;margin-top:4px;padding:.3rem;overflow-y:auto;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-md);background:var(--cb-color-surface-raised, var(--cb-color-surface));box-shadow:var(--cb-shadow-md, 0 8px 24px rgb(0 0 0 / .12))}.cb-editor__slash-item{display:flex;align-items:center;gap:.55rem;padding:.4rem .5rem;border-radius:var(--cb-radius-sm);font-size:var(--cb-text-sm);cursor:pointer}.cb-editor__slash-item:hover{background:var(--cb-color-surface-hover)}.cb-editor__slash-item.cb-is-active{color:var(--cb-color-primary-700);background:color-mix(in srgb,var(--cb-color-primary-500) 14%,transparent)}:host-context([data-theme=\"dark\"]) .cb-editor__slash-item.cb-is-active{color:var(--cb-color-primary-300)}@media(forced-colors:active){.cb-editor__slash-item.cb-is-active{outline:2px solid Highlight}}\n"], dependencies: [{ kind: "component", type: CbIcon, selector: "cb-icon", inputs: ["name", "size", "spin", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
4060
|
+
], viewQueries: [{ propertyName: "findInputRef", first: true, predicate: ["findInput"], descendants: true, isSignal: true }, { propertyName: "surfaceRef", first: true, predicate: ["surface"], descendants: true, isSignal: true }, { propertyName: "imageInputRef", first: true, predicate: ["imageInput"], descendants: true, isSignal: true }, { propertyName: "toolbarRef", first: true, predicate: ["toolbar"], descendants: true, isSignal: true }, { propertyName: "linkInputRef", first: true, predicate: ["linkInput"], descendants: true, isSignal: true }, { propertyName: "bodyRef", first: true, predicate: ["body"], descendants: true, isSignal: true }, { propertyName: "tableRowsRef", first: true, predicate: ["tableRows"], descendants: true, isSignal: true }, { propertyName: "tableColsRef", first: true, predicate: ["tableCols"], descendants: true, isSignal: true }, { propertyName: "tableHeaderRef", first: true, predicate: ["tableHeader"], descendants: true, isSignal: true }, { propertyName: "altInputRef", first: true, predicate: ["altInput"], descendants: true, isSignal: true }, { propertyName: "cropStageRef", first: true, predicate: ["cropStage"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
4061
|
+
<div class="cb-editor__header">
|
|
4062
|
+
@if (!readonly()) {
|
|
4063
|
+
<div
|
|
4064
|
+
#toolbar
|
|
4065
|
+
class="cb-editor__toolbar"
|
|
4066
|
+
role="toolbar"
|
|
4067
|
+
[attr.aria-label]="t().textFormatting"
|
|
4068
|
+
[attr.aria-controls]="surfaceId"
|
|
4069
|
+
(keydown)="onToolbarKeydown($event)"
|
|
4070
|
+
(focusin)="setActiveTool($any($event.target))"
|
|
4071
|
+
>
|
|
4072
|
+
@for (row of toolbarRows(); track $index) {
|
|
4073
|
+
<div class="cb-editor__row">
|
|
4074
|
+
@for (tool of row; track $index) {
|
|
4075
|
+
@if (tool === '|') {
|
|
4076
|
+
<span class="cb-editor__sep" aria-hidden="true"></span>
|
|
4077
|
+
} @else if (tool === 'heading') {
|
|
4078
|
+
<select
|
|
4079
|
+
class="cb-editor__select"
|
|
4080
|
+
[disabled]="isDisabled() || sourceMode()"
|
|
4081
|
+
[attr.aria-label]="t().paragraphStyle"
|
|
4082
|
+
[title]="t().paragraphStyle"
|
|
4083
|
+
(change)="format($any($event.target).value)"
|
|
4084
|
+
>
|
|
4085
|
+
<option value="P" [selected]="state().block === 'P'">{{ t().normal }}</option>
|
|
4086
|
+
<option value="H1" [selected]="state().block === 'H1'">{{ t().heading1 }}</option>
|
|
4087
|
+
<option value="H2" [selected]="state().block === 'H2'">{{ t().heading2 }}</option>
|
|
4088
|
+
<option value="H3" [selected]="state().block === 'H3'">{{ t().heading3 }}</option>
|
|
4089
|
+
<option value="H4" [selected]="state().block === 'H4'">{{ t().heading4 }}</option>
|
|
4090
|
+
</select>
|
|
4091
|
+
} @else if (tool === 'fontFamily') {
|
|
4092
|
+
<select
|
|
4093
|
+
class="cb-editor__select cb-editor__select--font"
|
|
4094
|
+
[disabled]="isDisabled() || sourceMode()"
|
|
4095
|
+
[attr.aria-label]="t().fontFamily"
|
|
4096
|
+
[title]="t().fontFamily"
|
|
4097
|
+
(change)="setFontFamily($any($event.target).value)"
|
|
4098
|
+
>
|
|
4099
|
+
@for (group of fonts(); track $index) {
|
|
4100
|
+
<optgroup [label]="groupLabel(group)">
|
|
4101
|
+
@for (font of group.fonts; track font.value) {
|
|
4102
|
+
<option [value]="font.value" [style.font-family]="font.value || null" [selected]="state().fontFamily === font.value">{{ font.name }}</option>
|
|
4103
|
+
}
|
|
4104
|
+
</optgroup>
|
|
4105
|
+
}
|
|
4106
|
+
</select>
|
|
4107
|
+
} @else if (tool === 'fontSize') {
|
|
4108
|
+
<select
|
|
4109
|
+
class="cb-editor__select cb-editor__select--size"
|
|
4110
|
+
[disabled]="isDisabled() || sourceMode()"
|
|
4111
|
+
[attr.aria-label]="t().fontSize"
|
|
4112
|
+
[title]="t().fontSize"
|
|
4113
|
+
(change)="setFontSize($any($event.target).value)"
|
|
4114
|
+
>
|
|
4115
|
+
<option value="" [selected]="!state().fontSize">{{ t().fontNormal }}</option>
|
|
4116
|
+
@for (size of fontSizes; track size) {
|
|
4117
|
+
<option [value]="size + 'px'" [selected]="state().fontSize === size + 'px'">{{ size }} px</option>
|
|
4118
|
+
}
|
|
4119
|
+
</select>
|
|
4120
|
+
} @else if (isMenu(tool)) {
|
|
4121
|
+
<div class="cb-editor__menu-wrap">
|
|
4122
|
+
<button
|
|
4123
|
+
type="button"
|
|
4124
|
+
class="cb-editor__tool cb-editor__menu-trigger"
|
|
4125
|
+
[disabled]="isDisabled() || sourceMode()"
|
|
4126
|
+
aria-haspopup="menu"
|
|
4127
|
+
[attr.aria-expanded]="openMenu() === tool"
|
|
4128
|
+
[attr.aria-label]="toolLabel(tool)"
|
|
4129
|
+
[attr.title]="toolLabel(tool)"
|
|
4130
|
+
(mousedown)="hold($event)"
|
|
4131
|
+
(click)="toggleMenu(tool)"
|
|
4132
|
+
(keydown)="onMenuTriggerKeydown($event, tool)"
|
|
4133
|
+
>
|
|
4134
|
+
<cb-icon [name]="menuIcon(tool)" [size]="16" aria-hidden="true" />
|
|
4135
|
+
@if (tool === 'textColor' || tool === 'highlight') {
|
|
4136
|
+
<span class="cb-editor__color-bar" [style.background]="tool === 'textColor' ? lastTextColor() : lastHighlight()"></span>
|
|
4137
|
+
}
|
|
4138
|
+
<cb-icon name="chevron-down" [size]="11" class="cb-editor__caret" aria-hidden="true" />
|
|
4139
|
+
</button>
|
|
4140
|
+
@if (openMenu() === tool) {
|
|
4141
|
+
<div
|
|
4142
|
+
class="cb-editor__menu"
|
|
4143
|
+
[class.cb-editor__menu--palette]="tool === 'textColor' || tool === 'highlight'"
|
|
4144
|
+
[class.cb-editor__menu--chars]="tool === 'specialChars'"
|
|
4145
|
+
role="menu"
|
|
4146
|
+
[attr.aria-label]="toolLabel(tool)"
|
|
4147
|
+
(keydown)="onMenuKeydown($event)"
|
|
4148
|
+
>
|
|
4149
|
+
@if (tool === 'alignment') {
|
|
4150
|
+
@for (option of alignments; track option) {
|
|
4151
|
+
<button
|
|
4152
|
+
type="button"
|
|
4153
|
+
class="cb-editor__menu-item"
|
|
4154
|
+
role="menuitemradio"
|
|
4155
|
+
[attr.aria-checked]="state()[option]"
|
|
4156
|
+
[tabindex]="-1"
|
|
4157
|
+
[attr.aria-keyshortcuts]="ariaKeys(option)"
|
|
4158
|
+
(mousedown)="hold($event)"
|
|
4159
|
+
(click)="applyMenu(tool, option)"
|
|
4160
|
+
>
|
|
4161
|
+
<cb-icon [name]="toolIcon(option)" [size]="16" aria-hidden="true" />
|
|
4162
|
+
<span>{{ toolLabel(option) }}</span>
|
|
4163
|
+
<kbd class="cb-editor__kbd">{{ toolKeys(option) }}</kbd>
|
|
4164
|
+
</button>
|
|
4165
|
+
}
|
|
4166
|
+
} @else if (tool === 'specialChars') {
|
|
4167
|
+
@for (group of specialChars; track $index) {
|
|
4168
|
+
<div class="cb-editor__chars-group" role="group" [attr.aria-label]="group.label(t())">
|
|
4169
|
+
<span class="cb-editor__chars-label" aria-hidden="true">{{ group.label(t()) }}</span>
|
|
4170
|
+
<div class="cb-editor__chars">
|
|
4171
|
+
@for (ch of group.chars; track ch) {
|
|
4172
|
+
<button type="button" class="cb-editor__char" role="menuitem" [tabindex]="-1" (mousedown)="hold($event)" (click)="applyMenu(tool, ch)">{{ ch }}</button>
|
|
4173
|
+
}
|
|
4174
|
+
</div>
|
|
4175
|
+
</div>
|
|
4176
|
+
}
|
|
4177
|
+
} @else {
|
|
4178
|
+
<button
|
|
4179
|
+
type="button"
|
|
4180
|
+
class="cb-editor__menu-item cb-editor__menu-item--wide"
|
|
4181
|
+
role="menuitem"
|
|
4182
|
+
[tabindex]="-1"
|
|
4183
|
+
(mousedown)="hold($event)"
|
|
4184
|
+
(click)="applyMenu(tool, null)"
|
|
4185
|
+
>
|
|
4186
|
+
<span class="cb-editor__swatch cb-editor__swatch--none" aria-hidden="true"></span>
|
|
4187
|
+
{{ tool === 'textColor' ? t().automaticColor : t().noHighlight }}
|
|
4188
|
+
</button>
|
|
4189
|
+
<div class="cb-editor__swatches">
|
|
4190
|
+
@for (color of tool === 'textColor' ? textColors : highlightColors; track color.value) {
|
|
4191
|
+
<button
|
|
4192
|
+
type="button"
|
|
4193
|
+
class="cb-editor__swatch"
|
|
4194
|
+
role="menuitem"
|
|
4195
|
+
[style.background]="color.value"
|
|
4196
|
+
[attr.aria-label]="color.label(t())"
|
|
4197
|
+
[attr.title]="color.label(t())"
|
|
4198
|
+
[tabindex]="-1"
|
|
4199
|
+
(mousedown)="hold($event)"
|
|
4200
|
+
(click)="applyMenu(tool, color.value)"
|
|
4201
|
+
></button>
|
|
4202
|
+
}
|
|
4203
|
+
</div>
|
|
4204
|
+
}
|
|
4205
|
+
</div>
|
|
4206
|
+
}
|
|
4207
|
+
</div>
|
|
4208
|
+
} @else {
|
|
4209
|
+
<button
|
|
4210
|
+
type="button"
|
|
4211
|
+
class="cb-editor__tool"
|
|
4212
|
+
[class.cb-is-active]="isActive(tool)"
|
|
4213
|
+
[disabled]="isDisabled() || (sourceMode() && tool !== 'source' && tool !== 'fullscreen')"
|
|
4214
|
+
[attr.aria-label]="toolLabel(tool)"
|
|
4215
|
+
[attr.title]="toolTitle(tool)"
|
|
4216
|
+
[attr.aria-keyshortcuts]="ariaKeys(tool)"
|
|
4217
|
+
[attr.aria-pressed]="pressable(tool) ? isActive(tool) : null"
|
|
4218
|
+
(mousedown)="hold($event)"
|
|
4219
|
+
(click)="run(tool)"
|
|
4220
|
+
>
|
|
4221
|
+
<cb-icon [name]="tool === 'fullscreen' && fullscreen() ? 'minimize' : toolIcon(tool)" [size]="16" aria-hidden="true" />
|
|
4222
|
+
</button>
|
|
4223
|
+
}
|
|
4224
|
+
}
|
|
4225
|
+
</div>
|
|
4226
|
+
}
|
|
4227
|
+
</div>
|
|
4228
|
+
}
|
|
4229
|
+
|
|
4230
|
+
@if (findOpen() && !readonly()) {
|
|
4231
|
+
<div class="cb-editor__link cb-editor__find" role="search" [attr.aria-label]="t().findReplace" (keydown)="onFindKeydown($event)">
|
|
4232
|
+
<label class="cb-editor__link-field">
|
|
4233
|
+
<span class="cb-editor__link-label">{{ t().findText }}</span>
|
|
4234
|
+
<input #findInput class="cb-editor__link-input" type="text" autocomplete="off" [value]="findQuery()" (input)="setFindQuery($any($event.target).value)" />
|
|
4235
|
+
</label>
|
|
4236
|
+
<label class="cb-editor__link-field">
|
|
4237
|
+
<span class="cb-editor__link-label">{{ t().replaceWith }}</span>
|
|
4238
|
+
<input class="cb-editor__link-input cb-editor__replace-input" type="text" autocomplete="off" [value]="replaceText()" (input)="replaceText.set($any($event.target).value)" />
|
|
4239
|
+
</label>
|
|
4240
|
+
<div class="cb-editor__find-options">
|
|
4241
|
+
<button
|
|
4242
|
+
type="button"
|
|
4243
|
+
class="cb-editor__tool"
|
|
4244
|
+
[class.cb-is-active]="findCase()"
|
|
4245
|
+
[attr.aria-pressed]="findCase()"
|
|
4246
|
+
[attr.aria-label]="t().matchCase"
|
|
4247
|
+
[attr.title]="t().matchCase"
|
|
4248
|
+
(click)="setFindOption('case')"
|
|
4249
|
+
>
|
|
4250
|
+
<cb-icon name="case-sensitive" [size]="16" aria-hidden="true" />
|
|
4251
|
+
</button>
|
|
4252
|
+
<button
|
|
4253
|
+
type="button"
|
|
4254
|
+
class="cb-editor__tool"
|
|
4255
|
+
[class.cb-is-active]="findWhole()"
|
|
4256
|
+
[attr.aria-pressed]="findWhole()"
|
|
4257
|
+
[attr.aria-label]="t().wholeWords"
|
|
4258
|
+
[attr.title]="t().wholeWords"
|
|
4259
|
+
(click)="setFindOption('whole')"
|
|
4260
|
+
>
|
|
4261
|
+
<cb-icon name="whole-word" [size]="16" aria-hidden="true" />
|
|
4262
|
+
</button>
|
|
4263
|
+
<span class="cb-editor__find-count" role="status">{{ findStatus() }}</span>
|
|
4264
|
+
<button type="button" class="cb-editor__tool" [disabled]="!findTotal()" [attr.aria-label]="t().findPrevious" [attr.title]="t().findPrevious + ' (Shift+Enter)'" (click)="findStep(-1)">
|
|
4265
|
+
<cb-icon name="chevron-up" [size]="16" aria-hidden="true" />
|
|
4266
|
+
</button>
|
|
4267
|
+
<button type="button" class="cb-editor__tool" [disabled]="!findTotal()" [attr.aria-label]="t().findNext" [attr.title]="t().findNext + ' (Enter)'" (click)="findStep(1)">
|
|
4268
|
+
<cb-icon name="chevron-down" [size]="16" aria-hidden="true" />
|
|
4269
|
+
</button>
|
|
4270
|
+
</div>
|
|
4271
|
+
<div class="cb-editor__link-actions">
|
|
4272
|
+
<button type="button" class="cb-editor__btn" [disabled]="!findTotal()" (click)="replaceCurrent()">{{ t().replaceOne }}</button>
|
|
4273
|
+
<button type="button" class="cb-editor__btn" [disabled]="!findTotal()" (click)="replaceAllMatches()">{{ t().replaceAll }}</button>
|
|
4274
|
+
<button type="button" class="cb-editor__tool" [attr.aria-label]="t().close" [attr.title]="t().close + ' (Esc)'" (click)="closeFind()">
|
|
4275
|
+
<cb-icon name="x" [size]="16" aria-hidden="true" />
|
|
4276
|
+
</button>
|
|
4277
|
+
</div>
|
|
4278
|
+
</div>
|
|
4279
|
+
}
|
|
4280
|
+
|
|
4281
|
+
@if (draft(); as d) {
|
|
4282
|
+
<div class="cb-editor__draft" role="status">
|
|
4283
|
+
<span>{{ fmt(t().editorDraftFound, draftDate(d.savedAt)) }}</span>
|
|
4284
|
+
<button type="button" class="cb-editor__btn cb-editor__btn--primary" (click)="restoreDraft()">{{ t().editorRestoreDraft }}</button>
|
|
4285
|
+
<button type="button" class="cb-editor__btn" (click)="discardDraft()">{{ t().editorDiscardDraft }}</button>
|
|
4286
|
+
</div>
|
|
4287
|
+
}
|
|
4288
|
+
|
|
4289
|
+
@if (codeBlock() && !readonly() && !sourceMode()) {
|
|
4290
|
+
<div class="cb-editor__context" role="toolbar" [attr.aria-label]="t().editorCodeTools" (keydown)="onContextKeydown($event)">
|
|
4291
|
+
<span class="cb-editor__group-label" aria-hidden="true">{{ t().codeBlock }}</span>
|
|
4292
|
+
<select class="cb-editor__select" tabindex="0" [attr.aria-label]="t().editorCodeLanguage" (change)="setCodeLanguage($any($event.target).value)">
|
|
4293
|
+
@for (lang of codeLanguages; track lang) {
|
|
4294
|
+
<option [value]="lang" [selected]="codeLanguage() === lang">{{ lang ? languageLabel(lang) : t().editorPlainText }}</option>
|
|
4295
|
+
}
|
|
4296
|
+
</select>
|
|
4297
|
+
<button type="button" class="cb-editor__tool cb-editor__tool--labeled" tabindex="-1" (mousedown)="hold($event)" (click)="copyCode()">
|
|
4298
|
+
<cb-icon name="copy" [size]="15" aria-hidden="true" />{{ codeCopied() ? t().editorCodeCopied : t().editorCopyCode }}
|
|
4299
|
+
</button>
|
|
4300
|
+
</div>
|
|
4301
|
+
}
|
|
4302
|
+
|
|
4303
|
+
@if (tableCell() && !readonly() && !sourceMode()) {
|
|
4304
|
+
<div class="cb-editor__context cb-editor__context--table" role="toolbar" [attr.aria-label]="t().tableTools" (keydown)="onContextKeydown($event)">
|
|
4305
|
+
<div class="cb-editor__group" role="group" [attr.aria-label]="t().tableRow">
|
|
4306
|
+
<span class="cb-editor__group-label" aria-hidden="true">{{ t().tableRow }}</span>
|
|
4307
|
+
<button type="button" class="cb-editor__tool cb-editor__tool--labeled" tabindex="0" [attr.aria-label]="t().insertRowAbove" (mousedown)="hold($event)" (click)="tableAction('rowAbove')">
|
|
4308
|
+
<cb-icon name="between-horizontal-start" [size]="15" aria-hidden="true" />{{ t().above }}
|
|
4309
|
+
</button>
|
|
4310
|
+
<button type="button" class="cb-editor__tool cb-editor__tool--labeled" tabindex="-1" [attr.aria-label]="t().insertRowBelow" (mousedown)="hold($event)" (click)="tableAction('rowBelow')">
|
|
4311
|
+
<cb-icon name="between-horizontal-end" [size]="15" aria-hidden="true" />{{ t().below }}
|
|
4312
|
+
</button>
|
|
4313
|
+
<button type="button" class="cb-editor__tool cb-editor__tool--labeled cb-editor__tool--danger" tabindex="-1" [attr.aria-label]="t().deleteRow" (mousedown)="hold($event)" (click)="tableAction('deleteRow')">
|
|
4314
|
+
<cb-icon name="trash" [size]="15" aria-hidden="true" />{{ t().tableDelete }}
|
|
4315
|
+
</button>
|
|
4316
|
+
</div>
|
|
4317
|
+
<span class="cb-editor__sep" aria-hidden="true"></span>
|
|
4318
|
+
<div class="cb-editor__group" role="group" [attr.aria-label]="t().tableColumn">
|
|
4319
|
+
<span class="cb-editor__group-label" aria-hidden="true">{{ t().tableColumn }}</span>
|
|
4320
|
+
<button type="button" class="cb-editor__tool cb-editor__tool--labeled" tabindex="-1" [attr.aria-label]="t().insertColumnLeft" (mousedown)="hold($event)" (click)="tableAction('colLeft')">
|
|
4321
|
+
<cb-icon name="between-vertical-start" [size]="15" aria-hidden="true" />{{ t().toLeft }}
|
|
4322
|
+
</button>
|
|
4323
|
+
<button type="button" class="cb-editor__tool cb-editor__tool--labeled" tabindex="-1" [attr.aria-label]="t().insertColumnRight" (mousedown)="hold($event)" (click)="tableAction('colRight')">
|
|
4324
|
+
<cb-icon name="between-vertical-end" [size]="15" aria-hidden="true" />{{ t().toRight }}
|
|
4325
|
+
</button>
|
|
4326
|
+
<button type="button" class="cb-editor__tool cb-editor__tool--labeled cb-editor__tool--danger" tabindex="-1" [attr.aria-label]="t().deleteColumn" (mousedown)="hold($event)" (click)="tableAction('deleteCol')">
|
|
4327
|
+
<cb-icon name="trash" [size]="15" aria-hidden="true" />{{ t().tableDelete }}
|
|
4328
|
+
</button>
|
|
4329
|
+
</div>
|
|
4330
|
+
<span class="cb-editor__sep" aria-hidden="true"></span>
|
|
4331
|
+
<div class="cb-editor__group" role="group" [attr.aria-label]="t().editorTableCells">
|
|
4332
|
+
<span class="cb-editor__group-label" aria-hidden="true">{{ t().editorTableCells }}</span>
|
|
4333
|
+
<button type="button" class="cb-editor__tool cb-editor__tool--labeled" tabindex="-1" [disabled]="!cellActions().right" [attr.aria-label]="t().editorMergeRight" [attr.title]="t().editorMergeRight" (mousedown)="hold($event)" (click)="tableAction('mergeRight')">
|
|
4334
|
+
<cb-icon name="table-cells-merge" [size]="15" aria-hidden="true" />→
|
|
4335
|
+
</button>
|
|
4336
|
+
<button type="button" class="cb-editor__tool cb-editor__tool--labeled" tabindex="-1" [disabled]="!cellActions().down" [attr.aria-label]="t().editorMergeDown" [attr.title]="t().editorMergeDown" (mousedown)="hold($event)" (click)="tableAction('mergeDown')">
|
|
4337
|
+
<cb-icon name="table-cells-merge" [size]="15" aria-hidden="true" />↓
|
|
4338
|
+
</button>
|
|
4339
|
+
<button type="button" class="cb-editor__tool cb-editor__tool--labeled" tabindex="-1" [disabled]="!cellActions().split" [attr.aria-label]="t().editorSplitCell" [attr.title]="t().editorSplitCell" (mousedown)="hold($event)" (click)="tableAction('split')">
|
|
4340
|
+
<cb-icon name="table-cells-split" [size]="15" aria-hidden="true" />{{ t().editorSplitCell }}
|
|
4341
|
+
</button>
|
|
4342
|
+
</div>
|
|
4343
|
+
<span class="cb-editor__sep" aria-hidden="true"></span>
|
|
4344
|
+
<button
|
|
4345
|
+
type="button"
|
|
4346
|
+
class="cb-editor__tool cb-editor__tool--labeled"
|
|
4347
|
+
tabindex="-1"
|
|
4348
|
+
[class.cb-is-active]="tableHasHeader()"
|
|
4349
|
+
[attr.aria-pressed]="tableHasHeader()"
|
|
4350
|
+
[attr.aria-label]="t().tableHeaderRow"
|
|
4351
|
+
(mousedown)="hold($event)"
|
|
4352
|
+
(click)="tableAction('header')"
|
|
4353
|
+
>
|
|
4354
|
+
<cb-icon name="panel-top" [size]="15" aria-hidden="true" />{{ t().tableHeaderRow }}
|
|
4355
|
+
</button>
|
|
4356
|
+
<span class="cb-editor__sep" aria-hidden="true"></span>
|
|
4357
|
+
<select class="cb-editor__select" tabindex="-1" [attr.aria-label]="t().tableBorders" (change)="setTableBorders($any($event.target).value, null)">
|
|
4358
|
+
<option value="all" [selected]="tableBorders().borders === 'all'">{{ t().bordersAll }}</option>
|
|
4359
|
+
<option value="horizontal" [selected]="tableBorders().borders === 'horizontal'">{{ t().bordersHorizontal }}</option>
|
|
4360
|
+
<option value="none" [selected]="tableBorders().borders === 'none'">{{ t().bordersNone }}</option>
|
|
4361
|
+
</select>
|
|
4362
|
+
<select
|
|
4363
|
+
class="cb-editor__select"
|
|
4364
|
+
tabindex="-1"
|
|
4365
|
+
[attr.aria-label]="t().borderColor"
|
|
4366
|
+
[disabled]="tableBorders().borders === 'none'"
|
|
4367
|
+
(change)="setTableBorders(null, $any($event.target).value)"
|
|
4368
|
+
>
|
|
4369
|
+
@for (color of borderColors; track color.value) {
|
|
4370
|
+
<option [value]="color.value" [selected]="tableBorders().color === color.value">{{ color.label(t()) }}</option>
|
|
4371
|
+
}
|
|
4372
|
+
</select>
|
|
4373
|
+
<span class="cb-editor__sep" aria-hidden="true"></span>
|
|
4374
|
+
<button type="button" class="cb-editor__tool cb-editor__tool--labeled cb-editor__tool--danger" tabindex="-1" [attr.aria-label]="t().deleteTable" (mousedown)="hold($event)" (click)="tableAction('deleteTable')">
|
|
4375
|
+
<cb-icon name="trash" [size]="15" aria-hidden="true" />{{ t().deleteTable }}
|
|
4376
|
+
</button>
|
|
4377
|
+
</div>
|
|
4378
|
+
}
|
|
4379
|
+
|
|
4380
|
+
@if (selectedImage() && !readonly() && !sourceMode()) {
|
|
4381
|
+
<div class="cb-editor__context" role="toolbar" [attr.aria-label]="t().imageTools" (keydown)="onContextKeydown($event)">
|
|
4382
|
+
@for (option of imageAligns; track option.icon; let first = $first) {
|
|
4383
|
+
<button
|
|
4384
|
+
type="button"
|
|
4385
|
+
class="cb-editor__tool"
|
|
4386
|
+
[class.cb-is-active]="imageState().align === option.align"
|
|
4387
|
+
[attr.aria-pressed]="imageState().align === option.align"
|
|
4388
|
+
[attr.aria-label]="option.label(t())"
|
|
4389
|
+
[attr.title]="option.label(t())"
|
|
4390
|
+
[tabindex]="first ? 0 : -1"
|
|
4391
|
+
(mousedown)="hold($event)"
|
|
4392
|
+
(click)="setImageAlign(option.align)"
|
|
4393
|
+
>
|
|
4394
|
+
<cb-icon [name]="option.icon" [size]="16" aria-hidden="true" />
|
|
4395
|
+
</button>
|
|
4396
|
+
}
|
|
4397
|
+
<span class="cb-editor__sep" aria-hidden="true"></span>
|
|
4398
|
+
@for (width of imageWidths; track width) {
|
|
4399
|
+
<button
|
|
4400
|
+
type="button"
|
|
4401
|
+
class="cb-editor__tool cb-editor__tool--text"
|
|
4402
|
+
[class.cb-is-active]="imageState().width === width"
|
|
4403
|
+
[attr.aria-pressed]="imageState().width === width"
|
|
4404
|
+
[attr.aria-label]="imageWidthLabel(width)"
|
|
4405
|
+
[tabindex]="-1"
|
|
4406
|
+
(mousedown)="hold($event)"
|
|
4407
|
+
(click)="setImageWidth(width)"
|
|
4408
|
+
>{{ width }}</button>
|
|
4409
|
+
}
|
|
4410
|
+
<button
|
|
4411
|
+
type="button"
|
|
4412
|
+
class="cb-editor__tool"
|
|
4413
|
+
[class.cb-is-active]="!imageState().width"
|
|
4414
|
+
[attr.aria-pressed]="!imageState().width"
|
|
4415
|
+
[attr.aria-label]="t().imageOriginalSize"
|
|
4416
|
+
[attr.title]="t().imageOriginalSize"
|
|
4417
|
+
[tabindex]="-1"
|
|
4418
|
+
(mousedown)="hold($event)"
|
|
4419
|
+
(click)="setImageWidth(null)"
|
|
4420
|
+
>
|
|
4421
|
+
<cb-icon name="ratio" [size]="16" aria-hidden="true" />
|
|
4422
|
+
</button>
|
|
4423
|
+
<span class="cb-editor__sep" aria-hidden="true"></span>
|
|
4424
|
+
<button type="button" class="cb-editor__tool cb-editor__tool--labeled" [attr.aria-label]="t().cropImage" [tabindex]="-1" (click)="openCrop()">
|
|
4425
|
+
<cb-icon name="crop" [size]="15" aria-hidden="true" />{{ t().cropImage }}
|
|
4426
|
+
</button>
|
|
4427
|
+
<button
|
|
4428
|
+
type="button"
|
|
4429
|
+
class="cb-editor__tool cb-editor__tool--labeled"
|
|
4430
|
+
[class.cb-is-active]="imageHasCaption()"
|
|
4431
|
+
[attr.aria-pressed]="imageHasCaption()"
|
|
4432
|
+
[attr.aria-label]="t().editorImageCaption"
|
|
4433
|
+
[tabindex]="-1"
|
|
4434
|
+
(mousedown)="hold($event)"
|
|
4435
|
+
(click)="toggleCaption()"
|
|
4436
|
+
>
|
|
4437
|
+
<cb-icon name="captions" [size]="15" aria-hidden="true" />{{ t().editorImageCaption }}
|
|
4438
|
+
</button>
|
|
4439
|
+
<button type="button" class="cb-editor__tool" [attr.aria-label]="t().altText" [attr.title]="t().altText" [tabindex]="-1" (click)="openAlt()">
|
|
4440
|
+
<cb-icon name="captions" [size]="16" aria-hidden="true" />
|
|
4441
|
+
</button>
|
|
4442
|
+
<button
|
|
4443
|
+
type="button"
|
|
4444
|
+
class="cb-editor__tool cb-editor__tool--danger"
|
|
4445
|
+
[attr.aria-label]="t().deleteImage"
|
|
4446
|
+
[attr.title]="t().deleteImage"
|
|
4447
|
+
[tabindex]="-1"
|
|
4448
|
+
(mousedown)="hold($event)"
|
|
4449
|
+
(click)="deleteImage()"
|
|
4450
|
+
>
|
|
4451
|
+
<cb-icon name="trash" [size]="16" aria-hidden="true" />
|
|
4452
|
+
</button>
|
|
4453
|
+
</div>
|
|
4454
|
+
}
|
|
4455
|
+
|
|
4456
|
+
@if (tablePanel()) {
|
|
4457
|
+
<div class="cb-editor__link cb-editor__picker-panel" role="group" [attr.aria-label]="t().insertTable" (keydown.escape)="closeTablePanel()">
|
|
4458
|
+
<div class="cb-editor__picker" (mouseleave)="pickerHover.set(null)">
|
|
4459
|
+
@for (r of pickerRows; track r) {
|
|
4460
|
+
<div class="cb-editor__picker-row">
|
|
4461
|
+
@for (c of pickerCols; track c) {
|
|
4462
|
+
<button
|
|
4463
|
+
type="button"
|
|
4464
|
+
class="cb-editor__picker-cell"
|
|
4465
|
+
[class.cb-is-on]="pickerOn(r, c)"
|
|
4466
|
+
[attr.aria-label]="tableSizeLabel(r, c)"
|
|
4467
|
+
[tabindex]="r === pickerFocus().r && c === pickerFocus().c ? 0 : -1"
|
|
4468
|
+
(mouseenter)="pickerHover.set({ r, c })"
|
|
4469
|
+
(focus)="pickerHover.set({ r, c }); pickerFocus.set({ r, c })"
|
|
4470
|
+
(keydown)="onPickerKeydown($event, r, c)"
|
|
4471
|
+
(click)="insertTable(r, c)"
|
|
4472
|
+
></button>
|
|
4473
|
+
}
|
|
4474
|
+
</div>
|
|
4475
|
+
}
|
|
4476
|
+
</div>
|
|
4477
|
+
<div class="cb-editor__picker-side">
|
|
4478
|
+
<span class="cb-editor__picker-size" aria-live="polite">{{ pickerHover() ? tableSizeLabel(pickerHover()!.r, pickerHover()!.c) : t().insertTable }}</span>
|
|
4479
|
+
<label class="cb-editor__link-check">
|
|
4480
|
+
<input type="checkbox" [checked]="tableHeader()" (change)="tableHeader.set($any($event.target).checked)" />
|
|
4481
|
+
{{ t().tableHeaderRow }}
|
|
4482
|
+
</label>
|
|
4483
|
+
<button type="button" class="cb-editor__btn" (click)="closeTablePanel()">{{ t().cancel }}</button>
|
|
4484
|
+
</div>
|
|
4485
|
+
</div>
|
|
4486
|
+
}
|
|
4487
|
+
|
|
4488
|
+
@if (crop(); as c) {
|
|
4489
|
+
<div class="cb-editor__crop" role="group" [attr.aria-label]="t().cropImage" (keydown.escape)="closeCrop()">
|
|
4490
|
+
<div class="cb-editor__crop-stage" #cropStage>
|
|
4491
|
+
<img class="cb-editor__crop-img" [src]="c.src" alt="" draggable="false" />
|
|
4492
|
+
<div
|
|
4493
|
+
class="cb-editor__crop-box"
|
|
4494
|
+
role="slider"
|
|
4495
|
+
tabindex="0"
|
|
4496
|
+
[attr.aria-label]="t().cropArea"
|
|
4497
|
+
[attr.aria-valuetext]="cropValueText()"
|
|
4498
|
+
[attr.aria-describedby]="cropHelpId"
|
|
4499
|
+
[style.left.%]="c.rect.x * 100"
|
|
4500
|
+
[style.top.%]="c.rect.y * 100"
|
|
4501
|
+
[style.width.%]="c.rect.w * 100"
|
|
4502
|
+
[style.height.%]="c.rect.h * 100"
|
|
4503
|
+
(pointerdown)="startCropDrag($event, null)"
|
|
4504
|
+
(keydown)="onCropKeydown($event)"
|
|
4505
|
+
>
|
|
4506
|
+
@for (corner of cropCorners; track corner) {
|
|
4507
|
+
<span class="cb-editor__crop-handle" [class]="'cb-editor__crop-handle cb-editor__crop-handle--' + corner" (pointerdown)="startCropDrag($event, corner)"></span>
|
|
4508
|
+
}
|
|
4509
|
+
</div>
|
|
4510
|
+
</div>
|
|
4511
|
+
<p class="cb-editor__crop-help" [id]="cropHelpId">{{ t().cropInstructions }}</p>
|
|
4512
|
+
<div class="cb-editor__link-actions">
|
|
4513
|
+
@for (ratio of cropRatios; track ratio.label) {
|
|
4514
|
+
<button
|
|
4515
|
+
type="button"
|
|
4516
|
+
class="cb-editor__btn"
|
|
4517
|
+
[class.cb-is-active]="c.ratio === ratio.value"
|
|
4518
|
+
[attr.aria-pressed]="c.ratio === ratio.value"
|
|
4519
|
+
(click)="setCropRatio(ratio.value)"
|
|
4520
|
+
>{{ ratio.value === null ? t().cropFree : ratio.label }}</button>
|
|
4521
|
+
}
|
|
4522
|
+
<span class="cb-editor__crop-spacer"></span>
|
|
4523
|
+
<button type="button" class="cb-editor__btn cb-editor__btn--primary" [disabled]="c.busy" (click)="applyCrop()">{{ t().cropImage }}</button>
|
|
4524
|
+
<button type="button" class="cb-editor__btn" (click)="closeCrop()">{{ t().cancel }}</button>
|
|
4525
|
+
</div>
|
|
4526
|
+
@if (c.error) {
|
|
4527
|
+
<p class="cb-editor__error" role="alert">{{ t().cropError }}</p>
|
|
4528
|
+
}
|
|
4529
|
+
</div>
|
|
4530
|
+
}
|
|
4531
|
+
|
|
4532
|
+
@if (altPanel()) {
|
|
4533
|
+
<form class="cb-editor__link" role="group" [attr.aria-label]="t().altText" (submit)="applyAlt($event)" (keydown.escape)="closeAlt()">
|
|
4534
|
+
<label class="cb-editor__link-field">
|
|
4535
|
+
<span class="cb-editor__link-label">{{ t().altText }}</span>
|
|
4536
|
+
<input #altInput class="cb-editor__link-input" type="text" [value]="selectedImage()?.getAttribute('alt') ?? ''" />
|
|
4537
|
+
</label>
|
|
4538
|
+
<div class="cb-editor__link-actions">
|
|
4539
|
+
<button type="submit" class="cb-editor__btn cb-editor__btn--primary">{{ t().accept }}</button>
|
|
4540
|
+
<button type="button" class="cb-editor__btn" (click)="closeAlt()">{{ t().cancel }}</button>
|
|
4541
|
+
</div>
|
|
4542
|
+
</form>
|
|
4543
|
+
}
|
|
4544
|
+
|
|
4545
|
+
@if (linkPanel(); as panel) {
|
|
4546
|
+
<form class="cb-editor__link" role="group" [attr.aria-label]="t().insertLink" (submit)="applyLink($event)" (keydown.escape)="closeLink()">
|
|
4547
|
+
<label class="cb-editor__link-field">
|
|
4548
|
+
<span class="cb-editor__link-label">{{ t().linkUrl }}</span>
|
|
4549
|
+
<input
|
|
4550
|
+
#linkInput
|
|
4551
|
+
class="cb-editor__link-input"
|
|
4552
|
+
type="text"
|
|
4553
|
+
inputmode="url"
|
|
4554
|
+
autocomplete="off"
|
|
4555
|
+
[value]="panel.url"
|
|
4556
|
+
[attr.aria-invalid]="linkError() ? 'true' : null"
|
|
4557
|
+
[attr.aria-describedby]="linkError() ? linkErrorId : null"
|
|
4558
|
+
(input)="linkUrl.set($any($event.target).value)"
|
|
4559
|
+
/>
|
|
4560
|
+
</label>
|
|
4561
|
+
<label class="cb-editor__link-check">
|
|
4562
|
+
<input type="checkbox" [checked]="panel.newTab" (change)="linkNewTab.set($any($event.target).checked)" />
|
|
4563
|
+
{{ t().openInNewTab }}
|
|
4564
|
+
</label>
|
|
4565
|
+
<div class="cb-editor__link-actions">
|
|
4566
|
+
<button type="submit" class="cb-editor__btn cb-editor__btn--primary">{{ t().accept }}</button>
|
|
4567
|
+
@if (panel.editing) {
|
|
4568
|
+
<button type="button" class="cb-editor__btn" (click)="removeLink()">
|
|
4569
|
+
<cb-icon name="unlink" [size]="14" aria-hidden="true" />
|
|
4570
|
+
{{ t().removeLink }}
|
|
4571
|
+
</button>
|
|
4572
|
+
}
|
|
4573
|
+
<button type="button" class="cb-editor__btn" (click)="closeLink()">{{ t().cancel }}</button>
|
|
4574
|
+
</div>
|
|
4575
|
+
@if (linkError()) {
|
|
4576
|
+
<p class="cb-editor__error" [id]="linkErrorId" role="alert">{{ t().invalidUrl }}</p>
|
|
4577
|
+
}
|
|
4578
|
+
</form>
|
|
4579
|
+
}
|
|
4580
|
+
|
|
4581
|
+
</div>
|
|
4582
|
+
|
|
4583
|
+
<div #body class="cb-editor__body">
|
|
4584
|
+
<div
|
|
4585
|
+
#surface
|
|
4586
|
+
class="cb-editor__surface"
|
|
4587
|
+
[id]="surfaceId"
|
|
4588
|
+
role="textbox"
|
|
4589
|
+
[hidden]="sourceMode()"
|
|
4590
|
+
[class.cb-is-empty]="isEmpty()"
|
|
4591
|
+
[attr.aria-label]="ariaLabel() ?? t().textEditor"
|
|
4592
|
+
[attr.aria-disabled]="isDisabled() || null"
|
|
4593
|
+
[attr.aria-readonly]="readonly() || null"
|
|
4594
|
+
[attr.aria-invalid]="invalidState() ? 'true' : null"
|
|
4595
|
+
[attr.aria-required]="fieldState.required() || null"
|
|
4596
|
+
[attr.aria-describedby]="describedBy()"
|
|
4597
|
+
aria-multiline="true"
|
|
4598
|
+
[attr.aria-activedescendant]="slash() && slashItems().length ? slashOptionId(slash()!.active) : mention() && mentionResults().length ? mentionOptionId(mention()!.active) : null"
|
|
4599
|
+
[attr.contenteditable]="!isDisabled() && !readonly()"
|
|
4600
|
+
[attr.data-placeholder]="readonly() ? '' : (placeholder() ?? t().editorPlaceholder)"
|
|
4601
|
+
[style.minHeight]="readonly() ? null : height()"
|
|
4602
|
+
[style.maxHeight]="readonly() || fullscreen() ? null : maxHeight()"
|
|
4603
|
+
(input)="onSurfaceInput($event)"
|
|
4604
|
+
(beforeinput)="onBeforeInput($event)"
|
|
4605
|
+
(keydown)="onSurfaceKeydown($event)"
|
|
4606
|
+
(paste)="onPaste($event)"
|
|
4607
|
+
(drop)="onDrop($event)"
|
|
4608
|
+
(click)="onSurfaceClick($event)"
|
|
4609
|
+
(mousedown)="pointerSelecting = true"
|
|
4610
|
+
(scroll)="refreshFrame()"
|
|
4611
|
+
(blur)="onBlur()"
|
|
4612
|
+
></div>
|
|
4613
|
+
@if (imageFrame(); as frame) {
|
|
4614
|
+
<div
|
|
4615
|
+
class="cb-editor__img-frame"
|
|
4616
|
+
aria-hidden="true"
|
|
4617
|
+
[style.left.px]="frame.left"
|
|
4618
|
+
[style.top.px]="frame.top"
|
|
4619
|
+
[style.width.px]="frame.width"
|
|
4620
|
+
[style.height.px]="frame.height"
|
|
4621
|
+
>
|
|
4622
|
+
<span class="cb-editor__img-handle" [attr.title]="t().imageResize" (pointerdown)="startResize($event)"></span>
|
|
4623
|
+
</div>
|
|
4624
|
+
}
|
|
4625
|
+
@if (bubble(); as b) {
|
|
4626
|
+
<div
|
|
4627
|
+
class="cb-editor__bubble"
|
|
4628
|
+
[class.cb-editor__bubble--below]="b.below"
|
|
4629
|
+
role="toolbar"
|
|
4630
|
+
[attr.aria-label]="t().quickFormat"
|
|
4631
|
+
[style.left.px]="b.left"
|
|
4632
|
+
[style.top.px]="b.top"
|
|
4633
|
+
(keydown)="onContextKeydown($event)"
|
|
4634
|
+
>
|
|
4635
|
+
@for (tool of bubbleTools; track tool; let first = $first) {
|
|
4636
|
+
<button
|
|
4637
|
+
type="button"
|
|
4638
|
+
class="cb-editor__tool"
|
|
4639
|
+
[class.cb-is-active]="isActive(tool)"
|
|
4640
|
+
[attr.aria-pressed]="pressable(tool) ? isActive(tool) : null"
|
|
4641
|
+
[attr.aria-label]="toolLabel(tool)"
|
|
4642
|
+
[attr.title]="toolTitle(tool)"
|
|
4643
|
+
[tabindex]="first ? 0 : -1"
|
|
4644
|
+
(mousedown)="hold($event)"
|
|
4645
|
+
(click)="runBubble(tool)"
|
|
4646
|
+
>
|
|
4647
|
+
<cb-icon [name]="toolIcon(tool)" [size]="16" aria-hidden="true" />
|
|
4648
|
+
</button>
|
|
4649
|
+
}
|
|
4650
|
+
</div>
|
|
4651
|
+
}
|
|
4652
|
+
@if (mention(); as m) {
|
|
4653
|
+
@if (mentionResults().length) {
|
|
4654
|
+
<div class="cb-editor__slash cb-editor__mentions" role="listbox" [id]="mentionListId" [attr.aria-label]="t().editorMentions" [style.left.px]="m.left" [style.top.px]="m.top">
|
|
4655
|
+
@for (item of mentionResults(); track item.id; let i = $index) {
|
|
4656
|
+
<div
|
|
4657
|
+
class="cb-editor__slash-item"
|
|
4658
|
+
role="option"
|
|
4659
|
+
[id]="mentionOptionId(i)"
|
|
4660
|
+
[class.cb-is-active]="i === m.active"
|
|
4661
|
+
[attr.aria-selected]="i === m.active"
|
|
4662
|
+
(mousedown)="hold($event)"
|
|
4663
|
+
(click)="applyMention(item)"
|
|
4664
|
+
>
|
|
4665
|
+
<cb-icon name="at-sign" [size]="15" aria-hidden="true" />
|
|
4666
|
+
<span class="cb-editor__mention-label">{{ item.label }}</span>
|
|
4667
|
+
@if (item.description) {
|
|
4668
|
+
<span class="cb-editor__mention-desc">{{ item.description }}</span>
|
|
4669
|
+
}
|
|
4670
|
+
</div>
|
|
4671
|
+
}
|
|
4672
|
+
</div>
|
|
4673
|
+
}
|
|
4674
|
+
}
|
|
4675
|
+
@if (slash(); as sl) {
|
|
4676
|
+
@if (slashItems().length) {
|
|
4677
|
+
<div class="cb-editor__slash" role="listbox" [id]="slashListId" [attr.aria-label]="t().insertBlock" [style.left.px]="sl.left" [style.top.px]="sl.top">
|
|
4678
|
+
@for (item of slashItems(); track item.id; let i = $index) {
|
|
4679
|
+
<div
|
|
4680
|
+
class="cb-editor__slash-item"
|
|
4681
|
+
role="option"
|
|
4682
|
+
[id]="slashOptionId(i)"
|
|
4683
|
+
[class.cb-is-active]="i === sl.active"
|
|
4684
|
+
[attr.aria-selected]="i === sl.active"
|
|
4685
|
+
(mousedown)="hold($event)"
|
|
4686
|
+
(click)="applySlash(item)"
|
|
4687
|
+
>
|
|
4688
|
+
<cb-icon [name]="item.icon" [size]="16" aria-hidden="true" />
|
|
4689
|
+
{{ item.label(t()) }}
|
|
4690
|
+
</div>
|
|
4691
|
+
}
|
|
4692
|
+
</div>
|
|
4693
|
+
}
|
|
4694
|
+
}
|
|
4695
|
+
</div>
|
|
4696
|
+
|
|
4697
|
+
@if (sourceMode()) {
|
|
4698
|
+
<textarea
|
|
4699
|
+
class="cb-editor__source"
|
|
4700
|
+
spellcheck="false"
|
|
4701
|
+
[attr.aria-label]="t().sourceCode"
|
|
4702
|
+
[style.minHeight]="height()"
|
|
4703
|
+
[value]="sourceText()"
|
|
4704
|
+
(input)="sourceText.set($any($event.target).value)"
|
|
4705
|
+
(blur)="onBlur()"
|
|
4706
|
+
></textarea>
|
|
4707
|
+
}
|
|
4708
|
+
|
|
4709
|
+
@if (message() || maxLength() || (wordCount() && !readonly()) || savedAt()) {
|
|
4710
|
+
<div class="cb-editor__footer">
|
|
4711
|
+
@if (message()) {
|
|
4712
|
+
<p class="cb-editor__error" role="alert">{{ message() }}</p>
|
|
4713
|
+
}
|
|
4714
|
+
@if (savedAt(); as at) {
|
|
4715
|
+
<span class="cb-editor__saved">{{ fmt(t().editorDraftSaved, draftTime(at)) }}</span>
|
|
4716
|
+
}
|
|
4717
|
+
@if (maxLength() || (wordCount() && !readonly())) {
|
|
4718
|
+
<span class="cb-editor__count" [id]="countId" [class.cb-is-over]="!!maxLength() && length() > maxLength()!">{{ countLine() }}</span>
|
|
4719
|
+
}
|
|
4720
|
+
</div>
|
|
4721
|
+
}
|
|
4722
|
+
|
|
4723
|
+
<input #imageInput type="file" accept="image/png,image/jpeg,image/gif,image/webp" hidden (change)="onImageFile($event)" />
|
|
4724
|
+
`, isInline: true, styles: [":host{display:block;width:100%;border:var(--cb-border-width) solid var(--cb-color-border);border-radius:var(--cb-radius-lg);background:var(--cb-color-surface);color:var(--cb-color-text);font-family:var(--cb-font-sans);overflow:clip}.cb-editor__header{position:sticky;top:var(--cb-editor-sticky-top, 0px);z-index:3;background:var(--cb-color-surface)}.cb-editor__toolbar{display:grid;gap:.25rem;padding:var(--cb-space-2) var(--cb-space-3);border-bottom:var(--cb-border-width) solid var(--cb-color-border);background:var(--cb-color-surface-subtle, var(--cb-color-surface))}.cb-editor__row{display:flex;align-items:center;flex-wrap:wrap;gap:.2rem}.cb-editor__row+.cb-editor__row{padding-top:.25rem;border-top:1px dashed var(--cb-color-border)}.cb-editor__select--font{width:10.5rem}.cb-editor__select--size{width:5.5rem}.cb-editor__select{height:1.75rem;padding:0 .4rem;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-sm);background:var(--cb-color-bg);color:var(--cb-color-text);font:inherit;font-size:var(--cb-text-sm);cursor:pointer}.cb-editor__sep{width:1px;height:1.1rem;margin:0 .25rem;background:var(--cb-color-border)}.cb-editor__tool{display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;border:none;border-radius:var(--cb-radius-sm);background:transparent;color:var(--cb-color-text);cursor:pointer}.cb-editor__tool:hover{background:color-mix(in srgb,var(--cb-color-primary-500) 12%,transparent)}.cb-editor__tool:disabled{opacity:.4;cursor:not-allowed}.cb-editor__tool:focus-visible,.cb-editor__select:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:1px}.cb-editor__tool.cb-is-active{color:var(--cb-color-primary-700);background:color-mix(in srgb,var(--cb-color-primary-500) 18%,transparent)}:host-context([data-theme=\"dark\"]) .cb-editor__tool.cb-is-active{color:var(--cb-color-primary-300)}@media(forced-colors:active){.cb-editor__tool.cb-is-active{outline:2px solid Highlight}}.cb-editor__link{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--cb-space-2) var(--cb-space-3);padding:var(--cb-space-2) var(--cb-space-3);border-bottom:var(--cb-border-width) solid var(--cb-color-border);background:var(--cb-color-surface-subtle, var(--cb-color-surface));font-size:var(--cb-text-sm)}.cb-editor__link-field{display:grid;flex:1 1 14rem;gap:.2rem}.cb-editor__link-label{font-size:var(--cb-text-xs);color:var(--cb-color-text-muted)}.cb-editor__link-input{height:2rem;padding:0 .5rem;border:1px solid var(--cb-color-border-strong, var(--cb-color-border));border-radius:var(--cb-radius-sm);background:var(--cb-color-bg, var(--cb-color-surface));color:var(--cb-color-text);font:inherit}.cb-editor__link-input[aria-invalid=true]{border-color:var(--cb-color-danger-500)}.cb-editor__link-input:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:0}.cb-editor__link-check{display:inline-flex;align-items:center;gap:.35rem;min-height:2rem}.cb-editor__link-actions{display:inline-flex;gap:var(--cb-space-2)}.cb-editor__btn{display:inline-flex;align-items:center;gap:.3rem;height:2rem;padding:0 .7rem;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-sm);background:var(--cb-color-surface);color:var(--cb-color-text);font:inherit;cursor:pointer}.cb-editor__btn:hover{background:var(--cb-color-surface-hover)}.cb-editor__btn:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:1px}.cb-editor__btn--primary{color:#fff;background:var(--cb-color-primary-700, #047857);border-color:var(--cb-color-primary-600)}.cb-editor__btn--primary:hover{background:var(--cb-color-primary-700)}.cb-editor__error{flex-basis:100%;margin:0;font-size:var(--cb-text-xs);color:var(--cb-color-danger-600)}.cb-editor__surface{padding:var(--cb-space-3);font-family:var(--cb-editor-font-family, Carlito, Calibri, var(--cb-font-sans), sans-serif);font-size:var(--cb-editor-font-size, 16px);line-height:1.6;outline:none;overflow-y:auto}.cb-editor__surface:empty:before,.cb-editor__surface.cb-is-empty:not(:focus):before{content:attr(data-placeholder);color:var(--cb-color-text-muted);pointer-events:none}.cb-editor__surface.cb-is-empty:not(:empty):before{position:absolute}.cb-editor__surface{position:relative}.cb-editor__source{display:block;box-sizing:border-box;width:100%;padding:var(--cb-space-3);border:none;resize:vertical;background:var(--cb-color-surface-subtle, var(--cb-color-surface));color:var(--cb-color-text);font-family:var(--cb-font-mono, monospace);font-size:var(--cb-text-xs);line-height:1.6;outline:none}.cb-editor__footer{display:flex;align-items:center;justify-content:space-between;gap:var(--cb-space-3);padding:var(--cb-space-1) var(--cb-space-3) var(--cb-space-2);border-top:var(--cb-border-width) solid var(--cb-color-border)}.cb-editor__count{margin-left:auto;font-size:var(--cb-text-xs);color:var(--cb-color-text-muted);font-variant-numeric:tabular-nums}.cb-editor__count.cb-is-over{color:var(--cb-color-danger-600);font-weight:var(--cb-font-weight-semibold)}:host(.cb-is-readonly){border-color:transparent;background:transparent}:host(.cb-is-readonly) .cb-editor__surface{padding:0}:host ::ng-deep .cb-editor__surface h1{font-size:1.5rem;margin:.4em 0}:host ::ng-deep .cb-editor__surface h2{font-size:1.25rem;margin:.4em 0}:host ::ng-deep .cb-editor__surface h3{font-size:1.1rem;margin:.4em 0}:host ::ng-deep .cb-editor__surface p{margin:.4em 0}:host ::ng-deep .cb-editor__surface ul,:host ::ng-deep .cb-editor__surface ol{margin:.4em 0;padding-left:1.5rem}:host ::ng-deep .cb-editor__surface blockquote{margin:.5em 0;padding:.25em .9em;border-left:3px solid var(--cb-color-primary-400, var(--cb-color-border));color:var(--cb-color-text-muted)}:host ::ng-deep .cb-editor__surface a{color:var(--cb-color-primary-text);text-decoration:underline}:host ::ng-deep .cb-editor__surface code{padding:.1em .35em;border-radius:var(--cb-radius-sm);background:var(--cb-color-surface-subtle, rgb(0 0 0 / .06));font-family:var(--cb-font-mono, monospace);font-size:.9em}:host ::ng-deep .cb-editor__surface pre{padding:var(--cb-space-3);border-radius:var(--cb-radius-md);background:var(--cb-color-surface-subtle, rgb(0 0 0 / .06));font-family:var(--cb-font-mono, monospace);white-space:pre-wrap}:host ::ng-deep .cb-editor__surface hr{margin:var(--cb-space-3) 0;border:none;border-top:1px solid var(--cb-color-border-strong, var(--cb-color-border))}:host ::ng-deep .cb-editor__surface img{max-width:100%;height:auto;border-radius:var(--cb-radius-sm, 4px)}:host(.cb-is-invalid){border-color:var(--cb-color-danger)}.cb-editor__context{display:flex;align-items:center;flex-wrap:wrap;gap:.2rem;padding:var(--cb-space-1) var(--cb-space-3);border-bottom:var(--cb-border-width) solid var(--cb-color-border);background:color-mix(in srgb,var(--cb-color-primary-500) 6%,var(--cb-color-surface))}.cb-editor__tool--text{width:auto;padding:0 .4rem;font:inherit;font-size:var(--cb-text-xs);font-variant-numeric:tabular-nums}.cb-editor__tool--danger:hover{color:var(--cb-color-danger-600);background:color-mix(in srgb,var(--cb-color-danger-500) 12%,transparent)}.cb-editor__link-field--narrow{flex:0 0 6rem}.cb-editor__body{position:relative}:host ::ng-deep .cb-editor__surface img{cursor:pointer}.cb-editor__img-frame{position:absolute;box-sizing:border-box;border:2px solid var(--cb-color-primary-500);border-radius:2px;pointer-events:none}.cb-editor__img-handle{position:absolute;right:-6px;bottom:-6px;width:12px;height:12px;border:2px solid var(--cb-color-surface);border-radius:2px;background:var(--cb-color-primary-500);cursor:nwse-resize;pointer-events:auto;touch-action:none}:host ::ng-deep .cb-editor__surface table{width:100%;margin:var(--cb-space-2) 0;border-collapse:collapse;table-layout:fixed}:host ::ng-deep .cb-editor__surface th,:host ::ng-deep .cb-editor__surface td{min-width:2rem;padding:.35rem .5rem;vertical-align:top;word-break:break-word;outline:1px dashed color-mix(in srgb,var(--cb-color-text-muted) 45%,transparent);outline-offset:-1px}:host ::ng-deep .cb-editor__surface th[style*=border],:host ::ng-deep .cb-editor__surface td[style*=border]{outline:none}:host ::ng-deep .cb-editor__surface th{font-weight:var(--cb-font-weight-semibold, 600);text-align:left}:host(.cb-is-readonly) ::ng-deep .cb-editor__surface th,:host(.cb-is-readonly) ::ng-deep .cb-editor__surface td{outline:none}:host ::ng-deep .cb-editor__surface pre code{padding:0;background:transparent;color:inherit;font-size:inherit}:host ::ng-deep .cb-editor__surface pre[data-language]:before{content:attr(data-language);float:right;margin:-.25rem -.25rem 0 .5rem;font-family:var(--cb-font-sans);font-size:.7rem;opacity:.6}.cb-editor__group{display:inline-flex;align-items:center;gap:.2rem}.cb-editor__group-label{margin-right:.15rem;font-size:var(--cb-text-xs);font-weight:var(--cb-font-weight-semibold, 600);color:var(--cb-color-text-muted)}.cb-editor__tool--labeled{width:auto;gap:.3rem;padding:0 .45rem;font:inherit;font-size:var(--cb-text-xs);white-space:nowrap}.cb-editor__picker-panel{align-items:flex-start}.cb-editor__picker{display:grid;gap:3px}.cb-editor__picker-row{display:flex;gap:3px}.cb-editor__picker-cell{width:1.1rem;height:1.1rem;padding:0;border:1px solid var(--cb-color-border-strong, var(--cb-color-border));border-radius:2px;background:var(--cb-color-surface);cursor:pointer}.cb-editor__picker-cell.cb-is-on{border-color:var(--cb-color-primary-600);background:color-mix(in srgb,var(--cb-color-primary-500) 35%,var(--cb-color-surface))}.cb-editor__picker-cell:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:1px}.cb-editor__picker-side{display:grid;gap:var(--cb-space-2);align-content:start}.cb-editor__picker-size{font-weight:var(--cb-font-weight-semibold, 600);font-variant-numeric:tabular-nums}.cb-editor__crop{display:grid;gap:var(--cb-space-2);padding:var(--cb-space-3);border-bottom:var(--cb-border-width) solid var(--cb-color-border);background:var(--cb-color-surface-subtle, var(--cb-color-surface))}.cb-editor__crop-stage{position:relative;justify-self:center;max-width:100%;overflow:hidden;-webkit-user-select:none;user-select:none;touch-action:none}.cb-editor__crop-img{display:block;max-width:100%;max-height:22rem}.cb-editor__crop-box{position:absolute;box-sizing:border-box;border:2px solid #fff;box-shadow:0 0 0 9999px #0000008c;cursor:move;outline:none}.cb-editor__crop-box:focus-visible{border-color:var(--cb-color-focus-ring)}.cb-editor__crop-handle{position:absolute;width:14px;height:14px;border:2px solid var(--cb-color-primary-600);border-radius:2px;background:#fff}.cb-editor__crop-handle--nw{left:-8px;top:-8px;cursor:nwse-resize}.cb-editor__crop-handle--ne{right:-8px;top:-8px;cursor:nesw-resize}.cb-editor__crop-handle--sw{left:-8px;bottom:-8px;cursor:nesw-resize}.cb-editor__crop-handle--se{right:-8px;bottom:-8px;cursor:nwse-resize}.cb-editor__crop-help{margin:0;font-size:var(--cb-text-xs);color:var(--cb-color-text-muted)}.cb-editor__crop-spacer{flex:1}.cb-editor__btn.cb-is-active{border-color:var(--cb-color-primary-600);color:var(--cb-color-primary-700);background:color-mix(in srgb,var(--cb-color-primary-500) 14%,var(--cb-color-surface))}.cb-editor__crop .cb-editor__link-actions{display:flex;flex-wrap:wrap}.cb-editor__select--size{width:7.5rem}.cb-editor__menu-wrap{position:relative;display:inline-flex}.cb-editor__menu-trigger{position:relative;width:auto;gap:1px;padding:0 .25rem}.cb-editor__color-bar{position:absolute;left:.35rem;right:1rem;bottom:.2rem;height:3px;border-radius:1px}.cb-editor__caret{opacity:.7}.cb-editor__menu{position:absolute;top:calc(100% + 4px);left:0;z-index:10;display:grid;gap:2px;min-width:12rem;padding:.35rem;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-md);background:var(--cb-color-surface-raised, var(--cb-color-surface));box-shadow:var(--cb-shadow-md, 0 8px 24px rgb(0 0 0 / .12))}.cb-editor__menu-item{display:flex;align-items:center;gap:.5rem;width:100%;padding:.4rem .5rem;border:none;border-radius:var(--cb-radius-sm);background:transparent;color:var(--cb-color-text);font:inherit;font-size:var(--cb-text-sm);text-align:left;cursor:pointer}.cb-editor__menu-item:hover,.cb-editor__menu-item:focus-visible{background:var(--cb-color-surface-hover);outline:none}.cb-editor__menu-item:focus-visible{box-shadow:inset 0 0 0 2px var(--cb-color-focus-ring)}.cb-editor__menu-item[aria-checked=true]{font-weight:var(--cb-font-weight-semibold, 600);color:var(--cb-color-primary-700)}.cb-editor__kbd{margin-left:auto;font-family:var(--cb-font-sans);font-size:var(--cb-text-xs);color:var(--cb-color-text-muted)}.cb-editor__swatches{display:grid;grid-template-columns:repeat(5,1.6rem);gap:.35rem;padding:.25rem}.cb-editor__swatch{width:1.6rem;height:1.6rem;padding:0;border:1px solid rgb(0 0 0 / .15);border-radius:var(--cb-radius-sm);cursor:pointer}.cb-editor__swatch:hover{transform:scale(1.1)}.cb-editor__swatch:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:2px}.cb-editor__swatch--none{display:inline-block;width:1rem;height:1rem;background:linear-gradient(135deg,transparent 45%,var(--cb-color-danger-500) 45% 55%,transparent 55%),var(--cb-color-surface)}.cb-editor__surface{resize:vertical}:host(.cb-is-readonly) .cb-editor__surface{resize:none}:host(.cb-is-fullscreen){position:fixed;inset:0;z-index:1000;display:flex;flex-direction:column;border-radius:0}:host(.cb-is-fullscreen) .cb-editor__body{flex:1;min-height:0;display:flex;flex-direction:column}:host(.cb-is-fullscreen) .cb-editor__surface{flex:1;resize:none;max-width:60rem;width:100%;margin:0 auto}:host ::ng-deep .cb-editor__surface h4{margin:.75rem 0 .35rem;font-size:1rem}:host ::ng-deep .cb-editor__surface ul[data-type=taskList]{padding-left:.25rem;list-style:none}:host ::ng-deep .cb-editor__surface ul[data-type=taskList]>li{position:relative;padding-left:1.75rem}:host ::ng-deep .cb-editor__surface ul[data-type=taskList]>li:before{content:\"\";position:absolute;left:0;top:.3em;box-sizing:border-box;width:1.05rem;height:1.05rem;border:1.5px solid var(--cb-color-border-strong, var(--cb-color-border));border-radius:3px;background:var(--cb-color-surface);cursor:pointer}:host ::ng-deep .cb-editor__surface ul[data-type=taskList]>li[data-checked=true]:before{content:\"\\2713\";display:grid;place-items:center;border-color:var(--cb-color-primary-600);background:var(--cb-color-primary-700, #047857);color:#fff;font-size:.75rem;font-weight:700;line-height:1}:host ::ng-deep .cb-editor__surface ul[data-type=taskList]>li[data-checked=true]{color:var(--cb-color-text-muted);text-decoration:line-through}.cb-editor__menu--chars{left:auto;right:0;width:max-content;max-height:18rem;overflow-y:auto}.cb-editor__chars-label{display:block;padding:.35rem .25rem .15rem;font-size:var(--cb-text-xs);font-weight:var(--cb-font-weight-semibold, 600);color:var(--cb-color-text-muted)}.cb-editor__chars{display:grid;grid-template-columns:repeat(8,2rem);gap:2px}.cb-editor__char{width:2rem;height:2rem;padding:0;border:none;border-radius:var(--cb-radius-sm);background:transparent;color:var(--cb-color-text);font-size:1.05rem;line-height:1;cursor:pointer}.cb-editor__char:hover,.cb-editor__char:focus-visible{background:var(--cb-color-surface-hover);outline:none}.cb-editor__char:focus-visible{box-shadow:inset 0 0 0 2px var(--cb-color-focus-ring)}.cb-editor__find{align-items:flex-end}.cb-editor__find-options{display:inline-flex;align-items:center;gap:.2rem;min-height:2rem}.cb-editor__find-count{min-width:5.5rem;padding:0 .35rem;font-size:var(--cb-text-xs);color:var(--cb-color-text-muted);font-variant-numeric:tabular-nums;text-align:center}.cb-editor__find .cb-editor__link-actions{align-items:center}.cb-editor__bubble{position:absolute;z-index:4;display:flex;gap:2px;padding:.2rem;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-md);background:var(--cb-color-surface-raised, var(--cb-color-surface));box-shadow:var(--cb-shadow-md, 0 8px 24px rgb(0 0 0 / .12));transform:translate(-50%,calc(-100% - 6px))}.cb-editor__bubble--below{transform:translate(-50%,6px)}.cb-editor__slash{position:absolute;z-index:4;display:grid;gap:1px;width:15rem;max-height:17rem;margin-top:4px;padding:.3rem;overflow-y:auto;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-md);background:var(--cb-color-surface-raised, var(--cb-color-surface));box-shadow:var(--cb-shadow-md, 0 8px 24px rgb(0 0 0 / .12))}.cb-editor__slash-item{display:flex;align-items:center;gap:.55rem;padding:.4rem .5rem;border-radius:var(--cb-radius-sm);font-size:var(--cb-text-sm);cursor:pointer}.cb-editor__slash-item:hover{background:var(--cb-color-surface-hover)}.cb-editor__slash-item.cb-is-active{color:var(--cb-color-primary-700);background:color-mix(in srgb,var(--cb-color-primary-500) 14%,transparent)}:host-context([data-theme=\"dark\"]) .cb-editor__slash-item.cb-is-active{color:var(--cb-color-primary-300)}@media(forced-colors:active){.cb-editor__slash-item.cb-is-active{outline:2px solid Highlight}}:host ::ng-deep .cb-editor__surface figure{margin:.5em 0}:host ::ng-deep .cb-editor__surface figure img{display:block;max-width:100%}:host ::ng-deep .cb-editor__surface figcaption{padding-top:.35rem;color:var(--cb-color-text-muted);font-size:.875em;text-align:center}:host ::ng-deep .cb-editor__surface figcaption:empty:before,:host ::ng-deep .cb-editor__surface figcaption:has(>br:only-child):before{content:attr(data-placeholder)}:host ::ng-deep .cb-editor__surface details{margin:.5em 0;padding:.35rem .75rem;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-md)}:host ::ng-deep .cb-editor__surface summary{font-weight:var(--cb-font-weight-semibold);cursor:pointer}:host ::ng-deep .cb-editor__surface details[open]>summary{margin-bottom:.35rem}:host ::ng-deep .cb-editor__surface nav[data-type=toc]{margin:.5em 0;padding:.5rem .9rem;border-left:3px solid var(--cb-color-primary-400, var(--cb-color-border));border-radius:0 var(--cb-radius-md) var(--cb-radius-md) 0;background:color-mix(in srgb,var(--cb-color-primary-500) 5%,transparent);-webkit-user-select:none;user-select:none}:host ::ng-deep .cb-editor__surface nav[data-type=toc] p{margin:0 0 .25rem}:host ::ng-deep .cb-editor__surface nav[data-type=toc] ol{margin:0;padding-left:1.25rem}:host ::ng-deep .cb-editor__surface nav[data-type=toc] a{cursor:pointer}:host ::ng-deep .cb-editor__surface [data-mention]{padding:0 .2rem;border-radius:var(--cb-radius-sm);background:color-mix(in srgb,var(--cb-color-primary-500) 14%,transparent);color:var(--cb-color-primary-700);font-weight:var(--cb-font-weight-medium);white-space:nowrap}:host-context([data-theme=\"dark\"]) ::ng-deep .cb-editor__surface [data-mention]{color:var(--cb-color-primary-300)}.cb-editor__mentions{width:17rem}.cb-editor__mention-label{font-weight:var(--cb-font-weight-medium)}.cb-editor__mention-desc{margin-left:auto;overflow:hidden;color:var(--cb-color-text-muted);font-size:var(--cb-text-xs);text-overflow:ellipsis;white-space:nowrap}.cb-editor__draft{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;padding:var(--cb-space-2) var(--cb-space-3);border-bottom:var(--cb-border-width) solid var(--cb-color-border);background:color-mix(in srgb,var(--cb-color-warning-500, #f59e0b) 12%,var(--cb-color-surface));font-size:var(--cb-text-sm)}.cb-editor__draft>span{flex:1 1 14rem}.cb-editor__saved{color:var(--cb-color-text-muted);font-size:var(--cb-text-xs)}\n"], dependencies: [{ kind: "component", type: CbIcon, selector: "cb-icon", inputs: ["name", "size", "spin", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
3806
4725
|
}
|
|
3807
4726
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: CbEditor, decorators: [{
|
|
3808
4727
|
type: Component,
|
|
@@ -3818,599 +4737,677 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
|
|
|
3818
4737
|
'[class.cb-is-disabled]': 'isDisabled()',
|
|
3819
4738
|
'[class.cb-is-readonly]': 'readonly()',
|
|
3820
4739
|
'[class.cb-is-fullscreen]': 'fullscreen()',
|
|
3821
|
-
}, template: `
|
|
3822
|
-
<div class="cb-editor__header">
|
|
3823
|
-
@if (!readonly()) {
|
|
3824
|
-
<div
|
|
3825
|
-
#toolbar
|
|
3826
|
-
class="cb-editor__toolbar"
|
|
3827
|
-
role="toolbar"
|
|
3828
|
-
[attr.aria-label]="t().textFormatting"
|
|
3829
|
-
[attr.aria-controls]="surfaceId"
|
|
3830
|
-
(keydown)="onToolbarKeydown($event)"
|
|
3831
|
-
(focusin)="setActiveTool($any($event.target))"
|
|
3832
|
-
>
|
|
3833
|
-
@for (row of toolbarRows(); track $index) {
|
|
3834
|
-
<div class="cb-editor__row">
|
|
3835
|
-
@for (tool of row; track $index) {
|
|
3836
|
-
@if (tool === '|') {
|
|
3837
|
-
<span class="cb-editor__sep" aria-hidden="true"></span>
|
|
3838
|
-
} @else if (tool === 'heading') {
|
|
3839
|
-
<select
|
|
3840
|
-
class="cb-editor__select"
|
|
3841
|
-
[disabled]="isDisabled() || sourceMode()"
|
|
3842
|
-
[attr.aria-label]="t().paragraphStyle"
|
|
3843
|
-
[title]="t().paragraphStyle"
|
|
3844
|
-
(change)="format($any($event.target).value)"
|
|
3845
|
-
>
|
|
3846
|
-
<option value="P" [selected]="state().block === 'P'">{{ t().normal }}</option>
|
|
3847
|
-
<option value="H1" [selected]="state().block === 'H1'">{{ t().heading1 }}</option>
|
|
3848
|
-
<option value="H2" [selected]="state().block === 'H2'">{{ t().heading2 }}</option>
|
|
3849
|
-
<option value="H3" [selected]="state().block === 'H3'">{{ t().heading3 }}</option>
|
|
3850
|
-
<option value="H4" [selected]="state().block === 'H4'">{{ t().heading4 }}</option>
|
|
3851
|
-
</select>
|
|
3852
|
-
} @else if (tool === 'fontFamily') {
|
|
3853
|
-
<select
|
|
3854
|
-
class="cb-editor__select cb-editor__select--font"
|
|
3855
|
-
[disabled]="isDisabled() || sourceMode()"
|
|
3856
|
-
[attr.aria-label]="t().fontFamily"
|
|
3857
|
-
[title]="t().fontFamily"
|
|
3858
|
-
(change)="setFontFamily($any($event.target).value)"
|
|
3859
|
-
>
|
|
3860
|
-
@for (group of fonts(); track $index) {
|
|
3861
|
-
<optgroup [label]="groupLabel(group)">
|
|
3862
|
-
@for (font of group.fonts; track font.value) {
|
|
3863
|
-
<option [value]="font.value" [style.font-family]="font.value || null" [selected]="state().fontFamily === font.value">{{ font.name }}</option>
|
|
3864
|
-
}
|
|
3865
|
-
</optgroup>
|
|
3866
|
-
}
|
|
3867
|
-
</select>
|
|
3868
|
-
} @else if (tool === 'fontSize') {
|
|
3869
|
-
<select
|
|
3870
|
-
class="cb-editor__select cb-editor__select--size"
|
|
3871
|
-
[disabled]="isDisabled() || sourceMode()"
|
|
3872
|
-
[attr.aria-label]="t().fontSize"
|
|
3873
|
-
[title]="t().fontSize"
|
|
3874
|
-
(change)="setFontSize($any($event.target).value)"
|
|
3875
|
-
>
|
|
3876
|
-
<option value="" [selected]="!state().fontSize">{{ t().fontNormal }}</option>
|
|
3877
|
-
@for (size of fontSizes; track size) {
|
|
3878
|
-
<option [value]="size + 'px'" [selected]="state().fontSize === size + 'px'">{{ size }} px</option>
|
|
3879
|
-
}
|
|
3880
|
-
</select>
|
|
3881
|
-
} @else if (isMenu(tool)) {
|
|
3882
|
-
<div class="cb-editor__menu-wrap">
|
|
3883
|
-
<button
|
|
3884
|
-
type="button"
|
|
3885
|
-
class="cb-editor__tool cb-editor__menu-trigger"
|
|
3886
|
-
[disabled]="isDisabled() || sourceMode()"
|
|
3887
|
-
aria-haspopup="menu"
|
|
3888
|
-
[attr.aria-expanded]="openMenu() === tool"
|
|
3889
|
-
[attr.aria-label]="toolLabel(tool)"
|
|
3890
|
-
[attr.title]="toolLabel(tool)"
|
|
3891
|
-
(mousedown)="hold($event)"
|
|
3892
|
-
(click)="toggleMenu(tool)"
|
|
3893
|
-
(keydown)="onMenuTriggerKeydown($event, tool)"
|
|
3894
|
-
>
|
|
3895
|
-
<cb-icon [name]="menuIcon(tool)" [size]="16" aria-hidden="true" />
|
|
3896
|
-
@if (tool === 'textColor' || tool === 'highlight') {
|
|
3897
|
-
<span class="cb-editor__color-bar" [style.background]="tool === 'textColor' ? lastTextColor() : lastHighlight()"></span>
|
|
3898
|
-
}
|
|
3899
|
-
<cb-icon name="chevron-down" [size]="11" class="cb-editor__caret" aria-hidden="true" />
|
|
3900
|
-
</button>
|
|
3901
|
-
@if (openMenu() === tool) {
|
|
3902
|
-
<div
|
|
3903
|
-
class="cb-editor__menu"
|
|
3904
|
-
[class.cb-editor__menu--palette]="tool === 'textColor' || tool === 'highlight'"
|
|
3905
|
-
[class.cb-editor__menu--chars]="tool === 'specialChars'"
|
|
3906
|
-
role="menu"
|
|
3907
|
-
[attr.aria-label]="toolLabel(tool)"
|
|
3908
|
-
(keydown)="onMenuKeydown($event)"
|
|
3909
|
-
>
|
|
3910
|
-
@if (tool === 'alignment') {
|
|
3911
|
-
@for (option of alignments; track option) {
|
|
3912
|
-
<button
|
|
3913
|
-
type="button"
|
|
3914
|
-
class="cb-editor__menu-item"
|
|
3915
|
-
role="menuitemradio"
|
|
3916
|
-
[attr.aria-checked]="state()[option]"
|
|
3917
|
-
[tabindex]="-1"
|
|
3918
|
-
[attr.aria-keyshortcuts]="ariaKeys(option)"
|
|
3919
|
-
(mousedown)="hold($event)"
|
|
3920
|
-
(click)="applyMenu(tool, option)"
|
|
3921
|
-
>
|
|
3922
|
-
<cb-icon [name]="toolIcon(option)" [size]="16" aria-hidden="true" />
|
|
3923
|
-
<span>{{ toolLabel(option) }}</span>
|
|
3924
|
-
<kbd class="cb-editor__kbd">{{ toolKeys(option) }}</kbd>
|
|
3925
|
-
</button>
|
|
3926
|
-
}
|
|
3927
|
-
} @else if (tool === 'specialChars') {
|
|
3928
|
-
@for (group of specialChars; track $index) {
|
|
3929
|
-
<div class="cb-editor__chars-group" role="group" [attr.aria-label]="group.label(t())">
|
|
3930
|
-
<span class="cb-editor__chars-label" aria-hidden="true">{{ group.label(t()) }}</span>
|
|
3931
|
-
<div class="cb-editor__chars">
|
|
3932
|
-
@for (ch of group.chars; track ch) {
|
|
3933
|
-
<button type="button" class="cb-editor__char" role="menuitem" [tabindex]="-1" (mousedown)="hold($event)" (click)="applyMenu(tool, ch)">{{ ch }}</button>
|
|
3934
|
-
}
|
|
3935
|
-
</div>
|
|
3936
|
-
</div>
|
|
3937
|
-
}
|
|
3938
|
-
} @else {
|
|
3939
|
-
<button
|
|
3940
|
-
type="button"
|
|
3941
|
-
class="cb-editor__menu-item cb-editor__menu-item--wide"
|
|
3942
|
-
role="menuitem"
|
|
3943
|
-
[tabindex]="-1"
|
|
3944
|
-
(mousedown)="hold($event)"
|
|
3945
|
-
(click)="applyMenu(tool, null)"
|
|
3946
|
-
>
|
|
3947
|
-
<span class="cb-editor__swatch cb-editor__swatch--none" aria-hidden="true"></span>
|
|
3948
|
-
{{ tool === 'textColor' ? t().automaticColor : t().noHighlight }}
|
|
3949
|
-
</button>
|
|
3950
|
-
<div class="cb-editor__swatches">
|
|
3951
|
-
@for (color of tool === 'textColor' ? textColors : highlightColors; track color.value) {
|
|
3952
|
-
<button
|
|
3953
|
-
type="button"
|
|
3954
|
-
class="cb-editor__swatch"
|
|
3955
|
-
role="menuitem"
|
|
3956
|
-
[style.background]="color.value"
|
|
3957
|
-
[attr.aria-label]="color.label(t())"
|
|
3958
|
-
[attr.title]="color.label(t())"
|
|
3959
|
-
[tabindex]="-1"
|
|
3960
|
-
(mousedown)="hold($event)"
|
|
3961
|
-
(click)="applyMenu(tool, color.value)"
|
|
3962
|
-
></button>
|
|
3963
|
-
}
|
|
3964
|
-
</div>
|
|
3965
|
-
}
|
|
3966
|
-
</div>
|
|
3967
|
-
}
|
|
3968
|
-
</div>
|
|
3969
|
-
} @else {
|
|
3970
|
-
<button
|
|
3971
|
-
type="button"
|
|
3972
|
-
class="cb-editor__tool"
|
|
3973
|
-
[class.cb-is-active]="isActive(tool)"
|
|
3974
|
-
[disabled]="isDisabled() || (sourceMode() && tool !== 'source' && tool !== 'fullscreen')"
|
|
3975
|
-
[attr.aria-label]="toolLabel(tool)"
|
|
3976
|
-
[attr.title]="toolTitle(tool)"
|
|
3977
|
-
[attr.aria-keyshortcuts]="ariaKeys(tool)"
|
|
3978
|
-
[attr.aria-pressed]="pressable(tool) ? isActive(tool) : null"
|
|
3979
|
-
(mousedown)="hold($event)"
|
|
3980
|
-
(click)="run(tool)"
|
|
3981
|
-
>
|
|
3982
|
-
<cb-icon [name]="tool === 'fullscreen' && fullscreen() ? 'minimize' : toolIcon(tool)" [size]="16" aria-hidden="true" />
|
|
3983
|
-
</button>
|
|
3984
|
-
}
|
|
3985
|
-
}
|
|
3986
|
-
</div>
|
|
3987
|
-
}
|
|
3988
|
-
</div>
|
|
3989
|
-
}
|
|
3990
|
-
|
|
3991
|
-
@if (findOpen() && !readonly()) {
|
|
3992
|
-
<div class="cb-editor__link cb-editor__find" role="search" [attr.aria-label]="t().findReplace" (keydown)="onFindKeydown($event)">
|
|
3993
|
-
<label class="cb-editor__link-field">
|
|
3994
|
-
<span class="cb-editor__link-label">{{ t().findText }}</span>
|
|
3995
|
-
<input #findInput class="cb-editor__link-input" type="text" autocomplete="off" [value]="findQuery()" (input)="setFindQuery($any($event.target).value)" />
|
|
3996
|
-
</label>
|
|
3997
|
-
<label class="cb-editor__link-field">
|
|
3998
|
-
<span class="cb-editor__link-label">{{ t().replaceWith }}</span>
|
|
3999
|
-
<input class="cb-editor__link-input cb-editor__replace-input" type="text" autocomplete="off" [value]="replaceText()" (input)="replaceText.set($any($event.target).value)" />
|
|
4000
|
-
</label>
|
|
4001
|
-
<div class="cb-editor__find-options">
|
|
4002
|
-
<button
|
|
4003
|
-
type="button"
|
|
4004
|
-
class="cb-editor__tool"
|
|
4005
|
-
[class.cb-is-active]="findCase()"
|
|
4006
|
-
[attr.aria-pressed]="findCase()"
|
|
4007
|
-
[attr.aria-label]="t().matchCase"
|
|
4008
|
-
[attr.title]="t().matchCase"
|
|
4009
|
-
(click)="setFindOption('case')"
|
|
4010
|
-
>
|
|
4011
|
-
<cb-icon name="case-sensitive" [size]="16" aria-hidden="true" />
|
|
4012
|
-
</button>
|
|
4013
|
-
<button
|
|
4014
|
-
type="button"
|
|
4015
|
-
class="cb-editor__tool"
|
|
4016
|
-
[class.cb-is-active]="findWhole()"
|
|
4017
|
-
[attr.aria-pressed]="findWhole()"
|
|
4018
|
-
[attr.aria-label]="t().wholeWords"
|
|
4019
|
-
[attr.title]="t().wholeWords"
|
|
4020
|
-
(click)="setFindOption('whole')"
|
|
4021
|
-
>
|
|
4022
|
-
<cb-icon name="whole-word" [size]="16" aria-hidden="true" />
|
|
4023
|
-
</button>
|
|
4024
|
-
<span class="cb-editor__find-count" role="status">{{ findStatus() }}</span>
|
|
4025
|
-
<button type="button" class="cb-editor__tool" [disabled]="!findTotal()" [attr.aria-label]="t().findPrevious" [attr.title]="t().findPrevious + ' (Shift+Enter)'" (click)="findStep(-1)">
|
|
4026
|
-
<cb-icon name="chevron-up" [size]="16" aria-hidden="true" />
|
|
4027
|
-
</button>
|
|
4028
|
-
<button type="button" class="cb-editor__tool" [disabled]="!findTotal()" [attr.aria-label]="t().findNext" [attr.title]="t().findNext + ' (Enter)'" (click)="findStep(1)">
|
|
4029
|
-
<cb-icon name="chevron-down" [size]="16" aria-hidden="true" />
|
|
4030
|
-
</button>
|
|
4031
|
-
</div>
|
|
4032
|
-
<div class="cb-editor__link-actions">
|
|
4033
|
-
<button type="button" class="cb-editor__btn" [disabled]="!findTotal()" (click)="replaceCurrent()">{{ t().replaceOne }}</button>
|
|
4034
|
-
<button type="button" class="cb-editor__btn" [disabled]="!findTotal()" (click)="replaceAllMatches()">{{ t().replaceAll }}</button>
|
|
4035
|
-
<button type="button" class="cb-editor__tool" [attr.aria-label]="t().close" [attr.title]="t().close + ' (Esc)'" (click)="closeFind()">
|
|
4036
|
-
<cb-icon name="x" [size]="16" aria-hidden="true" />
|
|
4037
|
-
</button>
|
|
4038
|
-
</div>
|
|
4039
|
-
</div>
|
|
4040
|
-
}
|
|
4041
|
-
|
|
4042
|
-
@if (
|
|
4043
|
-
<div class="cb-
|
|
4044
|
-
<
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
<
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
</
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
type="button"
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
>
|
|
4080
|
-
<
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
<
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
>
|
|
4120
|
-
|
|
4121
|
-
</
|
|
4122
|
-
|
|
4123
|
-
<
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
[
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
<
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
[attr.aria-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
}
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
[
|
|
4393
|
-
[style.
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
(
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
`, styles: [":host{display:block;width:100%;border:var(--cb-border-width) solid var(--cb-color-border);border-radius:var(--cb-radius-lg);background:var(--cb-color-surface);color:var(--cb-color-text);font-family:var(--cb-font-sans);overflow:clip}.cb-editor__header{position:sticky;top:var(--cb-editor-sticky-top, 0px);z-index:3;background:var(--cb-color-surface)}.cb-editor__toolbar{display:grid;gap:.25rem;padding:var(--cb-space-2) var(--cb-space-3);border-bottom:var(--cb-border-width) solid var(--cb-color-border);background:var(--cb-color-surface-subtle, var(--cb-color-surface))}.cb-editor__row{display:flex;align-items:center;flex-wrap:wrap;gap:.2rem}.cb-editor__row+.cb-editor__row{padding-top:.25rem;border-top:1px dashed var(--cb-color-border)}.cb-editor__select--font{width:10.5rem}.cb-editor__select--size{width:5.5rem}.cb-editor__select{height:1.75rem;padding:0 .4rem;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-sm);background:var(--cb-color-bg);color:var(--cb-color-text);font:inherit;font-size:var(--cb-text-sm);cursor:pointer}.cb-editor__sep{width:1px;height:1.1rem;margin:0 .25rem;background:var(--cb-color-border)}.cb-editor__tool{display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;border:none;border-radius:var(--cb-radius-sm);background:transparent;color:var(--cb-color-text);cursor:pointer}.cb-editor__tool:hover{background:color-mix(in srgb,var(--cb-color-primary-500) 12%,transparent)}.cb-editor__tool:disabled{opacity:.4;cursor:not-allowed}.cb-editor__tool:focus-visible,.cb-editor__select:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:1px}.cb-editor__tool.cb-is-active{color:var(--cb-color-primary-700);background:color-mix(in srgb,var(--cb-color-primary-500) 18%,transparent)}:host-context([data-theme=\"dark\"]) .cb-editor__tool.cb-is-active{color:var(--cb-color-primary-300)}@media(forced-colors:active){.cb-editor__tool.cb-is-active{outline:2px solid Highlight}}.cb-editor__link{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--cb-space-2) var(--cb-space-3);padding:var(--cb-space-2) var(--cb-space-3);border-bottom:var(--cb-border-width) solid var(--cb-color-border);background:var(--cb-color-surface-subtle, var(--cb-color-surface));font-size:var(--cb-text-sm)}.cb-editor__link-field{display:grid;flex:1 1 14rem;gap:.2rem}.cb-editor__link-label{font-size:var(--cb-text-xs);color:var(--cb-color-text-muted)}.cb-editor__link-input{height:2rem;padding:0 .5rem;border:1px solid var(--cb-color-border-strong, var(--cb-color-border));border-radius:var(--cb-radius-sm);background:var(--cb-color-bg, var(--cb-color-surface));color:var(--cb-color-text);font:inherit}.cb-editor__link-input[aria-invalid=true]{border-color:var(--cb-color-danger-500)}.cb-editor__link-input:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:0}.cb-editor__link-check{display:inline-flex;align-items:center;gap:.35rem;min-height:2rem}.cb-editor__link-actions{display:inline-flex;gap:var(--cb-space-2)}.cb-editor__btn{display:inline-flex;align-items:center;gap:.3rem;height:2rem;padding:0 .7rem;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-sm);background:var(--cb-color-surface);color:var(--cb-color-text);font:inherit;cursor:pointer}.cb-editor__btn:hover{background:var(--cb-color-surface-hover)}.cb-editor__btn:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:1px}.cb-editor__btn--primary{color:var(--cb-color-primary-contrast);background:var(--cb-color-primary-600);border-color:var(--cb-color-primary-600)}.cb-editor__btn--primary:hover{background:var(--cb-color-primary-700)}.cb-editor__error{flex-basis:100%;margin:0;font-size:var(--cb-text-xs);color:var(--cb-color-danger-600)}.cb-editor__surface{padding:var(--cb-space-3);font-family:var(--cb-editor-font-family, Carlito, Calibri, var(--cb-font-sans), sans-serif);font-size:var(--cb-editor-font-size, 16px);line-height:1.6;outline:none;overflow-y:auto}.cb-editor__surface:empty:before,.cb-editor__surface.cb-is-empty:not(:focus):before{content:attr(data-placeholder);color:var(--cb-color-text-muted);pointer-events:none}.cb-editor__surface.cb-is-empty:not(:empty):before{position:absolute}.cb-editor__surface{position:relative}.cb-editor__source{display:block;box-sizing:border-box;width:100%;padding:var(--cb-space-3);border:none;resize:vertical;background:var(--cb-color-surface-subtle, var(--cb-color-surface));color:var(--cb-color-text);font-family:var(--cb-font-mono, monospace);font-size:var(--cb-text-xs);line-height:1.6;outline:none}.cb-editor__footer{display:flex;align-items:center;justify-content:space-between;gap:var(--cb-space-3);padding:var(--cb-space-1) var(--cb-space-3) var(--cb-space-2);border-top:var(--cb-border-width) solid var(--cb-color-border)}.cb-editor__count{margin-left:auto;font-size:var(--cb-text-xs);color:var(--cb-color-text-muted);font-variant-numeric:tabular-nums}.cb-editor__count.cb-is-over{color:var(--cb-color-danger-600);font-weight:var(--cb-font-weight-semibold)}:host(.cb-is-readonly){border-color:transparent;background:transparent}:host(.cb-is-readonly) .cb-editor__surface{padding:0}.cb-editor__surface h1{font-size:1.5rem;margin:.4em 0}.cb-editor__surface h2{font-size:1.25rem;margin:.4em 0}.cb-editor__surface h3{font-size:1.1rem;margin:.4em 0}.cb-editor__surface p{margin:.4em 0}.cb-editor__surface ul,.cb-editor__surface ol{margin:.4em 0;padding-left:1.5rem}.cb-editor__surface blockquote{margin:.5em 0;padding:.25em .9em;border-left:3px solid var(--cb-color-primary-400, var(--cb-color-border));color:var(--cb-color-text-muted)}.cb-editor__surface a{color:var(--cb-color-primary-600, #059669);text-decoration:underline}.cb-editor__surface code{padding:.1em .35em;border-radius:var(--cb-radius-sm);background:var(--cb-color-surface-subtle, rgb(0 0 0 / .06));font-family:var(--cb-font-mono, monospace);font-size:.9em}.cb-editor__surface pre{padding:var(--cb-space-3);border-radius:var(--cb-radius-md);background:var(--cb-color-surface-subtle, rgb(0 0 0 / .06));font-family:var(--cb-font-mono, monospace);white-space:pre-wrap}.cb-editor__surface hr{margin:var(--cb-space-3) 0;border:none;border-top:1px solid var(--cb-color-border-strong, var(--cb-color-border))}.cb-editor__surface img{max-width:100%;height:auto;border-radius:var(--cb-radius-sm, 4px)}:host(.cb-is-invalid){border-color:var(--cb-color-danger)}.cb-editor__context{display:flex;align-items:center;flex-wrap:wrap;gap:.2rem;padding:var(--cb-space-1) var(--cb-space-3);border-bottom:var(--cb-border-width) solid var(--cb-color-border);background:color-mix(in srgb,var(--cb-color-primary-500) 6%,var(--cb-color-surface))}.cb-editor__tool--text{width:auto;padding:0 .4rem;font:inherit;font-size:var(--cb-text-xs);font-variant-numeric:tabular-nums}.cb-editor__tool--danger:hover{color:var(--cb-color-danger-600);background:color-mix(in srgb,var(--cb-color-danger-500) 12%,transparent)}.cb-editor__link-field--narrow{flex:0 0 6rem}.cb-editor__body{position:relative}.cb-editor__surface img{cursor:pointer}.cb-editor__img-frame{position:absolute;box-sizing:border-box;border:2px solid var(--cb-color-primary-500);border-radius:2px;pointer-events:none}.cb-editor__img-handle{position:absolute;right:-6px;bottom:-6px;width:12px;height:12px;border:2px solid var(--cb-color-surface);border-radius:2px;background:var(--cb-color-primary-500);cursor:nwse-resize;pointer-events:auto;touch-action:none}.cb-editor__surface table{width:100%;margin:var(--cb-space-2) 0;border-collapse:collapse;table-layout:fixed}.cb-editor__surface th,.cb-editor__surface td{min-width:2rem;padding:.35rem .5rem;vertical-align:top;word-break:break-word;outline:1px dashed color-mix(in srgb,var(--cb-color-text-muted) 45%,transparent);outline-offset:-1px}.cb-editor__surface th[style*=border],.cb-editor__surface td[style*=border]{outline:none}.cb-editor__surface th{font-weight:var(--cb-font-weight-semibold, 600);text-align:left}:host(.cb-is-readonly) .cb-editor__surface th,:host(.cb-is-readonly) .cb-editor__surface td{outline:none}.cb-editor__surface pre code{padding:0;background:transparent;color:inherit;font-size:inherit}.cb-editor__surface pre[data-language]:before{content:attr(data-language);float:right;margin:-.25rem -.25rem 0 .5rem;font-family:var(--cb-font-sans);font-size:.7rem;opacity:.6}.cb-editor__group{display:inline-flex;align-items:center;gap:.2rem}.cb-editor__group-label{margin-right:.15rem;font-size:var(--cb-text-xs);font-weight:var(--cb-font-weight-semibold, 600);color:var(--cb-color-text-muted)}.cb-editor__tool--labeled{width:auto;gap:.3rem;padding:0 .45rem;font:inherit;font-size:var(--cb-text-xs);white-space:nowrap}.cb-editor__picker-panel{align-items:flex-start}.cb-editor__picker{display:grid;gap:3px}.cb-editor__picker-row{display:flex;gap:3px}.cb-editor__picker-cell{width:1.1rem;height:1.1rem;padding:0;border:1px solid var(--cb-color-border-strong, var(--cb-color-border));border-radius:2px;background:var(--cb-color-surface);cursor:pointer}.cb-editor__picker-cell.cb-is-on{border-color:var(--cb-color-primary-600);background:color-mix(in srgb,var(--cb-color-primary-500) 35%,var(--cb-color-surface))}.cb-editor__picker-cell:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:1px}.cb-editor__picker-side{display:grid;gap:var(--cb-space-2);align-content:start}.cb-editor__picker-size{font-weight:var(--cb-font-weight-semibold, 600);font-variant-numeric:tabular-nums}.cb-editor__crop{display:grid;gap:var(--cb-space-2);padding:var(--cb-space-3);border-bottom:var(--cb-border-width) solid var(--cb-color-border);background:var(--cb-color-surface-subtle, var(--cb-color-surface))}.cb-editor__crop-stage{position:relative;justify-self:center;max-width:100%;overflow:hidden;-webkit-user-select:none;user-select:none;touch-action:none}.cb-editor__crop-img{display:block;max-width:100%;max-height:22rem}.cb-editor__crop-box{position:absolute;box-sizing:border-box;border:2px solid #fff;box-shadow:0 0 0 9999px #0000008c;cursor:move;outline:none}.cb-editor__crop-box:focus-visible{border-color:var(--cb-color-focus-ring)}.cb-editor__crop-handle{position:absolute;width:14px;height:14px;border:2px solid var(--cb-color-primary-600);border-radius:2px;background:#fff}.cb-editor__crop-handle--nw{left:-8px;top:-8px;cursor:nwse-resize}.cb-editor__crop-handle--ne{right:-8px;top:-8px;cursor:nesw-resize}.cb-editor__crop-handle--sw{left:-8px;bottom:-8px;cursor:nesw-resize}.cb-editor__crop-handle--se{right:-8px;bottom:-8px;cursor:nwse-resize}.cb-editor__crop-help{margin:0;font-size:var(--cb-text-xs);color:var(--cb-color-text-muted)}.cb-editor__crop-spacer{flex:1}.cb-editor__btn.cb-is-active{border-color:var(--cb-color-primary-600);color:var(--cb-color-primary-700);background:color-mix(in srgb,var(--cb-color-primary-500) 14%,var(--cb-color-surface))}.cb-editor__crop .cb-editor__link-actions{display:flex;flex-wrap:wrap}.cb-editor__select--size{width:7.5rem}.cb-editor__menu-wrap{position:relative;display:inline-flex}.cb-editor__menu-trigger{position:relative;width:auto;gap:1px;padding:0 .25rem}.cb-editor__color-bar{position:absolute;left:.35rem;right:1rem;bottom:.2rem;height:3px;border-radius:1px}.cb-editor__caret{opacity:.7}.cb-editor__menu{position:absolute;top:calc(100% + 4px);left:0;z-index:10;display:grid;gap:2px;min-width:12rem;padding:.35rem;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-md);background:var(--cb-color-surface-raised, var(--cb-color-surface));box-shadow:var(--cb-shadow-md, 0 8px 24px rgb(0 0 0 / .12))}.cb-editor__menu-item{display:flex;align-items:center;gap:.5rem;width:100%;padding:.4rem .5rem;border:none;border-radius:var(--cb-radius-sm);background:transparent;color:var(--cb-color-text);font:inherit;font-size:var(--cb-text-sm);text-align:left;cursor:pointer}.cb-editor__menu-item:hover,.cb-editor__menu-item:focus-visible{background:var(--cb-color-surface-hover);outline:none}.cb-editor__menu-item:focus-visible{box-shadow:inset 0 0 0 2px var(--cb-color-focus-ring)}.cb-editor__menu-item[aria-checked=true]{font-weight:var(--cb-font-weight-semibold, 600);color:var(--cb-color-primary-700)}.cb-editor__kbd{margin-left:auto;font-family:var(--cb-font-sans);font-size:var(--cb-text-xs);color:var(--cb-color-text-muted)}.cb-editor__swatches{display:grid;grid-template-columns:repeat(5,1.6rem);gap:.35rem;padding:.25rem}.cb-editor__swatch{width:1.6rem;height:1.6rem;padding:0;border:1px solid rgb(0 0 0 / .15);border-radius:var(--cb-radius-sm);cursor:pointer}.cb-editor__swatch:hover{transform:scale(1.1)}.cb-editor__swatch:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:2px}.cb-editor__swatch--none{display:inline-block;width:1rem;height:1rem;background:linear-gradient(135deg,transparent 45%,var(--cb-color-danger-500) 45% 55%,transparent 55%),var(--cb-color-surface)}.cb-editor__surface{resize:vertical}:host(.cb-is-readonly) .cb-editor__surface{resize:none}:host(.cb-is-fullscreen){position:fixed;inset:0;z-index:1000;display:flex;flex-direction:column;border-radius:0}:host(.cb-is-fullscreen) .cb-editor__body{flex:1;min-height:0;display:flex;flex-direction:column}:host(.cb-is-fullscreen) .cb-editor__surface{flex:1;resize:none;max-width:60rem;width:100%;margin:0 auto}.cb-editor__surface h4{margin:.75rem 0 .35rem;font-size:1rem}.cb-editor__surface ul[data-type=taskList]{padding-left:.25rem;list-style:none}.cb-editor__surface ul[data-type=taskList]>li{position:relative;padding-left:1.75rem}.cb-editor__surface ul[data-type=taskList]>li:before{content:\"\";position:absolute;left:0;top:.3em;box-sizing:border-box;width:1.05rem;height:1.05rem;border:1.5px solid var(--cb-color-border-strong, var(--cb-color-border));border-radius:3px;background:var(--cb-color-surface);cursor:pointer}.cb-editor__surface ul[data-type=taskList]>li[data-checked=true]:before{content:\"\\2713\";display:grid;place-items:center;border-color:var(--cb-color-primary-600);background:var(--cb-color-primary-600);color:var(--cb-color-primary-contrast, #fff);font-size:.75rem;font-weight:700;line-height:1}.cb-editor__surface ul[data-type=taskList]>li[data-checked=true]{color:var(--cb-color-text-muted);text-decoration:line-through}.cb-editor__menu--chars{left:auto;right:0;width:max-content;max-height:18rem;overflow-y:auto}.cb-editor__chars-label{display:block;padding:.35rem .25rem .15rem;font-size:var(--cb-text-xs);font-weight:var(--cb-font-weight-semibold, 600);color:var(--cb-color-text-muted)}.cb-editor__chars{display:grid;grid-template-columns:repeat(8,2rem);gap:2px}.cb-editor__char{width:2rem;height:2rem;padding:0;border:none;border-radius:var(--cb-radius-sm);background:transparent;color:var(--cb-color-text);font-size:1.05rem;line-height:1;cursor:pointer}.cb-editor__char:hover,.cb-editor__char:focus-visible{background:var(--cb-color-surface-hover);outline:none}.cb-editor__char:focus-visible{box-shadow:inset 0 0 0 2px var(--cb-color-focus-ring)}.cb-editor__find{align-items:flex-end}.cb-editor__find-options{display:inline-flex;align-items:center;gap:.2rem;min-height:2rem}.cb-editor__find-count{min-width:5.5rem;padding:0 .35rem;font-size:var(--cb-text-xs);color:var(--cb-color-text-muted);font-variant-numeric:tabular-nums;text-align:center}.cb-editor__find .cb-editor__link-actions{align-items:center}.cb-editor__bubble{position:absolute;z-index:4;display:flex;gap:2px;padding:.2rem;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-md);background:var(--cb-color-surface-raised, var(--cb-color-surface));box-shadow:var(--cb-shadow-md, 0 8px 24px rgb(0 0 0 / .12));transform:translate(-50%,calc(-100% - 6px))}.cb-editor__bubble--below{transform:translate(-50%,6px)}.cb-editor__slash{position:absolute;z-index:4;display:grid;gap:1px;width:15rem;max-height:17rem;margin-top:4px;padding:.3rem;overflow-y:auto;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-md);background:var(--cb-color-surface-raised, var(--cb-color-surface));box-shadow:var(--cb-shadow-md, 0 8px 24px rgb(0 0 0 / .12))}.cb-editor__slash-item{display:flex;align-items:center;gap:.55rem;padding:.4rem .5rem;border-radius:var(--cb-radius-sm);font-size:var(--cb-text-sm);cursor:pointer}.cb-editor__slash-item:hover{background:var(--cb-color-surface-hover)}.cb-editor__slash-item.cb-is-active{color:var(--cb-color-primary-700);background:color-mix(in srgb,var(--cb-color-primary-500) 14%,transparent)}:host-context([data-theme=\"dark\"]) .cb-editor__slash-item.cb-is-active{color:var(--cb-color-primary-300)}@media(forced-colors:active){.cb-editor__slash-item.cb-is-active{outline:2px solid Highlight}}\n"] }]
|
|
4413
|
-
|
|
4740
|
+
}, template: `
|
|
4741
|
+
<div class="cb-editor__header">
|
|
4742
|
+
@if (!readonly()) {
|
|
4743
|
+
<div
|
|
4744
|
+
#toolbar
|
|
4745
|
+
class="cb-editor__toolbar"
|
|
4746
|
+
role="toolbar"
|
|
4747
|
+
[attr.aria-label]="t().textFormatting"
|
|
4748
|
+
[attr.aria-controls]="surfaceId"
|
|
4749
|
+
(keydown)="onToolbarKeydown($event)"
|
|
4750
|
+
(focusin)="setActiveTool($any($event.target))"
|
|
4751
|
+
>
|
|
4752
|
+
@for (row of toolbarRows(); track $index) {
|
|
4753
|
+
<div class="cb-editor__row">
|
|
4754
|
+
@for (tool of row; track $index) {
|
|
4755
|
+
@if (tool === '|') {
|
|
4756
|
+
<span class="cb-editor__sep" aria-hidden="true"></span>
|
|
4757
|
+
} @else if (tool === 'heading') {
|
|
4758
|
+
<select
|
|
4759
|
+
class="cb-editor__select"
|
|
4760
|
+
[disabled]="isDisabled() || sourceMode()"
|
|
4761
|
+
[attr.aria-label]="t().paragraphStyle"
|
|
4762
|
+
[title]="t().paragraphStyle"
|
|
4763
|
+
(change)="format($any($event.target).value)"
|
|
4764
|
+
>
|
|
4765
|
+
<option value="P" [selected]="state().block === 'P'">{{ t().normal }}</option>
|
|
4766
|
+
<option value="H1" [selected]="state().block === 'H1'">{{ t().heading1 }}</option>
|
|
4767
|
+
<option value="H2" [selected]="state().block === 'H2'">{{ t().heading2 }}</option>
|
|
4768
|
+
<option value="H3" [selected]="state().block === 'H3'">{{ t().heading3 }}</option>
|
|
4769
|
+
<option value="H4" [selected]="state().block === 'H4'">{{ t().heading4 }}</option>
|
|
4770
|
+
</select>
|
|
4771
|
+
} @else if (tool === 'fontFamily') {
|
|
4772
|
+
<select
|
|
4773
|
+
class="cb-editor__select cb-editor__select--font"
|
|
4774
|
+
[disabled]="isDisabled() || sourceMode()"
|
|
4775
|
+
[attr.aria-label]="t().fontFamily"
|
|
4776
|
+
[title]="t().fontFamily"
|
|
4777
|
+
(change)="setFontFamily($any($event.target).value)"
|
|
4778
|
+
>
|
|
4779
|
+
@for (group of fonts(); track $index) {
|
|
4780
|
+
<optgroup [label]="groupLabel(group)">
|
|
4781
|
+
@for (font of group.fonts; track font.value) {
|
|
4782
|
+
<option [value]="font.value" [style.font-family]="font.value || null" [selected]="state().fontFamily === font.value">{{ font.name }}</option>
|
|
4783
|
+
}
|
|
4784
|
+
</optgroup>
|
|
4785
|
+
}
|
|
4786
|
+
</select>
|
|
4787
|
+
} @else if (tool === 'fontSize') {
|
|
4788
|
+
<select
|
|
4789
|
+
class="cb-editor__select cb-editor__select--size"
|
|
4790
|
+
[disabled]="isDisabled() || sourceMode()"
|
|
4791
|
+
[attr.aria-label]="t().fontSize"
|
|
4792
|
+
[title]="t().fontSize"
|
|
4793
|
+
(change)="setFontSize($any($event.target).value)"
|
|
4794
|
+
>
|
|
4795
|
+
<option value="" [selected]="!state().fontSize">{{ t().fontNormal }}</option>
|
|
4796
|
+
@for (size of fontSizes; track size) {
|
|
4797
|
+
<option [value]="size + 'px'" [selected]="state().fontSize === size + 'px'">{{ size }} px</option>
|
|
4798
|
+
}
|
|
4799
|
+
</select>
|
|
4800
|
+
} @else if (isMenu(tool)) {
|
|
4801
|
+
<div class="cb-editor__menu-wrap">
|
|
4802
|
+
<button
|
|
4803
|
+
type="button"
|
|
4804
|
+
class="cb-editor__tool cb-editor__menu-trigger"
|
|
4805
|
+
[disabled]="isDisabled() || sourceMode()"
|
|
4806
|
+
aria-haspopup="menu"
|
|
4807
|
+
[attr.aria-expanded]="openMenu() === tool"
|
|
4808
|
+
[attr.aria-label]="toolLabel(tool)"
|
|
4809
|
+
[attr.title]="toolLabel(tool)"
|
|
4810
|
+
(mousedown)="hold($event)"
|
|
4811
|
+
(click)="toggleMenu(tool)"
|
|
4812
|
+
(keydown)="onMenuTriggerKeydown($event, tool)"
|
|
4813
|
+
>
|
|
4814
|
+
<cb-icon [name]="menuIcon(tool)" [size]="16" aria-hidden="true" />
|
|
4815
|
+
@if (tool === 'textColor' || tool === 'highlight') {
|
|
4816
|
+
<span class="cb-editor__color-bar" [style.background]="tool === 'textColor' ? lastTextColor() : lastHighlight()"></span>
|
|
4817
|
+
}
|
|
4818
|
+
<cb-icon name="chevron-down" [size]="11" class="cb-editor__caret" aria-hidden="true" />
|
|
4819
|
+
</button>
|
|
4820
|
+
@if (openMenu() === tool) {
|
|
4821
|
+
<div
|
|
4822
|
+
class="cb-editor__menu"
|
|
4823
|
+
[class.cb-editor__menu--palette]="tool === 'textColor' || tool === 'highlight'"
|
|
4824
|
+
[class.cb-editor__menu--chars]="tool === 'specialChars'"
|
|
4825
|
+
role="menu"
|
|
4826
|
+
[attr.aria-label]="toolLabel(tool)"
|
|
4827
|
+
(keydown)="onMenuKeydown($event)"
|
|
4828
|
+
>
|
|
4829
|
+
@if (tool === 'alignment') {
|
|
4830
|
+
@for (option of alignments; track option) {
|
|
4831
|
+
<button
|
|
4832
|
+
type="button"
|
|
4833
|
+
class="cb-editor__menu-item"
|
|
4834
|
+
role="menuitemradio"
|
|
4835
|
+
[attr.aria-checked]="state()[option]"
|
|
4836
|
+
[tabindex]="-1"
|
|
4837
|
+
[attr.aria-keyshortcuts]="ariaKeys(option)"
|
|
4838
|
+
(mousedown)="hold($event)"
|
|
4839
|
+
(click)="applyMenu(tool, option)"
|
|
4840
|
+
>
|
|
4841
|
+
<cb-icon [name]="toolIcon(option)" [size]="16" aria-hidden="true" />
|
|
4842
|
+
<span>{{ toolLabel(option) }}</span>
|
|
4843
|
+
<kbd class="cb-editor__kbd">{{ toolKeys(option) }}</kbd>
|
|
4844
|
+
</button>
|
|
4845
|
+
}
|
|
4846
|
+
} @else if (tool === 'specialChars') {
|
|
4847
|
+
@for (group of specialChars; track $index) {
|
|
4848
|
+
<div class="cb-editor__chars-group" role="group" [attr.aria-label]="group.label(t())">
|
|
4849
|
+
<span class="cb-editor__chars-label" aria-hidden="true">{{ group.label(t()) }}</span>
|
|
4850
|
+
<div class="cb-editor__chars">
|
|
4851
|
+
@for (ch of group.chars; track ch) {
|
|
4852
|
+
<button type="button" class="cb-editor__char" role="menuitem" [tabindex]="-1" (mousedown)="hold($event)" (click)="applyMenu(tool, ch)">{{ ch }}</button>
|
|
4853
|
+
}
|
|
4854
|
+
</div>
|
|
4855
|
+
</div>
|
|
4856
|
+
}
|
|
4857
|
+
} @else {
|
|
4858
|
+
<button
|
|
4859
|
+
type="button"
|
|
4860
|
+
class="cb-editor__menu-item cb-editor__menu-item--wide"
|
|
4861
|
+
role="menuitem"
|
|
4862
|
+
[tabindex]="-1"
|
|
4863
|
+
(mousedown)="hold($event)"
|
|
4864
|
+
(click)="applyMenu(tool, null)"
|
|
4865
|
+
>
|
|
4866
|
+
<span class="cb-editor__swatch cb-editor__swatch--none" aria-hidden="true"></span>
|
|
4867
|
+
{{ tool === 'textColor' ? t().automaticColor : t().noHighlight }}
|
|
4868
|
+
</button>
|
|
4869
|
+
<div class="cb-editor__swatches">
|
|
4870
|
+
@for (color of tool === 'textColor' ? textColors : highlightColors; track color.value) {
|
|
4871
|
+
<button
|
|
4872
|
+
type="button"
|
|
4873
|
+
class="cb-editor__swatch"
|
|
4874
|
+
role="menuitem"
|
|
4875
|
+
[style.background]="color.value"
|
|
4876
|
+
[attr.aria-label]="color.label(t())"
|
|
4877
|
+
[attr.title]="color.label(t())"
|
|
4878
|
+
[tabindex]="-1"
|
|
4879
|
+
(mousedown)="hold($event)"
|
|
4880
|
+
(click)="applyMenu(tool, color.value)"
|
|
4881
|
+
></button>
|
|
4882
|
+
}
|
|
4883
|
+
</div>
|
|
4884
|
+
}
|
|
4885
|
+
</div>
|
|
4886
|
+
}
|
|
4887
|
+
</div>
|
|
4888
|
+
} @else {
|
|
4889
|
+
<button
|
|
4890
|
+
type="button"
|
|
4891
|
+
class="cb-editor__tool"
|
|
4892
|
+
[class.cb-is-active]="isActive(tool)"
|
|
4893
|
+
[disabled]="isDisabled() || (sourceMode() && tool !== 'source' && tool !== 'fullscreen')"
|
|
4894
|
+
[attr.aria-label]="toolLabel(tool)"
|
|
4895
|
+
[attr.title]="toolTitle(tool)"
|
|
4896
|
+
[attr.aria-keyshortcuts]="ariaKeys(tool)"
|
|
4897
|
+
[attr.aria-pressed]="pressable(tool) ? isActive(tool) : null"
|
|
4898
|
+
(mousedown)="hold($event)"
|
|
4899
|
+
(click)="run(tool)"
|
|
4900
|
+
>
|
|
4901
|
+
<cb-icon [name]="tool === 'fullscreen' && fullscreen() ? 'minimize' : toolIcon(tool)" [size]="16" aria-hidden="true" />
|
|
4902
|
+
</button>
|
|
4903
|
+
}
|
|
4904
|
+
}
|
|
4905
|
+
</div>
|
|
4906
|
+
}
|
|
4907
|
+
</div>
|
|
4908
|
+
}
|
|
4909
|
+
|
|
4910
|
+
@if (findOpen() && !readonly()) {
|
|
4911
|
+
<div class="cb-editor__link cb-editor__find" role="search" [attr.aria-label]="t().findReplace" (keydown)="onFindKeydown($event)">
|
|
4912
|
+
<label class="cb-editor__link-field">
|
|
4913
|
+
<span class="cb-editor__link-label">{{ t().findText }}</span>
|
|
4914
|
+
<input #findInput class="cb-editor__link-input" type="text" autocomplete="off" [value]="findQuery()" (input)="setFindQuery($any($event.target).value)" />
|
|
4915
|
+
</label>
|
|
4916
|
+
<label class="cb-editor__link-field">
|
|
4917
|
+
<span class="cb-editor__link-label">{{ t().replaceWith }}</span>
|
|
4918
|
+
<input class="cb-editor__link-input cb-editor__replace-input" type="text" autocomplete="off" [value]="replaceText()" (input)="replaceText.set($any($event.target).value)" />
|
|
4919
|
+
</label>
|
|
4920
|
+
<div class="cb-editor__find-options">
|
|
4921
|
+
<button
|
|
4922
|
+
type="button"
|
|
4923
|
+
class="cb-editor__tool"
|
|
4924
|
+
[class.cb-is-active]="findCase()"
|
|
4925
|
+
[attr.aria-pressed]="findCase()"
|
|
4926
|
+
[attr.aria-label]="t().matchCase"
|
|
4927
|
+
[attr.title]="t().matchCase"
|
|
4928
|
+
(click)="setFindOption('case')"
|
|
4929
|
+
>
|
|
4930
|
+
<cb-icon name="case-sensitive" [size]="16" aria-hidden="true" />
|
|
4931
|
+
</button>
|
|
4932
|
+
<button
|
|
4933
|
+
type="button"
|
|
4934
|
+
class="cb-editor__tool"
|
|
4935
|
+
[class.cb-is-active]="findWhole()"
|
|
4936
|
+
[attr.aria-pressed]="findWhole()"
|
|
4937
|
+
[attr.aria-label]="t().wholeWords"
|
|
4938
|
+
[attr.title]="t().wholeWords"
|
|
4939
|
+
(click)="setFindOption('whole')"
|
|
4940
|
+
>
|
|
4941
|
+
<cb-icon name="whole-word" [size]="16" aria-hidden="true" />
|
|
4942
|
+
</button>
|
|
4943
|
+
<span class="cb-editor__find-count" role="status">{{ findStatus() }}</span>
|
|
4944
|
+
<button type="button" class="cb-editor__tool" [disabled]="!findTotal()" [attr.aria-label]="t().findPrevious" [attr.title]="t().findPrevious + ' (Shift+Enter)'" (click)="findStep(-1)">
|
|
4945
|
+
<cb-icon name="chevron-up" [size]="16" aria-hidden="true" />
|
|
4946
|
+
</button>
|
|
4947
|
+
<button type="button" class="cb-editor__tool" [disabled]="!findTotal()" [attr.aria-label]="t().findNext" [attr.title]="t().findNext + ' (Enter)'" (click)="findStep(1)">
|
|
4948
|
+
<cb-icon name="chevron-down" [size]="16" aria-hidden="true" />
|
|
4949
|
+
</button>
|
|
4950
|
+
</div>
|
|
4951
|
+
<div class="cb-editor__link-actions">
|
|
4952
|
+
<button type="button" class="cb-editor__btn" [disabled]="!findTotal()" (click)="replaceCurrent()">{{ t().replaceOne }}</button>
|
|
4953
|
+
<button type="button" class="cb-editor__btn" [disabled]="!findTotal()" (click)="replaceAllMatches()">{{ t().replaceAll }}</button>
|
|
4954
|
+
<button type="button" class="cb-editor__tool" [attr.aria-label]="t().close" [attr.title]="t().close + ' (Esc)'" (click)="closeFind()">
|
|
4955
|
+
<cb-icon name="x" [size]="16" aria-hidden="true" />
|
|
4956
|
+
</button>
|
|
4957
|
+
</div>
|
|
4958
|
+
</div>
|
|
4959
|
+
}
|
|
4960
|
+
|
|
4961
|
+
@if (draft(); as d) {
|
|
4962
|
+
<div class="cb-editor__draft" role="status">
|
|
4963
|
+
<span>{{ fmt(t().editorDraftFound, draftDate(d.savedAt)) }}</span>
|
|
4964
|
+
<button type="button" class="cb-editor__btn cb-editor__btn--primary" (click)="restoreDraft()">{{ t().editorRestoreDraft }}</button>
|
|
4965
|
+
<button type="button" class="cb-editor__btn" (click)="discardDraft()">{{ t().editorDiscardDraft }}</button>
|
|
4966
|
+
</div>
|
|
4967
|
+
}
|
|
4968
|
+
|
|
4969
|
+
@if (codeBlock() && !readonly() && !sourceMode()) {
|
|
4970
|
+
<div class="cb-editor__context" role="toolbar" [attr.aria-label]="t().editorCodeTools" (keydown)="onContextKeydown($event)">
|
|
4971
|
+
<span class="cb-editor__group-label" aria-hidden="true">{{ t().codeBlock }}</span>
|
|
4972
|
+
<select class="cb-editor__select" tabindex="0" [attr.aria-label]="t().editorCodeLanguage" (change)="setCodeLanguage($any($event.target).value)">
|
|
4973
|
+
@for (lang of codeLanguages; track lang) {
|
|
4974
|
+
<option [value]="lang" [selected]="codeLanguage() === lang">{{ lang ? languageLabel(lang) : t().editorPlainText }}</option>
|
|
4975
|
+
}
|
|
4976
|
+
</select>
|
|
4977
|
+
<button type="button" class="cb-editor__tool cb-editor__tool--labeled" tabindex="-1" (mousedown)="hold($event)" (click)="copyCode()">
|
|
4978
|
+
<cb-icon name="copy" [size]="15" aria-hidden="true" />{{ codeCopied() ? t().editorCodeCopied : t().editorCopyCode }}
|
|
4979
|
+
</button>
|
|
4980
|
+
</div>
|
|
4981
|
+
}
|
|
4982
|
+
|
|
4983
|
+
@if (tableCell() && !readonly() && !sourceMode()) {
|
|
4984
|
+
<div class="cb-editor__context cb-editor__context--table" role="toolbar" [attr.aria-label]="t().tableTools" (keydown)="onContextKeydown($event)">
|
|
4985
|
+
<div class="cb-editor__group" role="group" [attr.aria-label]="t().tableRow">
|
|
4986
|
+
<span class="cb-editor__group-label" aria-hidden="true">{{ t().tableRow }}</span>
|
|
4987
|
+
<button type="button" class="cb-editor__tool cb-editor__tool--labeled" tabindex="0" [attr.aria-label]="t().insertRowAbove" (mousedown)="hold($event)" (click)="tableAction('rowAbove')">
|
|
4988
|
+
<cb-icon name="between-horizontal-start" [size]="15" aria-hidden="true" />{{ t().above }}
|
|
4989
|
+
</button>
|
|
4990
|
+
<button type="button" class="cb-editor__tool cb-editor__tool--labeled" tabindex="-1" [attr.aria-label]="t().insertRowBelow" (mousedown)="hold($event)" (click)="tableAction('rowBelow')">
|
|
4991
|
+
<cb-icon name="between-horizontal-end" [size]="15" aria-hidden="true" />{{ t().below }}
|
|
4992
|
+
</button>
|
|
4993
|
+
<button type="button" class="cb-editor__tool cb-editor__tool--labeled cb-editor__tool--danger" tabindex="-1" [attr.aria-label]="t().deleteRow" (mousedown)="hold($event)" (click)="tableAction('deleteRow')">
|
|
4994
|
+
<cb-icon name="trash" [size]="15" aria-hidden="true" />{{ t().tableDelete }}
|
|
4995
|
+
</button>
|
|
4996
|
+
</div>
|
|
4997
|
+
<span class="cb-editor__sep" aria-hidden="true"></span>
|
|
4998
|
+
<div class="cb-editor__group" role="group" [attr.aria-label]="t().tableColumn">
|
|
4999
|
+
<span class="cb-editor__group-label" aria-hidden="true">{{ t().tableColumn }}</span>
|
|
5000
|
+
<button type="button" class="cb-editor__tool cb-editor__tool--labeled" tabindex="-1" [attr.aria-label]="t().insertColumnLeft" (mousedown)="hold($event)" (click)="tableAction('colLeft')">
|
|
5001
|
+
<cb-icon name="between-vertical-start" [size]="15" aria-hidden="true" />{{ t().toLeft }}
|
|
5002
|
+
</button>
|
|
5003
|
+
<button type="button" class="cb-editor__tool cb-editor__tool--labeled" tabindex="-1" [attr.aria-label]="t().insertColumnRight" (mousedown)="hold($event)" (click)="tableAction('colRight')">
|
|
5004
|
+
<cb-icon name="between-vertical-end" [size]="15" aria-hidden="true" />{{ t().toRight }}
|
|
5005
|
+
</button>
|
|
5006
|
+
<button type="button" class="cb-editor__tool cb-editor__tool--labeled cb-editor__tool--danger" tabindex="-1" [attr.aria-label]="t().deleteColumn" (mousedown)="hold($event)" (click)="tableAction('deleteCol')">
|
|
5007
|
+
<cb-icon name="trash" [size]="15" aria-hidden="true" />{{ t().tableDelete }}
|
|
5008
|
+
</button>
|
|
5009
|
+
</div>
|
|
5010
|
+
<span class="cb-editor__sep" aria-hidden="true"></span>
|
|
5011
|
+
<div class="cb-editor__group" role="group" [attr.aria-label]="t().editorTableCells">
|
|
5012
|
+
<span class="cb-editor__group-label" aria-hidden="true">{{ t().editorTableCells }}</span>
|
|
5013
|
+
<button type="button" class="cb-editor__tool cb-editor__tool--labeled" tabindex="-1" [disabled]="!cellActions().right" [attr.aria-label]="t().editorMergeRight" [attr.title]="t().editorMergeRight" (mousedown)="hold($event)" (click)="tableAction('mergeRight')">
|
|
5014
|
+
<cb-icon name="table-cells-merge" [size]="15" aria-hidden="true" />→
|
|
5015
|
+
</button>
|
|
5016
|
+
<button type="button" class="cb-editor__tool cb-editor__tool--labeled" tabindex="-1" [disabled]="!cellActions().down" [attr.aria-label]="t().editorMergeDown" [attr.title]="t().editorMergeDown" (mousedown)="hold($event)" (click)="tableAction('mergeDown')">
|
|
5017
|
+
<cb-icon name="table-cells-merge" [size]="15" aria-hidden="true" />↓
|
|
5018
|
+
</button>
|
|
5019
|
+
<button type="button" class="cb-editor__tool cb-editor__tool--labeled" tabindex="-1" [disabled]="!cellActions().split" [attr.aria-label]="t().editorSplitCell" [attr.title]="t().editorSplitCell" (mousedown)="hold($event)" (click)="tableAction('split')">
|
|
5020
|
+
<cb-icon name="table-cells-split" [size]="15" aria-hidden="true" />{{ t().editorSplitCell }}
|
|
5021
|
+
</button>
|
|
5022
|
+
</div>
|
|
5023
|
+
<span class="cb-editor__sep" aria-hidden="true"></span>
|
|
5024
|
+
<button
|
|
5025
|
+
type="button"
|
|
5026
|
+
class="cb-editor__tool cb-editor__tool--labeled"
|
|
5027
|
+
tabindex="-1"
|
|
5028
|
+
[class.cb-is-active]="tableHasHeader()"
|
|
5029
|
+
[attr.aria-pressed]="tableHasHeader()"
|
|
5030
|
+
[attr.aria-label]="t().tableHeaderRow"
|
|
5031
|
+
(mousedown)="hold($event)"
|
|
5032
|
+
(click)="tableAction('header')"
|
|
5033
|
+
>
|
|
5034
|
+
<cb-icon name="panel-top" [size]="15" aria-hidden="true" />{{ t().tableHeaderRow }}
|
|
5035
|
+
</button>
|
|
5036
|
+
<span class="cb-editor__sep" aria-hidden="true"></span>
|
|
5037
|
+
<select class="cb-editor__select" tabindex="-1" [attr.aria-label]="t().tableBorders" (change)="setTableBorders($any($event.target).value, null)">
|
|
5038
|
+
<option value="all" [selected]="tableBorders().borders === 'all'">{{ t().bordersAll }}</option>
|
|
5039
|
+
<option value="horizontal" [selected]="tableBorders().borders === 'horizontal'">{{ t().bordersHorizontal }}</option>
|
|
5040
|
+
<option value="none" [selected]="tableBorders().borders === 'none'">{{ t().bordersNone }}</option>
|
|
5041
|
+
</select>
|
|
5042
|
+
<select
|
|
5043
|
+
class="cb-editor__select"
|
|
5044
|
+
tabindex="-1"
|
|
5045
|
+
[attr.aria-label]="t().borderColor"
|
|
5046
|
+
[disabled]="tableBorders().borders === 'none'"
|
|
5047
|
+
(change)="setTableBorders(null, $any($event.target).value)"
|
|
5048
|
+
>
|
|
5049
|
+
@for (color of borderColors; track color.value) {
|
|
5050
|
+
<option [value]="color.value" [selected]="tableBorders().color === color.value">{{ color.label(t()) }}</option>
|
|
5051
|
+
}
|
|
5052
|
+
</select>
|
|
5053
|
+
<span class="cb-editor__sep" aria-hidden="true"></span>
|
|
5054
|
+
<button type="button" class="cb-editor__tool cb-editor__tool--labeled cb-editor__tool--danger" tabindex="-1" [attr.aria-label]="t().deleteTable" (mousedown)="hold($event)" (click)="tableAction('deleteTable')">
|
|
5055
|
+
<cb-icon name="trash" [size]="15" aria-hidden="true" />{{ t().deleteTable }}
|
|
5056
|
+
</button>
|
|
5057
|
+
</div>
|
|
5058
|
+
}
|
|
5059
|
+
|
|
5060
|
+
@if (selectedImage() && !readonly() && !sourceMode()) {
|
|
5061
|
+
<div class="cb-editor__context" role="toolbar" [attr.aria-label]="t().imageTools" (keydown)="onContextKeydown($event)">
|
|
5062
|
+
@for (option of imageAligns; track option.icon; let first = $first) {
|
|
5063
|
+
<button
|
|
5064
|
+
type="button"
|
|
5065
|
+
class="cb-editor__tool"
|
|
5066
|
+
[class.cb-is-active]="imageState().align === option.align"
|
|
5067
|
+
[attr.aria-pressed]="imageState().align === option.align"
|
|
5068
|
+
[attr.aria-label]="option.label(t())"
|
|
5069
|
+
[attr.title]="option.label(t())"
|
|
5070
|
+
[tabindex]="first ? 0 : -1"
|
|
5071
|
+
(mousedown)="hold($event)"
|
|
5072
|
+
(click)="setImageAlign(option.align)"
|
|
5073
|
+
>
|
|
5074
|
+
<cb-icon [name]="option.icon" [size]="16" aria-hidden="true" />
|
|
5075
|
+
</button>
|
|
5076
|
+
}
|
|
5077
|
+
<span class="cb-editor__sep" aria-hidden="true"></span>
|
|
5078
|
+
@for (width of imageWidths; track width) {
|
|
5079
|
+
<button
|
|
5080
|
+
type="button"
|
|
5081
|
+
class="cb-editor__tool cb-editor__tool--text"
|
|
5082
|
+
[class.cb-is-active]="imageState().width === width"
|
|
5083
|
+
[attr.aria-pressed]="imageState().width === width"
|
|
5084
|
+
[attr.aria-label]="imageWidthLabel(width)"
|
|
5085
|
+
[tabindex]="-1"
|
|
5086
|
+
(mousedown)="hold($event)"
|
|
5087
|
+
(click)="setImageWidth(width)"
|
|
5088
|
+
>{{ width }}</button>
|
|
5089
|
+
}
|
|
5090
|
+
<button
|
|
5091
|
+
type="button"
|
|
5092
|
+
class="cb-editor__tool"
|
|
5093
|
+
[class.cb-is-active]="!imageState().width"
|
|
5094
|
+
[attr.aria-pressed]="!imageState().width"
|
|
5095
|
+
[attr.aria-label]="t().imageOriginalSize"
|
|
5096
|
+
[attr.title]="t().imageOriginalSize"
|
|
5097
|
+
[tabindex]="-1"
|
|
5098
|
+
(mousedown)="hold($event)"
|
|
5099
|
+
(click)="setImageWidth(null)"
|
|
5100
|
+
>
|
|
5101
|
+
<cb-icon name="ratio" [size]="16" aria-hidden="true" />
|
|
5102
|
+
</button>
|
|
5103
|
+
<span class="cb-editor__sep" aria-hidden="true"></span>
|
|
5104
|
+
<button type="button" class="cb-editor__tool cb-editor__tool--labeled" [attr.aria-label]="t().cropImage" [tabindex]="-1" (click)="openCrop()">
|
|
5105
|
+
<cb-icon name="crop" [size]="15" aria-hidden="true" />{{ t().cropImage }}
|
|
5106
|
+
</button>
|
|
5107
|
+
<button
|
|
5108
|
+
type="button"
|
|
5109
|
+
class="cb-editor__tool cb-editor__tool--labeled"
|
|
5110
|
+
[class.cb-is-active]="imageHasCaption()"
|
|
5111
|
+
[attr.aria-pressed]="imageHasCaption()"
|
|
5112
|
+
[attr.aria-label]="t().editorImageCaption"
|
|
5113
|
+
[tabindex]="-1"
|
|
5114
|
+
(mousedown)="hold($event)"
|
|
5115
|
+
(click)="toggleCaption()"
|
|
5116
|
+
>
|
|
5117
|
+
<cb-icon name="captions" [size]="15" aria-hidden="true" />{{ t().editorImageCaption }}
|
|
5118
|
+
</button>
|
|
5119
|
+
<button type="button" class="cb-editor__tool" [attr.aria-label]="t().altText" [attr.title]="t().altText" [tabindex]="-1" (click)="openAlt()">
|
|
5120
|
+
<cb-icon name="captions" [size]="16" aria-hidden="true" />
|
|
5121
|
+
</button>
|
|
5122
|
+
<button
|
|
5123
|
+
type="button"
|
|
5124
|
+
class="cb-editor__tool cb-editor__tool--danger"
|
|
5125
|
+
[attr.aria-label]="t().deleteImage"
|
|
5126
|
+
[attr.title]="t().deleteImage"
|
|
5127
|
+
[tabindex]="-1"
|
|
5128
|
+
(mousedown)="hold($event)"
|
|
5129
|
+
(click)="deleteImage()"
|
|
5130
|
+
>
|
|
5131
|
+
<cb-icon name="trash" [size]="16" aria-hidden="true" />
|
|
5132
|
+
</button>
|
|
5133
|
+
</div>
|
|
5134
|
+
}
|
|
5135
|
+
|
|
5136
|
+
@if (tablePanel()) {
|
|
5137
|
+
<div class="cb-editor__link cb-editor__picker-panel" role="group" [attr.aria-label]="t().insertTable" (keydown.escape)="closeTablePanel()">
|
|
5138
|
+
<div class="cb-editor__picker" (mouseleave)="pickerHover.set(null)">
|
|
5139
|
+
@for (r of pickerRows; track r) {
|
|
5140
|
+
<div class="cb-editor__picker-row">
|
|
5141
|
+
@for (c of pickerCols; track c) {
|
|
5142
|
+
<button
|
|
5143
|
+
type="button"
|
|
5144
|
+
class="cb-editor__picker-cell"
|
|
5145
|
+
[class.cb-is-on]="pickerOn(r, c)"
|
|
5146
|
+
[attr.aria-label]="tableSizeLabel(r, c)"
|
|
5147
|
+
[tabindex]="r === pickerFocus().r && c === pickerFocus().c ? 0 : -1"
|
|
5148
|
+
(mouseenter)="pickerHover.set({ r, c })"
|
|
5149
|
+
(focus)="pickerHover.set({ r, c }); pickerFocus.set({ r, c })"
|
|
5150
|
+
(keydown)="onPickerKeydown($event, r, c)"
|
|
5151
|
+
(click)="insertTable(r, c)"
|
|
5152
|
+
></button>
|
|
5153
|
+
}
|
|
5154
|
+
</div>
|
|
5155
|
+
}
|
|
5156
|
+
</div>
|
|
5157
|
+
<div class="cb-editor__picker-side">
|
|
5158
|
+
<span class="cb-editor__picker-size" aria-live="polite">{{ pickerHover() ? tableSizeLabel(pickerHover()!.r, pickerHover()!.c) : t().insertTable }}</span>
|
|
5159
|
+
<label class="cb-editor__link-check">
|
|
5160
|
+
<input type="checkbox" [checked]="tableHeader()" (change)="tableHeader.set($any($event.target).checked)" />
|
|
5161
|
+
{{ t().tableHeaderRow }}
|
|
5162
|
+
</label>
|
|
5163
|
+
<button type="button" class="cb-editor__btn" (click)="closeTablePanel()">{{ t().cancel }}</button>
|
|
5164
|
+
</div>
|
|
5165
|
+
</div>
|
|
5166
|
+
}
|
|
5167
|
+
|
|
5168
|
+
@if (crop(); as c) {
|
|
5169
|
+
<div class="cb-editor__crop" role="group" [attr.aria-label]="t().cropImage" (keydown.escape)="closeCrop()">
|
|
5170
|
+
<div class="cb-editor__crop-stage" #cropStage>
|
|
5171
|
+
<img class="cb-editor__crop-img" [src]="c.src" alt="" draggable="false" />
|
|
5172
|
+
<div
|
|
5173
|
+
class="cb-editor__crop-box"
|
|
5174
|
+
role="slider"
|
|
5175
|
+
tabindex="0"
|
|
5176
|
+
[attr.aria-label]="t().cropArea"
|
|
5177
|
+
[attr.aria-valuetext]="cropValueText()"
|
|
5178
|
+
[attr.aria-describedby]="cropHelpId"
|
|
5179
|
+
[style.left.%]="c.rect.x * 100"
|
|
5180
|
+
[style.top.%]="c.rect.y * 100"
|
|
5181
|
+
[style.width.%]="c.rect.w * 100"
|
|
5182
|
+
[style.height.%]="c.rect.h * 100"
|
|
5183
|
+
(pointerdown)="startCropDrag($event, null)"
|
|
5184
|
+
(keydown)="onCropKeydown($event)"
|
|
5185
|
+
>
|
|
5186
|
+
@for (corner of cropCorners; track corner) {
|
|
5187
|
+
<span class="cb-editor__crop-handle" [class]="'cb-editor__crop-handle cb-editor__crop-handle--' + corner" (pointerdown)="startCropDrag($event, corner)"></span>
|
|
5188
|
+
}
|
|
5189
|
+
</div>
|
|
5190
|
+
</div>
|
|
5191
|
+
<p class="cb-editor__crop-help" [id]="cropHelpId">{{ t().cropInstructions }}</p>
|
|
5192
|
+
<div class="cb-editor__link-actions">
|
|
5193
|
+
@for (ratio of cropRatios; track ratio.label) {
|
|
5194
|
+
<button
|
|
5195
|
+
type="button"
|
|
5196
|
+
class="cb-editor__btn"
|
|
5197
|
+
[class.cb-is-active]="c.ratio === ratio.value"
|
|
5198
|
+
[attr.aria-pressed]="c.ratio === ratio.value"
|
|
5199
|
+
(click)="setCropRatio(ratio.value)"
|
|
5200
|
+
>{{ ratio.value === null ? t().cropFree : ratio.label }}</button>
|
|
5201
|
+
}
|
|
5202
|
+
<span class="cb-editor__crop-spacer"></span>
|
|
5203
|
+
<button type="button" class="cb-editor__btn cb-editor__btn--primary" [disabled]="c.busy" (click)="applyCrop()">{{ t().cropImage }}</button>
|
|
5204
|
+
<button type="button" class="cb-editor__btn" (click)="closeCrop()">{{ t().cancel }}</button>
|
|
5205
|
+
</div>
|
|
5206
|
+
@if (c.error) {
|
|
5207
|
+
<p class="cb-editor__error" role="alert">{{ t().cropError }}</p>
|
|
5208
|
+
}
|
|
5209
|
+
</div>
|
|
5210
|
+
}
|
|
5211
|
+
|
|
5212
|
+
@if (altPanel()) {
|
|
5213
|
+
<form class="cb-editor__link" role="group" [attr.aria-label]="t().altText" (submit)="applyAlt($event)" (keydown.escape)="closeAlt()">
|
|
5214
|
+
<label class="cb-editor__link-field">
|
|
5215
|
+
<span class="cb-editor__link-label">{{ t().altText }}</span>
|
|
5216
|
+
<input #altInput class="cb-editor__link-input" type="text" [value]="selectedImage()?.getAttribute('alt') ?? ''" />
|
|
5217
|
+
</label>
|
|
5218
|
+
<div class="cb-editor__link-actions">
|
|
5219
|
+
<button type="submit" class="cb-editor__btn cb-editor__btn--primary">{{ t().accept }}</button>
|
|
5220
|
+
<button type="button" class="cb-editor__btn" (click)="closeAlt()">{{ t().cancel }}</button>
|
|
5221
|
+
</div>
|
|
5222
|
+
</form>
|
|
5223
|
+
}
|
|
5224
|
+
|
|
5225
|
+
@if (linkPanel(); as panel) {
|
|
5226
|
+
<form class="cb-editor__link" role="group" [attr.aria-label]="t().insertLink" (submit)="applyLink($event)" (keydown.escape)="closeLink()">
|
|
5227
|
+
<label class="cb-editor__link-field">
|
|
5228
|
+
<span class="cb-editor__link-label">{{ t().linkUrl }}</span>
|
|
5229
|
+
<input
|
|
5230
|
+
#linkInput
|
|
5231
|
+
class="cb-editor__link-input"
|
|
5232
|
+
type="text"
|
|
5233
|
+
inputmode="url"
|
|
5234
|
+
autocomplete="off"
|
|
5235
|
+
[value]="panel.url"
|
|
5236
|
+
[attr.aria-invalid]="linkError() ? 'true' : null"
|
|
5237
|
+
[attr.aria-describedby]="linkError() ? linkErrorId : null"
|
|
5238
|
+
(input)="linkUrl.set($any($event.target).value)"
|
|
5239
|
+
/>
|
|
5240
|
+
</label>
|
|
5241
|
+
<label class="cb-editor__link-check">
|
|
5242
|
+
<input type="checkbox" [checked]="panel.newTab" (change)="linkNewTab.set($any($event.target).checked)" />
|
|
5243
|
+
{{ t().openInNewTab }}
|
|
5244
|
+
</label>
|
|
5245
|
+
<div class="cb-editor__link-actions">
|
|
5246
|
+
<button type="submit" class="cb-editor__btn cb-editor__btn--primary">{{ t().accept }}</button>
|
|
5247
|
+
@if (panel.editing) {
|
|
5248
|
+
<button type="button" class="cb-editor__btn" (click)="removeLink()">
|
|
5249
|
+
<cb-icon name="unlink" [size]="14" aria-hidden="true" />
|
|
5250
|
+
{{ t().removeLink }}
|
|
5251
|
+
</button>
|
|
5252
|
+
}
|
|
5253
|
+
<button type="button" class="cb-editor__btn" (click)="closeLink()">{{ t().cancel }}</button>
|
|
5254
|
+
</div>
|
|
5255
|
+
@if (linkError()) {
|
|
5256
|
+
<p class="cb-editor__error" [id]="linkErrorId" role="alert">{{ t().invalidUrl }}</p>
|
|
5257
|
+
}
|
|
5258
|
+
</form>
|
|
5259
|
+
}
|
|
5260
|
+
|
|
5261
|
+
</div>
|
|
5262
|
+
|
|
5263
|
+
<div #body class="cb-editor__body">
|
|
5264
|
+
<div
|
|
5265
|
+
#surface
|
|
5266
|
+
class="cb-editor__surface"
|
|
5267
|
+
[id]="surfaceId"
|
|
5268
|
+
role="textbox"
|
|
5269
|
+
[hidden]="sourceMode()"
|
|
5270
|
+
[class.cb-is-empty]="isEmpty()"
|
|
5271
|
+
[attr.aria-label]="ariaLabel() ?? t().textEditor"
|
|
5272
|
+
[attr.aria-disabled]="isDisabled() || null"
|
|
5273
|
+
[attr.aria-readonly]="readonly() || null"
|
|
5274
|
+
[attr.aria-invalid]="invalidState() ? 'true' : null"
|
|
5275
|
+
[attr.aria-required]="fieldState.required() || null"
|
|
5276
|
+
[attr.aria-describedby]="describedBy()"
|
|
5277
|
+
aria-multiline="true"
|
|
5278
|
+
[attr.aria-activedescendant]="slash() && slashItems().length ? slashOptionId(slash()!.active) : mention() && mentionResults().length ? mentionOptionId(mention()!.active) : null"
|
|
5279
|
+
[attr.contenteditable]="!isDisabled() && !readonly()"
|
|
5280
|
+
[attr.data-placeholder]="readonly() ? '' : (placeholder() ?? t().editorPlaceholder)"
|
|
5281
|
+
[style.minHeight]="readonly() ? null : height()"
|
|
5282
|
+
[style.maxHeight]="readonly() || fullscreen() ? null : maxHeight()"
|
|
5283
|
+
(input)="onSurfaceInput($event)"
|
|
5284
|
+
(beforeinput)="onBeforeInput($event)"
|
|
5285
|
+
(keydown)="onSurfaceKeydown($event)"
|
|
5286
|
+
(paste)="onPaste($event)"
|
|
5287
|
+
(drop)="onDrop($event)"
|
|
5288
|
+
(click)="onSurfaceClick($event)"
|
|
5289
|
+
(mousedown)="pointerSelecting = true"
|
|
5290
|
+
(scroll)="refreshFrame()"
|
|
5291
|
+
(blur)="onBlur()"
|
|
5292
|
+
></div>
|
|
5293
|
+
@if (imageFrame(); as frame) {
|
|
5294
|
+
<div
|
|
5295
|
+
class="cb-editor__img-frame"
|
|
5296
|
+
aria-hidden="true"
|
|
5297
|
+
[style.left.px]="frame.left"
|
|
5298
|
+
[style.top.px]="frame.top"
|
|
5299
|
+
[style.width.px]="frame.width"
|
|
5300
|
+
[style.height.px]="frame.height"
|
|
5301
|
+
>
|
|
5302
|
+
<span class="cb-editor__img-handle" [attr.title]="t().imageResize" (pointerdown)="startResize($event)"></span>
|
|
5303
|
+
</div>
|
|
5304
|
+
}
|
|
5305
|
+
@if (bubble(); as b) {
|
|
5306
|
+
<div
|
|
5307
|
+
class="cb-editor__bubble"
|
|
5308
|
+
[class.cb-editor__bubble--below]="b.below"
|
|
5309
|
+
role="toolbar"
|
|
5310
|
+
[attr.aria-label]="t().quickFormat"
|
|
5311
|
+
[style.left.px]="b.left"
|
|
5312
|
+
[style.top.px]="b.top"
|
|
5313
|
+
(keydown)="onContextKeydown($event)"
|
|
5314
|
+
>
|
|
5315
|
+
@for (tool of bubbleTools; track tool; let first = $first) {
|
|
5316
|
+
<button
|
|
5317
|
+
type="button"
|
|
5318
|
+
class="cb-editor__tool"
|
|
5319
|
+
[class.cb-is-active]="isActive(tool)"
|
|
5320
|
+
[attr.aria-pressed]="pressable(tool) ? isActive(tool) : null"
|
|
5321
|
+
[attr.aria-label]="toolLabel(tool)"
|
|
5322
|
+
[attr.title]="toolTitle(tool)"
|
|
5323
|
+
[tabindex]="first ? 0 : -1"
|
|
5324
|
+
(mousedown)="hold($event)"
|
|
5325
|
+
(click)="runBubble(tool)"
|
|
5326
|
+
>
|
|
5327
|
+
<cb-icon [name]="toolIcon(tool)" [size]="16" aria-hidden="true" />
|
|
5328
|
+
</button>
|
|
5329
|
+
}
|
|
5330
|
+
</div>
|
|
5331
|
+
}
|
|
5332
|
+
@if (mention(); as m) {
|
|
5333
|
+
@if (mentionResults().length) {
|
|
5334
|
+
<div class="cb-editor__slash cb-editor__mentions" role="listbox" [id]="mentionListId" [attr.aria-label]="t().editorMentions" [style.left.px]="m.left" [style.top.px]="m.top">
|
|
5335
|
+
@for (item of mentionResults(); track item.id; let i = $index) {
|
|
5336
|
+
<div
|
|
5337
|
+
class="cb-editor__slash-item"
|
|
5338
|
+
role="option"
|
|
5339
|
+
[id]="mentionOptionId(i)"
|
|
5340
|
+
[class.cb-is-active]="i === m.active"
|
|
5341
|
+
[attr.aria-selected]="i === m.active"
|
|
5342
|
+
(mousedown)="hold($event)"
|
|
5343
|
+
(click)="applyMention(item)"
|
|
5344
|
+
>
|
|
5345
|
+
<cb-icon name="at-sign" [size]="15" aria-hidden="true" />
|
|
5346
|
+
<span class="cb-editor__mention-label">{{ item.label }}</span>
|
|
5347
|
+
@if (item.description) {
|
|
5348
|
+
<span class="cb-editor__mention-desc">{{ item.description }}</span>
|
|
5349
|
+
}
|
|
5350
|
+
</div>
|
|
5351
|
+
}
|
|
5352
|
+
</div>
|
|
5353
|
+
}
|
|
5354
|
+
}
|
|
5355
|
+
@if (slash(); as sl) {
|
|
5356
|
+
@if (slashItems().length) {
|
|
5357
|
+
<div class="cb-editor__slash" role="listbox" [id]="slashListId" [attr.aria-label]="t().insertBlock" [style.left.px]="sl.left" [style.top.px]="sl.top">
|
|
5358
|
+
@for (item of slashItems(); track item.id; let i = $index) {
|
|
5359
|
+
<div
|
|
5360
|
+
class="cb-editor__slash-item"
|
|
5361
|
+
role="option"
|
|
5362
|
+
[id]="slashOptionId(i)"
|
|
5363
|
+
[class.cb-is-active]="i === sl.active"
|
|
5364
|
+
[attr.aria-selected]="i === sl.active"
|
|
5365
|
+
(mousedown)="hold($event)"
|
|
5366
|
+
(click)="applySlash(item)"
|
|
5367
|
+
>
|
|
5368
|
+
<cb-icon [name]="item.icon" [size]="16" aria-hidden="true" />
|
|
5369
|
+
{{ item.label(t()) }}
|
|
5370
|
+
</div>
|
|
5371
|
+
}
|
|
5372
|
+
</div>
|
|
5373
|
+
}
|
|
5374
|
+
}
|
|
5375
|
+
</div>
|
|
5376
|
+
|
|
5377
|
+
@if (sourceMode()) {
|
|
5378
|
+
<textarea
|
|
5379
|
+
class="cb-editor__source"
|
|
5380
|
+
spellcheck="false"
|
|
5381
|
+
[attr.aria-label]="t().sourceCode"
|
|
5382
|
+
[style.minHeight]="height()"
|
|
5383
|
+
[value]="sourceText()"
|
|
5384
|
+
(input)="sourceText.set($any($event.target).value)"
|
|
5385
|
+
(blur)="onBlur()"
|
|
5386
|
+
></textarea>
|
|
5387
|
+
}
|
|
5388
|
+
|
|
5389
|
+
@if (message() || maxLength() || (wordCount() && !readonly()) || savedAt()) {
|
|
5390
|
+
<div class="cb-editor__footer">
|
|
5391
|
+
@if (message()) {
|
|
5392
|
+
<p class="cb-editor__error" role="alert">{{ message() }}</p>
|
|
5393
|
+
}
|
|
5394
|
+
@if (savedAt(); as at) {
|
|
5395
|
+
<span class="cb-editor__saved">{{ fmt(t().editorDraftSaved, draftTime(at)) }}</span>
|
|
5396
|
+
}
|
|
5397
|
+
@if (maxLength() || (wordCount() && !readonly())) {
|
|
5398
|
+
<span class="cb-editor__count" [id]="countId" [class.cb-is-over]="!!maxLength() && length() > maxLength()!">{{ countLine() }}</span>
|
|
5399
|
+
}
|
|
5400
|
+
</div>
|
|
5401
|
+
}
|
|
5402
|
+
|
|
5403
|
+
<input #imageInput type="file" accept="image/png,image/jpeg,image/gif,image/webp" hidden (change)="onImageFile($event)" />
|
|
5404
|
+
`, styles: [":host{display:block;width:100%;border:var(--cb-border-width) solid var(--cb-color-border);border-radius:var(--cb-radius-lg);background:var(--cb-color-surface);color:var(--cb-color-text);font-family:var(--cb-font-sans);overflow:clip}.cb-editor__header{position:sticky;top:var(--cb-editor-sticky-top, 0px);z-index:3;background:var(--cb-color-surface)}.cb-editor__toolbar{display:grid;gap:.25rem;padding:var(--cb-space-2) var(--cb-space-3);border-bottom:var(--cb-border-width) solid var(--cb-color-border);background:var(--cb-color-surface-subtle, var(--cb-color-surface))}.cb-editor__row{display:flex;align-items:center;flex-wrap:wrap;gap:.2rem}.cb-editor__row+.cb-editor__row{padding-top:.25rem;border-top:1px dashed var(--cb-color-border)}.cb-editor__select--font{width:10.5rem}.cb-editor__select--size{width:5.5rem}.cb-editor__select{height:1.75rem;padding:0 .4rem;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-sm);background:var(--cb-color-bg);color:var(--cb-color-text);font:inherit;font-size:var(--cb-text-sm);cursor:pointer}.cb-editor__sep{width:1px;height:1.1rem;margin:0 .25rem;background:var(--cb-color-border)}.cb-editor__tool{display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;border:none;border-radius:var(--cb-radius-sm);background:transparent;color:var(--cb-color-text);cursor:pointer}.cb-editor__tool:hover{background:color-mix(in srgb,var(--cb-color-primary-500) 12%,transparent)}.cb-editor__tool:disabled{opacity:.4;cursor:not-allowed}.cb-editor__tool:focus-visible,.cb-editor__select:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:1px}.cb-editor__tool.cb-is-active{color:var(--cb-color-primary-700);background:color-mix(in srgb,var(--cb-color-primary-500) 18%,transparent)}:host-context([data-theme=\"dark\"]) .cb-editor__tool.cb-is-active{color:var(--cb-color-primary-300)}@media(forced-colors:active){.cb-editor__tool.cb-is-active{outline:2px solid Highlight}}.cb-editor__link{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--cb-space-2) var(--cb-space-3);padding:var(--cb-space-2) var(--cb-space-3);border-bottom:var(--cb-border-width) solid var(--cb-color-border);background:var(--cb-color-surface-subtle, var(--cb-color-surface));font-size:var(--cb-text-sm)}.cb-editor__link-field{display:grid;flex:1 1 14rem;gap:.2rem}.cb-editor__link-label{font-size:var(--cb-text-xs);color:var(--cb-color-text-muted)}.cb-editor__link-input{height:2rem;padding:0 .5rem;border:1px solid var(--cb-color-border-strong, var(--cb-color-border));border-radius:var(--cb-radius-sm);background:var(--cb-color-bg, var(--cb-color-surface));color:var(--cb-color-text);font:inherit}.cb-editor__link-input[aria-invalid=true]{border-color:var(--cb-color-danger-500)}.cb-editor__link-input:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:0}.cb-editor__link-check{display:inline-flex;align-items:center;gap:.35rem;min-height:2rem}.cb-editor__link-actions{display:inline-flex;gap:var(--cb-space-2)}.cb-editor__btn{display:inline-flex;align-items:center;gap:.3rem;height:2rem;padding:0 .7rem;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-sm);background:var(--cb-color-surface);color:var(--cb-color-text);font:inherit;cursor:pointer}.cb-editor__btn:hover{background:var(--cb-color-surface-hover)}.cb-editor__btn:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:1px}.cb-editor__btn--primary{color:#fff;background:var(--cb-color-primary-700, #047857);border-color:var(--cb-color-primary-600)}.cb-editor__btn--primary:hover{background:var(--cb-color-primary-700)}.cb-editor__error{flex-basis:100%;margin:0;font-size:var(--cb-text-xs);color:var(--cb-color-danger-600)}.cb-editor__surface{padding:var(--cb-space-3);font-family:var(--cb-editor-font-family, Carlito, Calibri, var(--cb-font-sans), sans-serif);font-size:var(--cb-editor-font-size, 16px);line-height:1.6;outline:none;overflow-y:auto}.cb-editor__surface:empty:before,.cb-editor__surface.cb-is-empty:not(:focus):before{content:attr(data-placeholder);color:var(--cb-color-text-muted);pointer-events:none}.cb-editor__surface.cb-is-empty:not(:empty):before{position:absolute}.cb-editor__surface{position:relative}.cb-editor__source{display:block;box-sizing:border-box;width:100%;padding:var(--cb-space-3);border:none;resize:vertical;background:var(--cb-color-surface-subtle, var(--cb-color-surface));color:var(--cb-color-text);font-family:var(--cb-font-mono, monospace);font-size:var(--cb-text-xs);line-height:1.6;outline:none}.cb-editor__footer{display:flex;align-items:center;justify-content:space-between;gap:var(--cb-space-3);padding:var(--cb-space-1) var(--cb-space-3) var(--cb-space-2);border-top:var(--cb-border-width) solid var(--cb-color-border)}.cb-editor__count{margin-left:auto;font-size:var(--cb-text-xs);color:var(--cb-color-text-muted);font-variant-numeric:tabular-nums}.cb-editor__count.cb-is-over{color:var(--cb-color-danger-600);font-weight:var(--cb-font-weight-semibold)}:host(.cb-is-readonly){border-color:transparent;background:transparent}:host(.cb-is-readonly) .cb-editor__surface{padding:0}:host ::ng-deep .cb-editor__surface h1{font-size:1.5rem;margin:.4em 0}:host ::ng-deep .cb-editor__surface h2{font-size:1.25rem;margin:.4em 0}:host ::ng-deep .cb-editor__surface h3{font-size:1.1rem;margin:.4em 0}:host ::ng-deep .cb-editor__surface p{margin:.4em 0}:host ::ng-deep .cb-editor__surface ul,:host ::ng-deep .cb-editor__surface ol{margin:.4em 0;padding-left:1.5rem}:host ::ng-deep .cb-editor__surface blockquote{margin:.5em 0;padding:.25em .9em;border-left:3px solid var(--cb-color-primary-400, var(--cb-color-border));color:var(--cb-color-text-muted)}:host ::ng-deep .cb-editor__surface a{color:var(--cb-color-primary-text);text-decoration:underline}:host ::ng-deep .cb-editor__surface code{padding:.1em .35em;border-radius:var(--cb-radius-sm);background:var(--cb-color-surface-subtle, rgb(0 0 0 / .06));font-family:var(--cb-font-mono, monospace);font-size:.9em}:host ::ng-deep .cb-editor__surface pre{padding:var(--cb-space-3);border-radius:var(--cb-radius-md);background:var(--cb-color-surface-subtle, rgb(0 0 0 / .06));font-family:var(--cb-font-mono, monospace);white-space:pre-wrap}:host ::ng-deep .cb-editor__surface hr{margin:var(--cb-space-3) 0;border:none;border-top:1px solid var(--cb-color-border-strong, var(--cb-color-border))}:host ::ng-deep .cb-editor__surface img{max-width:100%;height:auto;border-radius:var(--cb-radius-sm, 4px)}:host(.cb-is-invalid){border-color:var(--cb-color-danger)}.cb-editor__context{display:flex;align-items:center;flex-wrap:wrap;gap:.2rem;padding:var(--cb-space-1) var(--cb-space-3);border-bottom:var(--cb-border-width) solid var(--cb-color-border);background:color-mix(in srgb,var(--cb-color-primary-500) 6%,var(--cb-color-surface))}.cb-editor__tool--text{width:auto;padding:0 .4rem;font:inherit;font-size:var(--cb-text-xs);font-variant-numeric:tabular-nums}.cb-editor__tool--danger:hover{color:var(--cb-color-danger-600);background:color-mix(in srgb,var(--cb-color-danger-500) 12%,transparent)}.cb-editor__link-field--narrow{flex:0 0 6rem}.cb-editor__body{position:relative}:host ::ng-deep .cb-editor__surface img{cursor:pointer}.cb-editor__img-frame{position:absolute;box-sizing:border-box;border:2px solid var(--cb-color-primary-500);border-radius:2px;pointer-events:none}.cb-editor__img-handle{position:absolute;right:-6px;bottom:-6px;width:12px;height:12px;border:2px solid var(--cb-color-surface);border-radius:2px;background:var(--cb-color-primary-500);cursor:nwse-resize;pointer-events:auto;touch-action:none}:host ::ng-deep .cb-editor__surface table{width:100%;margin:var(--cb-space-2) 0;border-collapse:collapse;table-layout:fixed}:host ::ng-deep .cb-editor__surface th,:host ::ng-deep .cb-editor__surface td{min-width:2rem;padding:.35rem .5rem;vertical-align:top;word-break:break-word;outline:1px dashed color-mix(in srgb,var(--cb-color-text-muted) 45%,transparent);outline-offset:-1px}:host ::ng-deep .cb-editor__surface th[style*=border],:host ::ng-deep .cb-editor__surface td[style*=border]{outline:none}:host ::ng-deep .cb-editor__surface th{font-weight:var(--cb-font-weight-semibold, 600);text-align:left}:host(.cb-is-readonly) ::ng-deep .cb-editor__surface th,:host(.cb-is-readonly) ::ng-deep .cb-editor__surface td{outline:none}:host ::ng-deep .cb-editor__surface pre code{padding:0;background:transparent;color:inherit;font-size:inherit}:host ::ng-deep .cb-editor__surface pre[data-language]:before{content:attr(data-language);float:right;margin:-.25rem -.25rem 0 .5rem;font-family:var(--cb-font-sans);font-size:.7rem;opacity:.6}.cb-editor__group{display:inline-flex;align-items:center;gap:.2rem}.cb-editor__group-label{margin-right:.15rem;font-size:var(--cb-text-xs);font-weight:var(--cb-font-weight-semibold, 600);color:var(--cb-color-text-muted)}.cb-editor__tool--labeled{width:auto;gap:.3rem;padding:0 .45rem;font:inherit;font-size:var(--cb-text-xs);white-space:nowrap}.cb-editor__picker-panel{align-items:flex-start}.cb-editor__picker{display:grid;gap:3px}.cb-editor__picker-row{display:flex;gap:3px}.cb-editor__picker-cell{width:1.1rem;height:1.1rem;padding:0;border:1px solid var(--cb-color-border-strong, var(--cb-color-border));border-radius:2px;background:var(--cb-color-surface);cursor:pointer}.cb-editor__picker-cell.cb-is-on{border-color:var(--cb-color-primary-600);background:color-mix(in srgb,var(--cb-color-primary-500) 35%,var(--cb-color-surface))}.cb-editor__picker-cell:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:1px}.cb-editor__picker-side{display:grid;gap:var(--cb-space-2);align-content:start}.cb-editor__picker-size{font-weight:var(--cb-font-weight-semibold, 600);font-variant-numeric:tabular-nums}.cb-editor__crop{display:grid;gap:var(--cb-space-2);padding:var(--cb-space-3);border-bottom:var(--cb-border-width) solid var(--cb-color-border);background:var(--cb-color-surface-subtle, var(--cb-color-surface))}.cb-editor__crop-stage{position:relative;justify-self:center;max-width:100%;overflow:hidden;-webkit-user-select:none;user-select:none;touch-action:none}.cb-editor__crop-img{display:block;max-width:100%;max-height:22rem}.cb-editor__crop-box{position:absolute;box-sizing:border-box;border:2px solid #fff;box-shadow:0 0 0 9999px #0000008c;cursor:move;outline:none}.cb-editor__crop-box:focus-visible{border-color:var(--cb-color-focus-ring)}.cb-editor__crop-handle{position:absolute;width:14px;height:14px;border:2px solid var(--cb-color-primary-600);border-radius:2px;background:#fff}.cb-editor__crop-handle--nw{left:-8px;top:-8px;cursor:nwse-resize}.cb-editor__crop-handle--ne{right:-8px;top:-8px;cursor:nesw-resize}.cb-editor__crop-handle--sw{left:-8px;bottom:-8px;cursor:nesw-resize}.cb-editor__crop-handle--se{right:-8px;bottom:-8px;cursor:nwse-resize}.cb-editor__crop-help{margin:0;font-size:var(--cb-text-xs);color:var(--cb-color-text-muted)}.cb-editor__crop-spacer{flex:1}.cb-editor__btn.cb-is-active{border-color:var(--cb-color-primary-600);color:var(--cb-color-primary-700);background:color-mix(in srgb,var(--cb-color-primary-500) 14%,var(--cb-color-surface))}.cb-editor__crop .cb-editor__link-actions{display:flex;flex-wrap:wrap}.cb-editor__select--size{width:7.5rem}.cb-editor__menu-wrap{position:relative;display:inline-flex}.cb-editor__menu-trigger{position:relative;width:auto;gap:1px;padding:0 .25rem}.cb-editor__color-bar{position:absolute;left:.35rem;right:1rem;bottom:.2rem;height:3px;border-radius:1px}.cb-editor__caret{opacity:.7}.cb-editor__menu{position:absolute;top:calc(100% + 4px);left:0;z-index:10;display:grid;gap:2px;min-width:12rem;padding:.35rem;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-md);background:var(--cb-color-surface-raised, var(--cb-color-surface));box-shadow:var(--cb-shadow-md, 0 8px 24px rgb(0 0 0 / .12))}.cb-editor__menu-item{display:flex;align-items:center;gap:.5rem;width:100%;padding:.4rem .5rem;border:none;border-radius:var(--cb-radius-sm);background:transparent;color:var(--cb-color-text);font:inherit;font-size:var(--cb-text-sm);text-align:left;cursor:pointer}.cb-editor__menu-item:hover,.cb-editor__menu-item:focus-visible{background:var(--cb-color-surface-hover);outline:none}.cb-editor__menu-item:focus-visible{box-shadow:inset 0 0 0 2px var(--cb-color-focus-ring)}.cb-editor__menu-item[aria-checked=true]{font-weight:var(--cb-font-weight-semibold, 600);color:var(--cb-color-primary-700)}.cb-editor__kbd{margin-left:auto;font-family:var(--cb-font-sans);font-size:var(--cb-text-xs);color:var(--cb-color-text-muted)}.cb-editor__swatches{display:grid;grid-template-columns:repeat(5,1.6rem);gap:.35rem;padding:.25rem}.cb-editor__swatch{width:1.6rem;height:1.6rem;padding:0;border:1px solid rgb(0 0 0 / .15);border-radius:var(--cb-radius-sm);cursor:pointer}.cb-editor__swatch:hover{transform:scale(1.1)}.cb-editor__swatch:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:2px}.cb-editor__swatch--none{display:inline-block;width:1rem;height:1rem;background:linear-gradient(135deg,transparent 45%,var(--cb-color-danger-500) 45% 55%,transparent 55%),var(--cb-color-surface)}.cb-editor__surface{resize:vertical}:host(.cb-is-readonly) .cb-editor__surface{resize:none}:host(.cb-is-fullscreen){position:fixed;inset:0;z-index:1000;display:flex;flex-direction:column;border-radius:0}:host(.cb-is-fullscreen) .cb-editor__body{flex:1;min-height:0;display:flex;flex-direction:column}:host(.cb-is-fullscreen) .cb-editor__surface{flex:1;resize:none;max-width:60rem;width:100%;margin:0 auto}:host ::ng-deep .cb-editor__surface h4{margin:.75rem 0 .35rem;font-size:1rem}:host ::ng-deep .cb-editor__surface ul[data-type=taskList]{padding-left:.25rem;list-style:none}:host ::ng-deep .cb-editor__surface ul[data-type=taskList]>li{position:relative;padding-left:1.75rem}:host ::ng-deep .cb-editor__surface ul[data-type=taskList]>li:before{content:\"\";position:absolute;left:0;top:.3em;box-sizing:border-box;width:1.05rem;height:1.05rem;border:1.5px solid var(--cb-color-border-strong, var(--cb-color-border));border-radius:3px;background:var(--cb-color-surface);cursor:pointer}:host ::ng-deep .cb-editor__surface ul[data-type=taskList]>li[data-checked=true]:before{content:\"\\2713\";display:grid;place-items:center;border-color:var(--cb-color-primary-600);background:var(--cb-color-primary-700, #047857);color:#fff;font-size:.75rem;font-weight:700;line-height:1}:host ::ng-deep .cb-editor__surface ul[data-type=taskList]>li[data-checked=true]{color:var(--cb-color-text-muted);text-decoration:line-through}.cb-editor__menu--chars{left:auto;right:0;width:max-content;max-height:18rem;overflow-y:auto}.cb-editor__chars-label{display:block;padding:.35rem .25rem .15rem;font-size:var(--cb-text-xs);font-weight:var(--cb-font-weight-semibold, 600);color:var(--cb-color-text-muted)}.cb-editor__chars{display:grid;grid-template-columns:repeat(8,2rem);gap:2px}.cb-editor__char{width:2rem;height:2rem;padding:0;border:none;border-radius:var(--cb-radius-sm);background:transparent;color:var(--cb-color-text);font-size:1.05rem;line-height:1;cursor:pointer}.cb-editor__char:hover,.cb-editor__char:focus-visible{background:var(--cb-color-surface-hover);outline:none}.cb-editor__char:focus-visible{box-shadow:inset 0 0 0 2px var(--cb-color-focus-ring)}.cb-editor__find{align-items:flex-end}.cb-editor__find-options{display:inline-flex;align-items:center;gap:.2rem;min-height:2rem}.cb-editor__find-count{min-width:5.5rem;padding:0 .35rem;font-size:var(--cb-text-xs);color:var(--cb-color-text-muted);font-variant-numeric:tabular-nums;text-align:center}.cb-editor__find .cb-editor__link-actions{align-items:center}.cb-editor__bubble{position:absolute;z-index:4;display:flex;gap:2px;padding:.2rem;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-md);background:var(--cb-color-surface-raised, var(--cb-color-surface));box-shadow:var(--cb-shadow-md, 0 8px 24px rgb(0 0 0 / .12));transform:translate(-50%,calc(-100% - 6px))}.cb-editor__bubble--below{transform:translate(-50%,6px)}.cb-editor__slash{position:absolute;z-index:4;display:grid;gap:1px;width:15rem;max-height:17rem;margin-top:4px;padding:.3rem;overflow-y:auto;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-md);background:var(--cb-color-surface-raised, var(--cb-color-surface));box-shadow:var(--cb-shadow-md, 0 8px 24px rgb(0 0 0 / .12))}.cb-editor__slash-item{display:flex;align-items:center;gap:.55rem;padding:.4rem .5rem;border-radius:var(--cb-radius-sm);font-size:var(--cb-text-sm);cursor:pointer}.cb-editor__slash-item:hover{background:var(--cb-color-surface-hover)}.cb-editor__slash-item.cb-is-active{color:var(--cb-color-primary-700);background:color-mix(in srgb,var(--cb-color-primary-500) 14%,transparent)}:host-context([data-theme=\"dark\"]) .cb-editor__slash-item.cb-is-active{color:var(--cb-color-primary-300)}@media(forced-colors:active){.cb-editor__slash-item.cb-is-active{outline:2px solid Highlight}}:host ::ng-deep .cb-editor__surface figure{margin:.5em 0}:host ::ng-deep .cb-editor__surface figure img{display:block;max-width:100%}:host ::ng-deep .cb-editor__surface figcaption{padding-top:.35rem;color:var(--cb-color-text-muted);font-size:.875em;text-align:center}:host ::ng-deep .cb-editor__surface figcaption:empty:before,:host ::ng-deep .cb-editor__surface figcaption:has(>br:only-child):before{content:attr(data-placeholder)}:host ::ng-deep .cb-editor__surface details{margin:.5em 0;padding:.35rem .75rem;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-md)}:host ::ng-deep .cb-editor__surface summary{font-weight:var(--cb-font-weight-semibold);cursor:pointer}:host ::ng-deep .cb-editor__surface details[open]>summary{margin-bottom:.35rem}:host ::ng-deep .cb-editor__surface nav[data-type=toc]{margin:.5em 0;padding:.5rem .9rem;border-left:3px solid var(--cb-color-primary-400, var(--cb-color-border));border-radius:0 var(--cb-radius-md) var(--cb-radius-md) 0;background:color-mix(in srgb,var(--cb-color-primary-500) 5%,transparent);-webkit-user-select:none;user-select:none}:host ::ng-deep .cb-editor__surface nav[data-type=toc] p{margin:0 0 .25rem}:host ::ng-deep .cb-editor__surface nav[data-type=toc] ol{margin:0;padding-left:1.25rem}:host ::ng-deep .cb-editor__surface nav[data-type=toc] a{cursor:pointer}:host ::ng-deep .cb-editor__surface [data-mention]{padding:0 .2rem;border-radius:var(--cb-radius-sm);background:color-mix(in srgb,var(--cb-color-primary-500) 14%,transparent);color:var(--cb-color-primary-700);font-weight:var(--cb-font-weight-medium);white-space:nowrap}:host-context([data-theme=\"dark\"]) ::ng-deep .cb-editor__surface [data-mention]{color:var(--cb-color-primary-300)}.cb-editor__mentions{width:17rem}.cb-editor__mention-label{font-weight:var(--cb-font-weight-medium)}.cb-editor__mention-desc{margin-left:auto;overflow:hidden;color:var(--cb-color-text-muted);font-size:var(--cb-text-xs);text-overflow:ellipsis;white-space:nowrap}.cb-editor__draft{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;padding:var(--cb-space-2) var(--cb-space-3);border-bottom:var(--cb-border-width) solid var(--cb-color-border);background:color-mix(in srgb,var(--cb-color-warning-500, #f59e0b) 12%,var(--cb-color-surface));font-size:var(--cb-text-sm)}.cb-editor__draft>span{flex:1 1 14rem}.cb-editor__saved{color:var(--cb-color-text-muted);font-size:var(--cb-text-xs)}\n"] }]
|
|
5405
|
+
}], ctorParameters: () => [], propDecorators: { placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], maxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxHeight", required: false }] }], wordCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "wordCount", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], toolbar: [{ type: i0.Input, args: [{ isSignal: true, alias: "toolbar", required: false }] }], maxLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxLength", required: false }] }], images: [{ type: i0.Input, args: [{ isSignal: true, alias: "images", required: false }] }], imageUpload: [{ type: i0.Input, args: [{ isSignal: true, alias: "imageUpload", required: false }] }], maxImageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxImageSize", required: false }] }], pastePlainText: [{ type: i0.Input, args: [{ isSignal: true, alias: "pastePlainText", required: false }] }], fonts: [{ type: i0.Input, args: [{ isSignal: true, alias: "fonts", required: false }] }], fontsHref: [{ type: i0.Input, args: [{ isSignal: true, alias: "fontsHref", required: false }] }], bubbleMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "bubbleMenu", required: false }] }], slashCommands: [{ type: i0.Input, args: [{ isSignal: true, alias: "slashCommands", required: false }] }], autolink: [{ type: i0.Input, args: [{ isSignal: true, alias: "autolink", required: false }] }], typography: [{ type: i0.Input, args: [{ isSignal: true, alias: "typography", required: false }] }], mentions: [{ type: i0.Input, args: [{ isSignal: true, alias: "mentions", required: false }] }], autosaveKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "autosaveKey", required: false }] }], autosaveDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "autosaveDelay", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], mentionAdd: [{ type: i0.Output, args: ["mentionAdd"] }], imageError: [{ type: i0.Output, args: ["imageError"] }], findInputRef: [{ type: i0.ViewChild, args: ['findInput', { isSignal: true }] }], surfaceRef: [{ type: i0.ViewChild, args: ['surface', { isSignal: true }] }], imageInputRef: [{ type: i0.ViewChild, args: ['imageInput', { isSignal: true }] }], toolbarRef: [{ type: i0.ViewChild, args: ['toolbar', { isSignal: true }] }], linkInputRef: [{ type: i0.ViewChild, args: ['linkInput', { isSignal: true }] }], bodyRef: [{ type: i0.ViewChild, args: ['body', { isSignal: true }] }], tableRowsRef: [{ type: i0.ViewChild, args: ['tableRows', { isSignal: true }] }], tableColsRef: [{ type: i0.ViewChild, args: ['tableCols', { isSignal: true }] }], tableHeaderRef: [{ type: i0.ViewChild, args: ['tableHeader', { isSignal: true }] }], altInputRef: [{ type: i0.ViewChild, args: ['altInput', { isSignal: true }] }], cropStageRef: [{ type: i0.ViewChild, args: ['cropStage', { isSignal: true }] }] } });
|
|
5406
|
+
/** Índices y menciones no se editan letra a letra (el atributo no se guarda en el HTML). */
|
|
5407
|
+
function decorate(root) {
|
|
5408
|
+
for (const el of Array.from(root.querySelectorAll('nav[data-type="toc"], span[data-mention]')))
|
|
5409
|
+
el.setAttribute('contenteditable', 'false');
|
|
5410
|
+
}
|
|
4414
5411
|
/** Añade una hoja de estilos al documento una sola vez. */
|
|
4415
5412
|
function loadStylesheet(doc, href) {
|
|
4416
5413
|
if (!doc.head || Array.from(doc.head.querySelectorAll('link[data-cb-editor-fonts]')).some((l) => l.getAttribute('href') === href))
|
|
@@ -4469,6 +5466,22 @@ function setLinkAttrs(anchor, url, newTab) {
|
|
|
4469
5466
|
anchor.removeAttribute('rel');
|
|
4470
5467
|
}
|
|
4471
5468
|
}
|
|
5469
|
+
/** Saca de la selección los espacios de los extremos (si caen en un nodo de texto). */
|
|
5470
|
+
function trimRangeWhitespace(range) {
|
|
5471
|
+
const { startContainer: start, endContainer: end } = range;
|
|
5472
|
+
if (end.nodeType === Node.TEXT_NODE) {
|
|
5473
|
+
const before = (end.textContent ?? '').slice(0, range.endOffset);
|
|
5474
|
+
const trailing = before.length - before.trimEnd().length;
|
|
5475
|
+
if (trailing && range.toString().trim())
|
|
5476
|
+
range.setEnd(end, range.endOffset - trailing);
|
|
5477
|
+
}
|
|
5478
|
+
if (start.nodeType === Node.TEXT_NODE) {
|
|
5479
|
+
const after = (start.textContent ?? '').slice(range.startOffset);
|
|
5480
|
+
const leading = after.length - after.trimStart().length;
|
|
5481
|
+
if (leading && range.toString().trim())
|
|
5482
|
+
range.setStart(start, range.startOffset + leading);
|
|
5483
|
+
}
|
|
5484
|
+
}
|
|
4472
5485
|
function readAsDataUrl(file) {
|
|
4473
5486
|
return new Promise((resolve, reject) => {
|
|
4474
5487
|
const reader = new FileReader();
|