@widgetic/editor 3.11.4 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +245 -0
- package/dist/assets/fonts.json +331 -0
- package/dist/assets/icons/ic-browser/audio.svg +7 -0
- package/dist/assets/icons/ic-browser/document.svg +11 -0
- package/dist/assets/icons/ic-browser/error.svg +5 -0
- package/dist/assets/icons/ic-browser/folder.svg +28 -0
- package/dist/assets/icons/ic-browser/player-pause.svg +1 -0
- package/dist/assets/icons/ic-browser/player-play.svg +1 -0
- package/dist/assets/icons/ic-browser/trash.svg +1 -0
- package/dist/assets/icons/ic-fixed-position/background-deselected.svg +3 -0
- package/dist/assets/icons/ic-fixed-position/background-selected.svg +3 -0
- package/dist/assets/icons/ic-fixed-position/bottom-left-background-deselected.svg +3 -0
- package/dist/assets/icons/ic-fixed-position/bottom-left-background-selected.svg +3 -0
- package/dist/assets/icons/ic-fixed-position/bottom-left-icon.svg +4 -0
- package/dist/assets/icons/ic-fixed-position/bottom-right-background-deselected.svg +3 -0
- package/dist/assets/icons/ic-fixed-position/bottom-right-background-selected.svg +3 -0
- package/dist/assets/icons/ic-fixed-position/bottom-right-icon.svg +4 -0
- package/dist/assets/icons/ic-fixed-position/center-bottom-icon.svg +5 -0
- package/dist/assets/icons/ic-fixed-position/center-center-icon.svg +3 -0
- package/dist/assets/icons/ic-fixed-position/center-left-icon.svg +5 -0
- package/dist/assets/icons/ic-fixed-position/center-right-icon.svg +5 -0
- package/dist/assets/icons/ic-fixed-position/center-top-icon.svg +5 -0
- package/dist/assets/icons/ic-fixed-position/top-left-background-deselected.svg +3 -0
- package/dist/assets/icons/ic-fixed-position/top-left-background-selected.svg +3 -0
- package/dist/assets/icons/ic-fixed-position/top-left-icon.svg +4 -0
- package/dist/assets/icons/ic-fixed-position/top-right-background-deselected.svg +3 -0
- package/dist/assets/icons/ic-fixed-position/top-right-background-selected.svg +3 -0
- package/dist/assets/icons/ic-fixed-position/top-right-icon.svg +4 -0
- package/dist/assets/icons/trash.svg +1 -0
- package/dist/components/MockWidget.svelte +108 -0
- package/dist/components/MockWidget.svelte.d.ts +22 -0
- package/dist/components/WidgetPreview.svelte +852 -0
- package/dist/components/WidgetPreview.svelte.d.ts +100 -0
- package/dist/components/color-select/ColorSelect.svelte +258 -0
- package/dist/components/color-select/ColorSelect.svelte.d.ts +42 -0
- package/dist/components/color-select/color.d.ts +25 -0
- package/dist/components/color-select/color.js +21 -0
- package/dist/components/color-select/constants.d.ts +5 -0
- package/dist/components/color-select/constants.js +5 -0
- package/dist/components/color-select/render.d.ts +5 -0
- package/dist/components/color-select/render.js +77 -0
- package/dist/components/color-select/shaders.d.ts +3 -0
- package/dist/components/color-select/shaders.js +8 -0
- package/dist/components/editors/EmbedEditor.svelte +700 -0
- package/dist/components/editors/EmbedEditor.svelte.d.ts +50 -0
- package/dist/components/editors/PropsEditor.svelte +3952 -0
- package/dist/components/editors/PropsEditor.svelte.d.ts +62 -0
- package/dist/components/editors/props-editor.css +73 -0
- package/dist/components/ic/AngleInputController.svelte +276 -0
- package/dist/components/ic/AngleInputController.svelte.d.ts +26 -0
- package/dist/components/ic/BrowserInputController.svelte +724 -0
- package/dist/components/ic/BrowserInputController.svelte.d.ts +39 -0
- package/dist/components/ic/CheckboxInputController.svelte +203 -0
- package/dist/components/ic/CheckboxInputController.svelte.d.ts +41 -0
- package/dist/components/ic/ColorPickerInputController.svelte +1584 -0
- package/dist/components/ic/ColorPickerInputController.svelte.d.ts +50 -0
- package/dist/components/ic/DateFieldInputController.svelte +453 -0
- package/dist/components/ic/DateFieldInputController.svelte.d.ts +35 -0
- package/dist/components/ic/DatePickerInputController.svelte +364 -0
- package/dist/components/ic/DatePickerInputController.svelte.d.ts +28 -0
- package/dist/components/ic/DropdownInputController.svelte +226 -0
- package/dist/components/ic/DropdownInputController.svelte.d.ts +43 -0
- package/dist/components/ic/FixedPositionInputController.svelte +353 -0
- package/dist/components/ic/FixedPositionInputController.svelte.d.ts +23 -0
- package/dist/components/ic/FontInputController.svelte +2062 -0
- package/dist/components/ic/FontInputController.svelte.d.ts +47 -0
- package/dist/components/ic/InputControllerHeader.svelte +147 -0
- package/dist/components/ic/InputControllerHeader.svelte.d.ts +23 -0
- package/dist/components/ic/NumberControls.svelte +299 -0
- package/dist/components/ic/NumberControls.svelte.d.ts +30 -0
- package/dist/components/ic/NumberInputController.svelte +397 -0
- package/dist/components/ic/NumberInputController.svelte.d.ts +30 -0
- package/dist/components/ic/PositionInputController.svelte +252 -0
- package/dist/components/ic/PositionInputController.svelte.d.ts +37 -0
- package/dist/components/ic/RadioGroupInputController.svelte +162 -0
- package/dist/components/ic/RadioGroupInputController.svelte.d.ts +38 -0
- package/dist/components/ic/RangeCalendarInputController.svelte +262 -0
- package/dist/components/ic/RangeCalendarInputController.svelte.d.ts +32 -0
- package/dist/components/ic/RoundedCornersInputController.svelte +469 -0
- package/dist/components/ic/RoundedCornersInputController.svelte.d.ts +37 -0
- package/dist/components/ic/SliderInputController.svelte +411 -0
- package/dist/components/ic/SliderInputController.svelte.d.ts +46 -0
- package/dist/components/ic/SliderRangeInputController.svelte +155 -0
- package/dist/components/ic/SliderRangeInputController.svelte.d.ts +33 -0
- package/dist/components/ic/SwitchInputController.svelte +107 -0
- package/dist/components/ic/SwitchInputController.svelte.d.ts +24 -0
- package/dist/components/ic/TagsInputController.svelte +125 -0
- package/dist/components/ic/TagsInputController.svelte.d.ts +27 -0
- package/dist/components/ic/TextAreaInputController.svelte +1804 -0
- package/dist/components/ic/TextAreaInputController.svelte.d.ts +13 -0
- package/dist/components/ic/TextAreaPreviewInputController.svelte +476 -0
- package/dist/components/ic/TextAreaPreviewInputController.svelte.d.ts +28 -0
- package/dist/components/ic/TextInputController.svelte +70 -0
- package/dist/components/ic/TextInputController.svelte.d.ts +30 -0
- package/dist/components/ic/TimeInputController.svelte +434 -0
- package/dist/components/ic/TimeInputController.svelte.d.ts +25 -0
- package/dist/components/ic/VectorInputController.svelte +1826 -0
- package/dist/components/ic/VectorInputController.svelte.d.ts +47 -0
- package/dist/components/ic/index.d.ts +39 -0
- package/dist/components/ic/index.js +24 -0
- package/dist/components/ic/shared/CategoryTabs.svelte +40 -0
- package/dist/components/ic/shared/CategoryTabs.svelte.d.ts +25 -0
- package/dist/components/ic/shared/ItemGrid.svelte +181 -0
- package/dist/components/ic/shared/ItemGrid.svelte.d.ts +35 -0
- package/dist/components/ic/shared/RecentItemsSection.svelte +260 -0
- package/dist/components/ic/shared/RecentItemsSection.svelte.d.ts +35 -0
- package/dist/components/ic/shared/index.d.ts +8 -0
- package/dist/components/ic/shared/index.js +9 -0
- package/dist/components/icons/addnew.svelte +21 -0
- package/dist/components/icons/addnew.svelte.d.ts +20 -0
- package/dist/components/icons/drag.svelte +29 -0
- package/dist/components/icons/drag.svelte.d.ts +20 -0
- package/dist/components/icons/drag.svg +1 -0
- package/dist/components/icons/locked.svelte +4 -0
- package/dist/components/icons/locked.svelte.d.ts +26 -0
- package/dist/components/icons/locked.svg +4 -0
- package/dist/components/icons/ok.svelte +20 -0
- package/dist/components/icons/ok.svelte.d.ts +20 -0
- package/dist/components/icons/ok.svg +11 -0
- package/dist/components/icons/unlocked.svelte +4 -0
- package/dist/components/icons/unlocked.svelte.d.ts +26 -0
- package/dist/components/icons/unlocked.svg +4 -0
- package/dist/components/icons/x.svelte +14 -0
- package/dist/components/icons/x.svelte.d.ts +20 -0
- package/dist/components/icons/x.svg +5 -0
- package/dist/components/ui/arrows/OpacitySliderArrows.svelte +43 -0
- package/dist/components/ui/arrows/OpacitySliderArrows.svelte.d.ts +23 -0
- package/dist/components/ui/checkerboard-background/CheckerboardBackground.svelte +15 -0
- package/dist/components/ui/checkerboard-background/CheckerboardBackground.svelte.d.ts +20 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +19 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +8 -0
- package/dist/stores/userSession.d.ts +7 -0
- package/dist/stores/userSession.js +28 -0
- package/dist/utils/FontManager.d.ts +276 -0
- package/dist/utils/FontManager.js +1283 -0
- package/dist/utils/MediaManager.d.ts +35 -0
- package/dist/utils/MediaManager.js +59 -0
- package/dist/utils/VectorManager.d.ts +190 -0
- package/dist/utils/VectorManager.js +754 -0
- package/dist/utils/colorAutocomplete.d.ts +10 -0
- package/dist/utils/colorAutocomplete.js +28 -0
- package/dist/utils/colorNames.d.ts +3 -0
- package/dist/utils/colorNames.js +152 -0
- package/dist/utils/colorTransparency.d.ts +52 -0
- package/dist/utils/colorTransparency.js +114 -0
- package/dist/utils/fontUtils.d.ts +34 -0
- package/dist/utils/fontUtils.js +132 -0
- package/dist/utils/passiveEvents.js +20 -0
- package/dist/utils/recentColors.d.ts +10 -0
- package/dist/utils/recentColors.js +63 -0
- package/dist/utils/skipFonts.d.ts +3 -0
- package/dist/utils/skipFonts.js +49 -0
- package/dist/utils/vectorUtils.d.ts +34 -0
- package/dist/utils/vectorUtils.js +71 -0
- package/dist/utils.d.ts +11 -0
- package/dist/utils.js +38 -0
- package/package.json +134 -74
- package/.babelrc +0 -10
- package/.bowerrc +0 -4
- package/.nvmrc +0 -1
- package/Gruntfile.coffee +0 -69
- package/bower.json +0 -28
- package/css/browser/entries/activateable.styl +0 -29
- package/css/browser/entries/audio.styl +0 -39
- package/css/browser/entries/folder.styl +0 -79
- package/css/browser/entries/image.styl +0 -26
- package/css/browser/entries/index.styl +0 -66
- package/css/browser/entries/rss.styl +0 -11
- package/css/browser/entries/video.styl +0 -11
- package/css/browser/index.styl +0 -86
- package/css/browser/intent/index.styl +0 -61
- package/css/browser/nav/index.styl +0 -73
- package/css/browser/social-browser.styl +0 -23
- package/css/controls/clock.styl +0 -110
- package/css/controls/config.styl +0 -6
- package/css/controls/control-audio-details.styl +0 -63
- package/css/controls/control-audio.styl +0 -28
- package/css/controls/control-browser.styl +0 -48
- package/css/controls/control-color.styl +0 -423
- package/css/controls/control-date_time.styl +0 -191
- package/css/controls/control-dropdown.styl +0 -79
- package/css/controls/control-font.styl +0 -147
- package/css/controls/control-image-details.styl +0 -35
- package/css/controls/control-image.styl +0 -43
- package/css/controls/control-order-picker.styl +0 -29
- package/css/controls/control-position.styl +0 -390
- package/css/controls/control-range.styl +0 -68
- package/css/controls/control-scale.styl +0 -30
- package/css/controls/control-slider.styl +0 -38
- package/css/controls/control-spinner.styl +0 -39
- package/css/controls/control-textarea.styl +0 -52
- package/css/controls/control-toggle.styl +0 -34
- package/css/controls/control-url.styl +0 -32
- package/css/controls/control-video-details.styl +0 -85
- package/css/controls/control-video.styl +0 -43
- package/css/controls/general.styl +0 -98
- package/css/controls/index.styl +0 -25
- package/css/editor/colors.styl +0 -7
- package/css/editor/content/directinput.styl +0 -47
- package/css/editor/content/index.styl +0 -111
- package/css/editor/content/message.styl +0 -53
- package/css/editor/content/tabs.styl +0 -75
- package/css/editor/copied-from-old-style.styl +0 -197
- package/css/editor/details/bulk-editor.styl +0 -104
- package/css/editor/details/index.styl +0 -21
- package/css/editor/details/item.styl +0 -153
- package/css/editor/embed.styl +0 -106
- package/css/editor/footer.styl +0 -180
- package/css/editor/full-editor.styl +0 -16
- package/css/editor/index.styl +0 -124
- package/css/editor/login-popup.styl +0 -13
- package/css/editor/preview.styl +0 -257
- package/css/editor/skin/editor/index.styl +0 -54
- package/css/editor/skin/editor/tabs.styl +0 -111
- package/css/editor/skin/index.styl +0 -138
- package/css/editor/skin/popup.styl +0 -152
- package/css/editor/skin/presets/dropdown.styl +0 -61
- package/css/editor/skin/presets/index.styl +0 -86
- package/css/editor/skin/presets/list.styl +0 -244
- package/css/editor/tooltips.styl +0 -85
- package/css/fonts.styl +0 -47
- package/css/icons/BlogvioEditor.dev.svg +0 -112
- package/css/icons.styl +0 -16
- package/css/img/arrow-right-rounded-v-small.png +0 -0
- package/css/img/browser-folder-separator.png +0 -0
- package/css/img/calendar-input-icons.png +0 -0
- package/css/img/check.png +0 -0
- package/css/img/close.png +0 -0
- package/css/img/composition/edit-icon.png +0 -0
- package/css/img/content/audio-icon.png +0 -0
- package/css/img/content/folder-icon.png +0 -0
- package/css/img/content/messages/error-background.png +0 -0
- package/css/img/content/messages/error-icon.png +0 -0
- package/css/img/content/messages/notification-background.png +0 -0
- package/css/img/content/messages/notification-icon.png +0 -0
- package/css/img/content/no-files-audio.png +0 -0
- package/css/img/content/no-files-image.png +0 -0
- package/css/img/content/no-files-video.png +0 -0
- package/css/img/content/no-files.png +0 -0
- package/css/img/content/rss-icon.png +0 -0
- package/css/img/content/selected.png +0 -0
- package/css/img/content/video-icon.png +0 -0
- package/css/img/content/with-control-text.png +0 -0
- package/css/img/control-dropdown-arrow.png +0 -0
- package/css/img/control-image-valid.png +0 -0
- package/css/img/control-position-icon.png +0 -0
- package/css/img/control-scale-lock-icon.png +0 -0
- package/css/img/control-slider-arrows-hover.png +0 -0
- package/css/img/control-slider-arrows.png +0 -0
- package/css/img/control-spinner-buttons-hover.png +0 -0
- package/css/img/control-spinner-buttons.png +0 -0
- package/css/img/control-textarea-bold-active.png +0 -0
- package/css/img/control-textarea-bold.png +0 -0
- package/css/img/control-textarea-italic-active.png +0 -0
- package/css/img/control-textarea-italic.png +0 -0
- package/css/img/control-textarea-underline-active.png +0 -0
- package/css/img/control-textarea-underline.png +0 -0
- package/css/img/custom-checkbox-sprite.png +0 -0
- package/css/img/details/item-collapse-icon.png +0 -0
- package/css/img/details/item-expand-icon.png +0 -0
- package/css/img/details/item-sort-icon.png +0 -0
- package/css/img/done-arrow.png +0 -0
- package/css/img/fb-logo-icon-w.png +0 -0
- package/css/img/grid-icons.png +0 -0
- package/css/img/icon-reference.png +0 -0
- package/css/img/indicator.png +0 -0
- package/css/img/loader.gif +0 -0
- package/css/img/loaders/audio-white.png +0 -0
- package/css/img/loaders/audio.png +0 -0
- package/css/img/loaders/photo-white.png +0 -0
- package/css/img/loaders/photo.png +0 -0
- package/css/img/loaders/util-white.png +0 -0
- package/css/img/loaders/util.png +0 -0
- package/css/img/loaders/video-white.png +0 -0
- package/css/img/loaders/video.png +0 -0
- package/css/img/maximize.png +0 -0
- package/css/img/minimize.png +0 -0
- package/css/img/next-btn.png +0 -0
- package/css/img/search-icon-sprite.png +0 -0
- package/css/img/services/blogvio.png +0 -0
- package/css/img/services/drive.png +0 -0
- package/css/img/services/dropbox-login.png +0 -0
- package/css/img/services/dropbox.png +0 -0
- package/css/img/services/facebook-login.png +0 -0
- package/css/img/services/facebook.png +0 -0
- package/css/img/services/flickr-login.png +0 -0
- package/css/img/services/flickr.png +0 -0
- package/css/img/services/instagram-login.png +0 -0
- package/css/img/services/instagram.png +0 -0
- package/css/img/services/text.png +0 -0
- package/css/img/services/uploads.png +0 -0
- package/css/img/services/uploads.svg +0 -689
- package/css/img/services/webaddress-bw.png +0 -0
- package/css/img/services/webaddress.png +0 -0
- package/css/img/services/webadress.png +0 -0
- package/css/img/services/websearch.png +0 -0
- package/css/img/services/wix.png +0 -0
- package/css/img/skin/item-active-indicator.png +0 -0
- package/css/img/skin/item-hover-icon.png +0 -0
- package/css/img/skin/overwrite.png +0 -0
- package/css/img/skin/popup-name-icon.png +0 -0
- package/css/img/skin/popup-save.png +0 -0
- package/css/img/skin/preset.png +0 -0
- package/css/img/skin/presets-arrow-purple.png +0 -0
- package/css/img/tw-icon-w.png +0 -0
- package/css/img/warning.png +0 -0
- package/css/img/white-rounded-check.png +0 -0
- package/css/index.styl +0 -9
- package/css/mixins.styl +0 -62
- package/css/scrollable.styl +0 -101
- package/karma.conf.js +0 -73
- package/lib/control.js +0 -1
- package/lib/controls.js +0 -1
- package/lib/core.editor.js +0 -4
- package/lib/core.js +0 -1
- package/lib/core.vendor.js +0 -3
- package/lib/dev/control.js +0 -845
- package/lib/dev/controls.js +0 -7774
- package/lib/dev/core.editor.js +0 -6439
- package/lib/dev/core.js +0 -1486
- package/lib/dev/core.vendor.js +0 -10708
- package/lib/dev/editor.css +0 -166
- package/lib/dev/editor.js +0 -29710
- package/lib/dev/editor.vendor.js +0 -15119
- package/lib/dev/jquery.nanoscroller.js.map +0 -10
- package/lib/editor.css +0 -167
- package/lib/editor.js +0 -49
- package/lib/editor.vendor.js +0 -11
- package/src/api/index.coffee +0 -23
- package/src/api/mixin.coffee +0 -89
- package/src/browser/controller/entry/audio.coffee +0 -38
- package/src/browser/controller/entry/button.coffee +0 -16
- package/src/browser/controller/entry/file.coffee +0 -35
- package/src/browser/controller/entry/folder.coffee +0 -14
- package/src/browser/controller/entry/image.coffee +0 -24
- package/src/browser/controller/entry/index.coffee +0 -18
- package/src/browser/controller/entry/rss.coffee +0 -5
- package/src/browser/controller/entry/video.coffee +0 -4
- package/src/browser/controller/index.coffee +0 -289
- package/src/browser/controller/instagram.coffee +0 -129
- package/src/browser/controller/navbar.coffee +0 -48
- package/src/browser/controller/social.coffee +0 -213
- package/src/browser/model/entry.coffee +0 -18
- package/src/browser/model/index.coffee +0 -34
- package/src/core/controller/font-loader.coffee +0 -65
- package/src/core/controller/lazyload.coffee +0 -14
- package/src/core/controller/overlay.coffee +0 -146
- package/src/core/controller/popup.coffee +0 -36
- package/src/core/controller/scrollable.coffee +0 -68
- package/src/core/controls/audio.coffee +0 -56
- package/src/core/controls/browser.coffee +0 -271
- package/src/core/controls/color.coffee +0 -121
- package/src/core/controls/date-time/calendar/index.coffee +0 -99
- package/src/core/controls/date-time/calendar/months.coffee +0 -52
- package/src/core/controls/date-time/calendar/years.coffee +0 -58
- package/src/core/controls/date-time/clock.coffee +0 -62
- package/src/core/controls/date-time/helper.coffee +0 -31
- package/src/core/controls/date-time/index.coffee +0 -62
- package/src/core/controls/date-time/popup.coffee +0 -56
- package/src/core/controls/details/audio.coffee +0 -33
- package/src/core/controls/details/image.coffee +0 -19
- package/src/core/controls/details/video.coffee +0 -14
- package/src/core/controls/dropdown.coffee +0 -100
- package/src/core/controls/font/font-item.coffee +0 -22
- package/src/core/controls/font/font-list.coffee +0 -108
- package/src/core/controls/font/fonts-popup.coffee +0 -91
- package/src/core/controls/font/index.coffee +0 -66
- package/src/core/controls/image.coffee +0 -37
- package/src/core/controls/index.coffee +0 -88
- package/src/core/controls/orderPicker.coffee +0 -93
- package/src/core/controls/position/grid-panel.coffee +0 -75
- package/src/core/controls/position/index.coffee +0 -98
- package/src/core/controls/position/panel.coffee +0 -68
- package/src/core/controls/position/pixel-panel.coffee +0 -54
- package/src/core/controls/position/popup.coffee +0 -63
- package/src/core/controls/position/reference-panel.coffee +0 -26
- package/src/core/controls/range.coffee +0 -191
- package/src/core/controls/rss.coffee +0 -16
- package/src/core/controls/scale.coffee +0 -78
- package/src/core/controls/slider.coffee +0 -122
- package/src/core/controls/stepper.coffee +0 -97
- package/src/core/controls/text.coffee +0 -54
- package/src/core/controls/textarea.coffee +0 -78
- package/src/core/controls/toggle.coffee +0 -29
- package/src/core/controls/url.coffee +0 -88
- package/src/core/controls/video.coffee +0 -9
- package/src/core/embed/api.js +0 -178
- package/src/core/embed/controller.js +0 -331
- package/src/core/models/composition.coffee +0 -178
- package/src/core/models/content.coffee +0 -79
- package/src/core/models/skin.coffee +0 -91
- package/src/core/models/user.coffee +0 -6
- package/src/core/models/widget.coffee +0 -88
- package/src/core/router/index.coffee +0 -44
- package/src/core/services/container/index.js +0 -18
- package/src/core/services/container/instance.js +0 -3
- package/src/core/services/content.js +0 -80
- package/src/core/services/instagram.coffee +0 -47
- package/src/core/services/mixin/events.js +0 -17
- package/src/core/services/service.js +0 -1
- package/src/core/services/sounds.js +0 -81
- package/src/core/services/uploads.js +0 -299
- package/src/core/utils/popup.coffee +0 -67
- package/src/core/utils/should-show.coffee +0 -12
- package/src/core/views/audio-details.pug +0 -9
- package/src/core/views/audio.pug +0 -7
- package/src/core/views/browser.pug +0 -4
- package/src/core/views/color.pug +0 -8
- package/src/core/views/date-time/calendar.pug +0 -3
- package/src/core/views/date-time/clock.pug +0 -12
- package/src/core/views/date-time/grid.pug +0 -6
- package/src/core/views/date-time/input.pug +0 -4
- package/src/core/views/date-time/table.pug +0 -6
- package/src/core/views/dropdown.pug +0 -9
- package/src/core/views/font/font-item.pug +0 -3
- package/src/core/views/font/fonts-list.pug +0 -7
- package/src/core/views/font/fonts-popup.pug +0 -5
- package/src/core/views/font/index.pug +0 -7
- package/src/core/views/image-details.pug +0 -6
- package/src/core/views/image.pug +0 -5
- package/src/core/views/input-layout.pug +0 -15
- package/src/core/views/order-items.pug +0 -7
- package/src/core/views/order-picker.pug +0 -7
- package/src/core/views/pixelpanel.pug +0 -69
- package/src/core/views/position.pug +0 -10
- package/src/core/views/range.pug +0 -9
- package/src/core/views/scale.pug +0 -7
- package/src/core/views/slider.pug +0 -6
- package/src/core/views/stepper.pug +0 -6
- package/src/core/views/text.pug +0 -4
- package/src/core/views/textarea.pug +0 -8
- package/src/core/views/toggle.pug +0 -5
- package/src/core/views/video-details.pug +0 -7
- package/src/core/views/video.pug +0 -4
- package/src/core/widget/index.coffee +0 -66
- package/src/editor/content/browser/dropzone.html +0 -268
- package/src/editor/content/browser/file.html +0 -124
- package/src/editor/content/browser/scroll-pane.html +0 -32
- package/src/editor/content/browser/service.html +0 -53
- package/src/editor/content/browser/upload.coffee +0 -44
- package/src/editor/content/browser/upload.html +0 -439
- package/src/editor/content/index.coffee +0 -294
- package/src/editor/content/input/field.coffee +0 -45
- package/src/editor/content/input/index.coffee +0 -115
- package/src/editor/content/service/base/photoitem.coffee +0 -49
- package/src/editor/content/service/cache.coffee +0 -20
- package/src/editor/content/service/default.coffee +0 -59
- package/src/editor/content/service/dropbox.coffee +0 -71
- package/src/editor/content/service/facebook.coffee +0 -143
- package/src/editor/content/service/flickr.coffee +0 -85
- package/src/editor/content/service/google-drive-sdk.coffee +0 -18
- package/src/editor/content/service/google-drive.coffee +0 -144
- package/src/editor/content/service/instagram-social.coffee +0 -84
- package/src/editor/content/service/instagram.coffee +0 -168
- package/src/editor/content/service/share-popup.coffee +0 -15
- package/src/editor/content/service/soundcloud.coffee +0 -163
- package/src/editor/content/tab.coffee +0 -27
- package/src/editor/details/bulk-editor/attributes-panel.coffee +0 -11
- package/src/editor/details/bulk-editor/index.coffee +0 -86
- package/src/editor/details/index.coffee +0 -156
- package/src/editor/details/item/attributes.coffee +0 -45
- package/src/editor/details/item/index.coffee +0 -82
- package/src/editor/editor.coffee +0 -274
- package/src/editor/footer/breadcrumb.coffee +0 -17
- package/src/editor/footer/index.coffee +0 -36
- package/src/editor/index.coffee +0 -79
- package/src/editor/listeners/embed-listener.coffee +0 -143
- package/src/editor/login-popup.coffee +0 -29
- package/src/editor/notification-controller.coffee +0 -35
- package/src/editor/popup.coffee +0 -68
- package/src/editor/remote-preview.coffee +0 -68
- package/src/editor/save/index.coffee +0 -62
- package/src/editor/skin/delete.coffee +0 -33
- package/src/editor/skin/editor/index.coffee +0 -188
- package/src/editor/skin/editor/panel.coffee +0 -37
- package/src/editor/skin/index.coffee +0 -126
- package/src/editor/skin/save.coffee +0 -58
- package/src/editor/skin/selector/index.coffee +0 -59
- package/src/editor/skin/selector/item.coffee +0 -123
- package/src/editor/skin/selector/list.coffee +0 -97
- package/src/editor/views/embed.pug +0 -10
- package/src/editor/views/loginbutton.pug +0 -8
- package/src/editor/views/popup.pug +0 -17
- package/src/editor/views/presets.pug +0 -8
- package/src/editor/views/presetslist-header.pug +0 -8
- package/src/editor/views/skinitem.pug +0 -13
- package/src/modules/control.js +0 -3
- package/src/modules/controls.js +0 -29
- package/src/modules/core.js +0 -19
- package/src/modules/editor.js +0 -80
- package/test/config.js +0 -3
- package/test/core/models/skin-spec.js +0 -209
- package/webpack.config.js +0 -46
- package/webpack.config.prod.js +0 -9
- /package/{src/core/views/date-time/popup.pug → dist/utils/passiveEvents.d.ts} +0 -0
|
@@ -0,0 +1,1804 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
TextAreaInputController
|
|
3
|
+
|
|
4
|
+
A rich text editor component with advanced formatting capabilities:
|
|
5
|
+
- Text styling (bold, italic, underline)
|
|
6
|
+
- Headings and lists
|
|
7
|
+
- Color picker integration
|
|
8
|
+
- Link insertion
|
|
9
|
+
- Dynamic height adjustment
|
|
10
|
+
|
|
11
|
+
Key Features:
|
|
12
|
+
- Maintains editor state across format changes
|
|
13
|
+
- Handles complex selection scenarios
|
|
14
|
+
- Manages toolbar state based on text formatting
|
|
15
|
+
- Implements smooth height transitions
|
|
16
|
+
- Preserves cursor position during operations
|
|
17
|
+
|
|
18
|
+
Edge Cases:
|
|
19
|
+
- Handles empty selections gracefully
|
|
20
|
+
- Manages focus state during dropdown interactions
|
|
21
|
+
- Prevents recursive updates during state changes
|
|
22
|
+
- Maintains formatting consistency across operations
|
|
23
|
+
-->
|
|
24
|
+
|
|
25
|
+
<script lang="ts">
|
|
26
|
+
// Svelte imports
|
|
27
|
+
import { onMount, onDestroy } from 'svelte';
|
|
28
|
+
import { writable } from 'svelte/store';
|
|
29
|
+
import type { ComponentEvents } from 'svelte';
|
|
30
|
+
|
|
31
|
+
// Component config
|
|
32
|
+
import type { InputControllerConfig } from './index';
|
|
33
|
+
|
|
34
|
+
// Import Shadcn UI components
|
|
35
|
+
import { Button, ToggleGroup, Popover, Command } from '@widgetic/design-system/components';
|
|
36
|
+
|
|
37
|
+
// Import Lucide Icons
|
|
38
|
+
import {
|
|
39
|
+
IconBrush,
|
|
40
|
+
IconBold,
|
|
41
|
+
IconItalic,
|
|
42
|
+
IconUnderline,
|
|
43
|
+
IconLink,
|
|
44
|
+
ChevronDown,
|
|
45
|
+
X,
|
|
46
|
+
IconPilcrow,
|
|
47
|
+
IconList,
|
|
48
|
+
IconListNumbers,
|
|
49
|
+
IconListCheck
|
|
50
|
+
} from '@widgetic/design-system/icons';
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
// Import Shad Editor
|
|
54
|
+
import { ShadEditor, type TipTapEditor, type TipTapEditorLevel } from '@widgetic/design-system/shad-editor';
|
|
55
|
+
|
|
56
|
+
// Add local ColorPickerInputController import
|
|
57
|
+
import ColorPickerInputController from './ColorPickerInputController.svelte';
|
|
58
|
+
|
|
59
|
+
// Add this constant for the decoration type
|
|
60
|
+
const TRANSITION_DURATION = 250;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Component props
|
|
64
|
+
*/
|
|
65
|
+
let {
|
|
66
|
+
class: className = '',
|
|
67
|
+
label,
|
|
68
|
+
value = $bindable(''),
|
|
69
|
+
onChange,
|
|
70
|
+
config = {},
|
|
71
|
+
description = undefined,
|
|
72
|
+
onFocus = () => {}
|
|
73
|
+
} = $props<{
|
|
74
|
+
class: string;
|
|
75
|
+
label: string;
|
|
76
|
+
value: string;
|
|
77
|
+
onChange: (newValue: string) => void;
|
|
78
|
+
config?: InputControllerConfig;
|
|
79
|
+
description?: string;
|
|
80
|
+
onFocus?: () => void;
|
|
81
|
+
}>();
|
|
82
|
+
|
|
83
|
+
// Use config values with fallbacks
|
|
84
|
+
const textColor = config.textColor || '#1a1a1a';
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* State Variables
|
|
88
|
+
*/
|
|
89
|
+
let isInitialMount = $state(false); // Tracks initial component mounting to prevent unnecessary updates
|
|
90
|
+
let editorInstance: TipTapEditor | null = null; // Reference to TipTap editor instance
|
|
91
|
+
let textStyleDropdownOpen = $state(false); // Controls visibility of text style dropdown
|
|
92
|
+
let listStyleDropdownOpen = $state(false);
|
|
93
|
+
/**
|
|
94
|
+
* Format Tracking
|
|
95
|
+
* Maintains active format states for toolbar UI:
|
|
96
|
+
* - Prevents invalid format combinations
|
|
97
|
+
* - Updates reactively with selection changes
|
|
98
|
+
* - Syncs with editor commands
|
|
99
|
+
*/
|
|
100
|
+
// Add type definition for formats
|
|
101
|
+
type FormatType = 'bold' | 'italic' | 'underline' | 'link';
|
|
102
|
+
type ActiveFormats = Record<FormatType, boolean>;
|
|
103
|
+
type FormatMapping = Record<FormatType, { value: string; icon: any }>;
|
|
104
|
+
|
|
105
|
+
let activeFormats = $state<ActiveFormats>({
|
|
106
|
+
bold: false,
|
|
107
|
+
italic: false,
|
|
108
|
+
underline: false,
|
|
109
|
+
link: false
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const formatMapping: FormatMapping = {
|
|
113
|
+
bold: { value: 'b', icon: IconBold },
|
|
114
|
+
italic: { value: 'i', icon: IconItalic },
|
|
115
|
+
underline: { value: 'u', icon: IconUnderline },
|
|
116
|
+
link: { value: 'link', icon: IconLink }
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
let currentTextStyle = $state('text'); // Track current text style
|
|
120
|
+
|
|
121
|
+
let showColorPicker = $state(false);
|
|
122
|
+
let currentColor = $state(textColor);
|
|
123
|
+
let selectedTextRange: { from: number, to: number } | null = $state(null);
|
|
124
|
+
|
|
125
|
+
const textOptions = [
|
|
126
|
+
{ value: 'text', label: 'Paragraph', icon: IconPilcrow },
|
|
127
|
+
{ value: 'h1', label: 'H1' },
|
|
128
|
+
{ value: 'h2', label: 'H2' },
|
|
129
|
+
{ value: 'h3', label: 'H3' }
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
const listOptions = [
|
|
133
|
+
{ value: 'bulletList', label: 'Bullet List', icon: IconList },
|
|
134
|
+
{ value: 'orderedList', label: 'Numbered List', icon: IconListNumbers },
|
|
135
|
+
{ value: 'checkList', label: 'Check List', icon: IconListCheck }
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
const content = writable(value);
|
|
139
|
+
|
|
140
|
+
// Create a store for managing dropdowns
|
|
141
|
+
const openDropdowns = writable<Set<string>>(new Set());
|
|
142
|
+
|
|
143
|
+
function toggleDropdown(id: string) {
|
|
144
|
+
openDropdowns.update(set => {
|
|
145
|
+
if (set.has(id)) {
|
|
146
|
+
set.delete(id);
|
|
147
|
+
} else {
|
|
148
|
+
set.add(id);
|
|
149
|
+
}
|
|
150
|
+
return set;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Add these variables to track focus state
|
|
155
|
+
let editorHasFocus = $state(false);
|
|
156
|
+
let toolbarRef: HTMLDivElement;
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
// Update to use $derived for reactive statement
|
|
161
|
+
const editorStyle = $derived(`
|
|
162
|
+
--editor-min-height: auto;
|
|
163
|
+
--editor-max-height: none;
|
|
164
|
+
--transition-duration: 0ms;
|
|
165
|
+
--transition-timing: ease-out;
|
|
166
|
+
`);
|
|
167
|
+
|
|
168
|
+
// Add this with other state declarations
|
|
169
|
+
let editorClass = $derived(`border-none overflow-auto editor-height ${''}`);
|
|
170
|
+
|
|
171
|
+
// Add state to track if any dropdown is open
|
|
172
|
+
let isDropdownOpen = $state(false);
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Editor Mount Handler
|
|
176
|
+
*
|
|
177
|
+
* Initializes the editor instance and sets up event listeners:
|
|
178
|
+
* 1. Sets initial content if provided
|
|
179
|
+
* 2. Configures editor options (autofocus, editability)
|
|
180
|
+
* 3. Sets up selection tracking for format updates
|
|
181
|
+
* 4. Manages focus/blur behavior and height transitions
|
|
182
|
+
* 5. Handles initial mount state
|
|
183
|
+
*
|
|
184
|
+
* Edge Cases:
|
|
185
|
+
* - Prevents height changes when dropdowns are open
|
|
186
|
+
* - Manages focus state during toolbar interactions
|
|
187
|
+
* - Handles selection preservation during format changes
|
|
188
|
+
*/
|
|
189
|
+
// Add these new state variables after the other state declarations
|
|
190
|
+
let lastKnownContent = $state('');
|
|
191
|
+
let wasVisible = $state(true);
|
|
192
|
+
|
|
193
|
+
function handleEditorMount(e: any) {
|
|
194
|
+
editorInstance = e.detail.editor;
|
|
195
|
+
|
|
196
|
+
if (!editorInstance) {
|
|
197
|
+
console.error('Failed to initialize editor instance');
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Restore content if we were previously hidden
|
|
202
|
+
if (!wasVisible && lastKnownContent) {
|
|
203
|
+
try {
|
|
204
|
+
const coloredContent = `<span style="color: ${textColor}">${lastKnownContent}</span>`;
|
|
205
|
+
editorInstance.commands.setContent(coloredContent);
|
|
206
|
+
} catch (error) {
|
|
207
|
+
console.error('Error restoring content after visibility change:', error);
|
|
208
|
+
}
|
|
209
|
+
} else if (value) {
|
|
210
|
+
try {
|
|
211
|
+
const coloredContent = `<span style="color: ${textColor}">${value}</span>`;
|
|
212
|
+
editorInstance.commands.setContent(coloredContent);
|
|
213
|
+
|
|
214
|
+
// Set caret to start after setting content
|
|
215
|
+
editorInstance.commands.setTextSelection(0);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.error('Error setting initial content and color:', error);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Disable auto-focus behavior
|
|
222
|
+
editorInstance.setOptions({
|
|
223
|
+
autofocus: false,
|
|
224
|
+
editable: true
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Add selection change listener
|
|
228
|
+
editorInstance.on('selectionUpdate', () => {
|
|
229
|
+
handleSelectionChange();
|
|
230
|
+
updateActiveFormats();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Track focus state and handle height changes
|
|
234
|
+
editorInstance.on('focus', () => {
|
|
235
|
+
onFocus();
|
|
236
|
+
editorHasFocus = true;
|
|
237
|
+
|
|
238
|
+
// When focusing, ensure caret is at start if this is initial mount
|
|
239
|
+
if (isInitialMount && editorInstance) {
|
|
240
|
+
editorInstance.commands.setTextSelection(0);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Update the blur handler inside handleEditorMount
|
|
245
|
+
editorInstance.on('blur', ({ event }) => {
|
|
246
|
+
const relatedTarget = (event as FocusEvent).relatedTarget as Node | null;
|
|
247
|
+
handleEditorBlur(relatedTarget);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Set initial mount flag
|
|
251
|
+
isInitialMount = true;
|
|
252
|
+
setTimeout(() => {
|
|
253
|
+
isInitialMount = false;
|
|
254
|
+
// Check overflow only once after initial mount
|
|
255
|
+
setTimeout(() => {
|
|
256
|
+
checkContentOverflow();
|
|
257
|
+
}, 300); // Give it a bit more time to ensure content is fully rendered
|
|
258
|
+
}, 100);
|
|
259
|
+
|
|
260
|
+
updateActiveFormats();
|
|
261
|
+
|
|
262
|
+
// Helper function to check if content overflows viewport - only called once on mount
|
|
263
|
+
function checkContentOverflow() {
|
|
264
|
+
if (!editorInstance) return;
|
|
265
|
+
try {
|
|
266
|
+
const editorDom = editorInstance.view.dom;
|
|
267
|
+
const contentArea = editorDom.closest('.editor-content-area') as HTMLElement;
|
|
268
|
+
|
|
269
|
+
if (!contentArea) {
|
|
270
|
+
console.error('Could not find .editor-content-area parent element');
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const proseMirror = contentArea.querySelector('.ProseMirror') as HTMLElement;
|
|
275
|
+
if (!proseMirror) {
|
|
276
|
+
console.error('Could not find .ProseMirror element inside content area');
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Get the actual content height (including padding)
|
|
281
|
+
const contentHeight = proseMirror.scrollHeight;
|
|
282
|
+
// Get the available container height
|
|
283
|
+
const containerHeight = contentArea.clientHeight;
|
|
284
|
+
const isOverflowing = contentHeight > containerHeight;
|
|
285
|
+
|
|
286
|
+
console.log('Initial overflow check:', {
|
|
287
|
+
contentHeight,
|
|
288
|
+
containerHeight,
|
|
289
|
+
difference: contentHeight - containerHeight,
|
|
290
|
+
isOverflowing
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
if (isOverflowing) {
|
|
294
|
+
proseMirror.style.backgroundColor = 'rgba(255, 0, 0, 0.1)';
|
|
295
|
+
}
|
|
296
|
+
} catch (error) {
|
|
297
|
+
console.error('Error checking content overflow:', error);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Helper function to force caret and scroll to beginning
|
|
302
|
+
function forceCaretToStart() {
|
|
303
|
+
if (!editorInstance) return;
|
|
304
|
+
try {
|
|
305
|
+
// Only force caret to start if this is the initial mount
|
|
306
|
+
if (isInitialMount) {
|
|
307
|
+
// Force the caret (text selection) to the beginning
|
|
308
|
+
editorInstance.chain().setTextSelection({ from: 0, to: 0 }).run();
|
|
309
|
+
}
|
|
310
|
+
} catch (error) {
|
|
311
|
+
console.error('Error forcing caret to beginning:', error);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Only call forceCaretToStart once during initial mount
|
|
316
|
+
if (isInitialMount) {
|
|
317
|
+
setTimeout(forceCaretToStart, 300);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function handleEditorUpdate(e: any) {
|
|
322
|
+
if (!editorInstance) return;
|
|
323
|
+
|
|
324
|
+
const newContent = e.detail.html;
|
|
325
|
+
if (newContent !== value) {
|
|
326
|
+
content.set(newContent);
|
|
327
|
+
onChange(newContent);
|
|
328
|
+
updateActiveFormats();
|
|
329
|
+
currentTextStyle = getCurrentTextStyle();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Format Management System
|
|
335
|
+
*
|
|
336
|
+
* Handles text formatting operations and state:
|
|
337
|
+
* - Updates toolbar UI based on current selection
|
|
338
|
+
* - Manages format toggles (bold, italic, etc.)
|
|
339
|
+
* - Handles text style changes (headings, lists)
|
|
340
|
+
* - Preserves selection during format operations
|
|
341
|
+
*
|
|
342
|
+
* Selection Handling:
|
|
343
|
+
* - Tracks active formats only when text is selected
|
|
344
|
+
* - Resets formats when no selection exists
|
|
345
|
+
* - Maintains format state during dropdown operations
|
|
346
|
+
*/
|
|
347
|
+
function updateActiveFormats() {
|
|
348
|
+
if (!editorInstance) return;
|
|
349
|
+
|
|
350
|
+
// Update activeFormats based on editor state
|
|
351
|
+
const newFormats = {
|
|
352
|
+
bold: editorInstance.isActive('bold'),
|
|
353
|
+
italic: editorInstance.isActive('italic'),
|
|
354
|
+
underline: editorInstance.isActive('underline'),
|
|
355
|
+
link: editorInstance.isActive('link')
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
// Only update if there's a change to prevent unnecessary rerenders
|
|
359
|
+
if (JSON.stringify(newFormats) !== JSON.stringify($state.snapshot(activeFormats))) {
|
|
360
|
+
activeFormats = newFormats;
|
|
361
|
+
console.log('Active formats updated:', $state.snapshot(activeFormats));
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function applyFormat(format: FormatType, isAdding: boolean = true) {
|
|
366
|
+
if (!editorInstance) return;
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
// Always focus first and start a chain
|
|
370
|
+
const chain = editorInstance.chain().focus() as any;
|
|
371
|
+
|
|
372
|
+
switch (format) {
|
|
373
|
+
case 'bold':
|
|
374
|
+
chain.toggleBold().run();
|
|
375
|
+
break;
|
|
376
|
+
case 'italic':
|
|
377
|
+
chain.toggleItalic().run();
|
|
378
|
+
break;
|
|
379
|
+
case 'underline':
|
|
380
|
+
chain.toggleUnderline().run();
|
|
381
|
+
break;
|
|
382
|
+
case 'link':
|
|
383
|
+
if (isAdding) {
|
|
384
|
+
const href = window.prompt('Enter URL');
|
|
385
|
+
if (href) {
|
|
386
|
+
chain.setLink({ href }).run();
|
|
387
|
+
}
|
|
388
|
+
} else {
|
|
389
|
+
chain.unsetLink().run();
|
|
390
|
+
}
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Update formats after applying changes
|
|
395
|
+
setTimeout(() => {
|
|
396
|
+
updateActiveFormats();
|
|
397
|
+
}, 0);
|
|
398
|
+
} catch (error) {
|
|
399
|
+
console.error('Error applying format:', format, error);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function handleToggleChange(newValues: string[] | undefined): void {
|
|
404
|
+
if (!editorInstance) {
|
|
405
|
+
console.warn('No editor instance available');
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (!newValues) {
|
|
410
|
+
console.warn('No new values provided');
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Use $state.snapshot() for logging
|
|
415
|
+
const currentValues = $state.snapshot(toggleValues);
|
|
416
|
+
console.log('Toggle change:', { newValues, currentValues });
|
|
417
|
+
|
|
418
|
+
// Find which format was toggled by comparing the arrays
|
|
419
|
+
const added = newValues.filter(v => !currentValues.includes(v));
|
|
420
|
+
const removed = currentValues.filter(v => !newValues.includes(v));
|
|
421
|
+
|
|
422
|
+
// Handle the change
|
|
423
|
+
if (added.length > 0) {
|
|
424
|
+
// Find the format type for the added value
|
|
425
|
+
const format = Object.entries(formatMapping).find(([_, m]) => m.value === added[0])?.[0] as FormatType;
|
|
426
|
+
if (format) {
|
|
427
|
+
console.log('Applying format:', format);
|
|
428
|
+
applyFormat(format, true);
|
|
429
|
+
}
|
|
430
|
+
} else if (removed.length > 0) {
|
|
431
|
+
// Find the format type for the removed value
|
|
432
|
+
const format = Object.entries(formatMapping).find(([_, m]) => m.value === removed[0])?.[0] as FormatType;
|
|
433
|
+
if (format) {
|
|
434
|
+
console.log('Removing format:', format);
|
|
435
|
+
applyFormat(format, false);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Update derived toggleValues with proper typing
|
|
441
|
+
const toggleValues = $derived(Object.entries(activeFormats)
|
|
442
|
+
.filter(([_, isActive]) => isActive)
|
|
443
|
+
.map(([format]) => formatMapping[format as FormatType].value)
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
// Restore handleTextStyleChange for other style changes
|
|
447
|
+
function handleTextStyleChange(value: string) {
|
|
448
|
+
if (!editorInstance) return;
|
|
449
|
+
|
|
450
|
+
try {
|
|
451
|
+
const { from, to } = editorInstance.state.selection;
|
|
452
|
+
const selectedText = editorInstance.state.doc.textBetween(from, to);
|
|
453
|
+
|
|
454
|
+
console.log('Changing style for text "%s" to:', (selectedText || 'No text selected'), value);
|
|
455
|
+
|
|
456
|
+
// Store the current scroll position and selection
|
|
457
|
+
const editorDom = editorInstance.view.dom;
|
|
458
|
+
const contentArea = editorDom.closest('.editor-content-area') as HTMLElement;
|
|
459
|
+
const scrollTop = contentArea ? contentArea.scrollTop : editorDom.scrollTop;
|
|
460
|
+
const selection = { from, to };
|
|
461
|
+
|
|
462
|
+
const chain = editorInstance.chain().focus() as any;
|
|
463
|
+
|
|
464
|
+
switch (value) {
|
|
465
|
+
case 'text':
|
|
466
|
+
chain.setNode('paragraph').run();
|
|
467
|
+
currentTextStyle = value;
|
|
468
|
+
break;
|
|
469
|
+
case 'h1':
|
|
470
|
+
case 'h2':
|
|
471
|
+
case 'h3':
|
|
472
|
+
const level = parseInt(value.charAt(1)) as TipTapEditorLevel;
|
|
473
|
+
chain.toggleHeading({ level }).run();
|
|
474
|
+
currentTextStyle = editorInstance.isActive('heading', { level }) ? value : 'text';
|
|
475
|
+
break;
|
|
476
|
+
case 'bulletList':
|
|
477
|
+
chain.toggleBulletList().run();
|
|
478
|
+
currentTextStyle = value;
|
|
479
|
+
break;
|
|
480
|
+
case 'orderedList':
|
|
481
|
+
chain.toggleOrderedList().run();
|
|
482
|
+
currentTextStyle = value;
|
|
483
|
+
break;
|
|
484
|
+
case 'checkList':
|
|
485
|
+
chain.toggleTaskList().run();
|
|
486
|
+
currentTextStyle = value;
|
|
487
|
+
break;
|
|
488
|
+
case 'blockquote':
|
|
489
|
+
chain.toggleBlockquote().run();
|
|
490
|
+
currentTextStyle = value;
|
|
491
|
+
break;
|
|
492
|
+
case 'code':
|
|
493
|
+
chain.toggleCode().run();
|
|
494
|
+
currentTextStyle = value;
|
|
495
|
+
break;
|
|
496
|
+
case 'strike':
|
|
497
|
+
chain.toggleStrike().run();
|
|
498
|
+
currentTextStyle = value;
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Restore the scroll position and selection after the style change
|
|
503
|
+
requestAnimationFrame(() => {
|
|
504
|
+
if (contentArea) {
|
|
505
|
+
contentArea.scrollTop = scrollTop;
|
|
506
|
+
} else if (editorDom) {
|
|
507
|
+
editorDom.scrollTop = scrollTop;
|
|
508
|
+
}
|
|
509
|
+
// Restore selection
|
|
510
|
+
editorInstance?.chain()
|
|
511
|
+
.setTextSelection(selection)
|
|
512
|
+
.run();
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
updateActiveFormats();
|
|
516
|
+
textStyleDropdownOpen = false;
|
|
517
|
+
} catch (error) {
|
|
518
|
+
console.error('Error changing text style:', error);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function getCurrentTextStyle() {
|
|
523
|
+
if (!editorInstance) return 'text';
|
|
524
|
+
|
|
525
|
+
// Check for headings first
|
|
526
|
+
if (editorInstance.isActive('heading', { level: 1 })) return 'h1';
|
|
527
|
+
if (editorInstance.isActive('heading', { level: 2 })) return 'h2';
|
|
528
|
+
if (editorInstance.isActive('heading', { level: 3 })) return 'h3';
|
|
529
|
+
|
|
530
|
+
// Check for lists and other block-level formats
|
|
531
|
+
if (editorInstance.isActive('bulletList')) return 'bulletList';
|
|
532
|
+
if (editorInstance.isActive('orderedList')) return 'orderedList';
|
|
533
|
+
if (editorInstance.isActive('checkList')) return 'checkList';
|
|
534
|
+
if (editorInstance.isActive('blockquote')) return 'blockquote';
|
|
535
|
+
if (editorInstance.isActive('code')) return 'code';
|
|
536
|
+
if (editorInstance.isActive('strike')) return 'strike';
|
|
537
|
+
|
|
538
|
+
// If no block-level format is active, return the current text style
|
|
539
|
+
const marks = editorInstance.state.selection.$head.marks();
|
|
540
|
+
if (marks.length > 0) {
|
|
541
|
+
// Keep the current text style if there are inline marks
|
|
542
|
+
return currentTextStyle;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return 'text';
|
|
546
|
+
}
|
|
547
|
+
// Function to store text selection
|
|
548
|
+
function storeSelection() {
|
|
549
|
+
if (!editorInstance) return null;
|
|
550
|
+
const { from, to } = editorInstance.state.selection;
|
|
551
|
+
if (from === to) return null;
|
|
552
|
+
return { from, to };
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Function to restore text selection
|
|
556
|
+
function restoreSelection() {
|
|
557
|
+
if (!editorInstance || !selectedTextRange) return;
|
|
558
|
+
editorInstance.chain()
|
|
559
|
+
.setTextSelection({ from: selectedTextRange.from, to: selectedTextRange.to })
|
|
560
|
+
.run();
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Helper function to convert RGB to Hex
|
|
564
|
+
function rgbToHex(r: number, g: number, b: number): string {
|
|
565
|
+
const toHex = (n: number) => {
|
|
566
|
+
const hex = Math.round(n).toString(16);
|
|
567
|
+
return hex.length === 1 ? '0' + hex : hex;
|
|
568
|
+
};
|
|
569
|
+
return '#' + toHex(r) + toHex(g) + toHex(b);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Update handleColorChange to use current selection if available
|
|
573
|
+
function handleColorChange(color: string) {
|
|
574
|
+
if (!editorInstance) return;
|
|
575
|
+
|
|
576
|
+
try {
|
|
577
|
+
// Get current selection
|
|
578
|
+
const { from, to } = editorInstance.state.selection;
|
|
579
|
+
const hasSelection = from !== to;
|
|
580
|
+
|
|
581
|
+
if (hasSelection) {
|
|
582
|
+
// Apply color to selection using the correct command
|
|
583
|
+
editorInstance.chain()
|
|
584
|
+
.setTextSelection({ from, to })
|
|
585
|
+
.setMark('textStyle', { color })
|
|
586
|
+
.run();
|
|
587
|
+
|
|
588
|
+
// Remove any highlight marks and restore selection visibility
|
|
589
|
+
editorInstance.chain()
|
|
590
|
+
.unsetMark('highlight')
|
|
591
|
+
.run();
|
|
592
|
+
|
|
593
|
+
// Remove hide-selection class
|
|
594
|
+
editorInstance.view.dom.classList.remove('hide-selection');
|
|
595
|
+
|
|
596
|
+
// Ensure editor has focus
|
|
597
|
+
editorInstance.commands.focus();
|
|
598
|
+
}
|
|
599
|
+
} catch (error) {
|
|
600
|
+
console.error('Error applying color:', error);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// $effect(() => {
|
|
605
|
+
// if (value !== $content && editorInstance) {
|
|
606
|
+
// console.log('External value changed:', value);
|
|
607
|
+
// content.set(value);
|
|
608
|
+
// editorInstance.commands.setContent(value);
|
|
609
|
+
// }
|
|
610
|
+
// });
|
|
611
|
+
|
|
612
|
+
// Add colorPickerOpen state
|
|
613
|
+
let colorPickerOpen = $state(false);
|
|
614
|
+
|
|
615
|
+
// Add function to handle color picker open/close
|
|
616
|
+
function handleColorPickerToggle() {
|
|
617
|
+
colorPickerOpen = !colorPickerOpen;
|
|
618
|
+
if (colorPickerOpen && selectedTextRange) {
|
|
619
|
+
restoreSelection();
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// Update getSelectedTextColor to ensure hex format with 100% opacity
|
|
624
|
+
function getSelectedTextColor(): string {
|
|
625
|
+
if (!editorInstance) return currentColor;
|
|
626
|
+
|
|
627
|
+
const { from, to } = editorInstance.state.selection;
|
|
628
|
+
if (from === to) return currentColor;
|
|
629
|
+
|
|
630
|
+
// Get the color mark from the selection
|
|
631
|
+
const marks = editorInstance.state.doc.nodeAt(from)?.marks;
|
|
632
|
+
const colorMark = marks?.find(mark => mark.type.name === 'textStyle' && mark.attrs.color);
|
|
633
|
+
|
|
634
|
+
let color = colorMark?.attrs.color || currentColor;
|
|
635
|
+
|
|
636
|
+
// Convert RGB to Hex if needed
|
|
637
|
+
if (color.startsWith('rgb')) {
|
|
638
|
+
const matches = color.match(/\d+/g);
|
|
639
|
+
if (matches && matches.length >= 3) {
|
|
640
|
+
color = rgbToHex(
|
|
641
|
+
parseInt(matches[0]),
|
|
642
|
+
parseInt(matches[1]),
|
|
643
|
+
parseInt(matches[2])
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// Ensure color is in hex format and has 100% opacity
|
|
649
|
+
if (color.length === 9) { // If it's a hex with alpha
|
|
650
|
+
color = color.substring(0, 7); // Remove alpha channel
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
return color;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Update handleSelectionChange to properly handle color conversion
|
|
657
|
+
function handleSelectionChange() {
|
|
658
|
+
if (!editorInstance) return;
|
|
659
|
+
|
|
660
|
+
const { from, to } = editorInstance.state.selection;
|
|
661
|
+
const hasSelection = from !== to;
|
|
662
|
+
|
|
663
|
+
if (hasSelection) {
|
|
664
|
+
selectedRange = { from, to };
|
|
665
|
+
|
|
666
|
+
// Get the selected text and check for color mark
|
|
667
|
+
const marks = editorInstance.state.doc.nodeAt(from)?.marks || [];
|
|
668
|
+
const colorMark = marks.find(mark =>
|
|
669
|
+
mark.type.name === 'textStyle' && mark.attrs.color
|
|
670
|
+
);
|
|
671
|
+
|
|
672
|
+
// Update currentColor if a color mark is found
|
|
673
|
+
if (colorMark?.attrs.color) {
|
|
674
|
+
let color = colorMark.attrs.color;
|
|
675
|
+
|
|
676
|
+
// Convert RGB to Hex if needed
|
|
677
|
+
if (color.startsWith('rgb')) {
|
|
678
|
+
const matches = color.match(/\d+/g);
|
|
679
|
+
if (matches && matches.length >= 3) {
|
|
680
|
+
color = rgbToHex(
|
|
681
|
+
parseInt(matches[0]),
|
|
682
|
+
parseInt(matches[1]),
|
|
683
|
+
parseInt(matches[2])
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// Remove alpha channel if present
|
|
689
|
+
if (color.length === 9) {
|
|
690
|
+
color = color.substring(0, 7);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
currentColor = color;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
console.log('Selection updated:', {
|
|
697
|
+
text: editorInstance.state.doc.textBetween(from, to),
|
|
698
|
+
range: { from, to },
|
|
699
|
+
style: currentTextStyle,
|
|
700
|
+
format: [
|
|
701
|
+
...(editorInstance.isActive('bold') ? ['b'] : []),
|
|
702
|
+
...(editorInstance.isActive('italic') ? ['i'] : []),
|
|
703
|
+
...(editorInstance.isActive('underline') ? ['u'] : []),
|
|
704
|
+
...(editorInstance.isActive('link') ? ['link'] : [])
|
|
705
|
+
],
|
|
706
|
+
color: currentColor
|
|
707
|
+
});
|
|
708
|
+
} else {
|
|
709
|
+
selectedRange = null;
|
|
710
|
+
currentColor = textColor; // Reset to default color when deselecting
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// Add this state to track animation
|
|
715
|
+
let isColorPickerAnimating = $state(false);
|
|
716
|
+
|
|
717
|
+
// Update handleColorButtonClick to ensure hex format
|
|
718
|
+
function handleColorButtonClick() {
|
|
719
|
+
if (!editorInstance) return;
|
|
720
|
+
|
|
721
|
+
const { from, to } = editorInstance.state.selection;
|
|
722
|
+
if (from !== to) {
|
|
723
|
+
selectedRange = { from, to };
|
|
724
|
+
isSelectionActive = true;
|
|
725
|
+
|
|
726
|
+
// Get and convert color to hex format
|
|
727
|
+
let selectedColor = getSelectedTextColor();
|
|
728
|
+
// Ensure we convert any rgb colors to hex
|
|
729
|
+
if (selectedColor.startsWith('rgb')) {
|
|
730
|
+
const matches = selectedColor.match(/\d+/g);
|
|
731
|
+
if (matches && matches.length >= 3) {
|
|
732
|
+
selectedColor = rgbToHex(
|
|
733
|
+
parseInt(matches[0]),
|
|
734
|
+
parseInt(matches[1]),
|
|
735
|
+
parseInt(matches[2])
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
// Remove any alpha channel
|
|
740
|
+
if (selectedColor.length === 9) {
|
|
741
|
+
selectedColor = selectedColor.substring(0, 7);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
currentColor = selectedColor;
|
|
745
|
+
|
|
746
|
+
// Set focus to editor
|
|
747
|
+
editorHasFocus = true;
|
|
748
|
+
// editorInstance.commands.focus();
|
|
749
|
+
|
|
750
|
+
} else {
|
|
751
|
+
selectedRange = null;
|
|
752
|
+
isSelectionActive = false;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
showColorPicker = true;
|
|
756
|
+
isColorPickerAnimating = true;
|
|
757
|
+
|
|
758
|
+
// console.log('Color picker toolbar opened with selection:', {
|
|
759
|
+
// range: selectedRange,
|
|
760
|
+
// color: currentColor,
|
|
761
|
+
// hasFocus: editorHasFocus,
|
|
762
|
+
// isActive
|
|
763
|
+
// });
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Update the event handler function to handle CustomEvent
|
|
767
|
+
function handleColorPickerInteraction(event: CustomEvent<any>) {
|
|
768
|
+
// Get the original MouseEvent from the detail if available
|
|
769
|
+
const mouseEvent = event.detail?.originalEvent as MouseEvent;
|
|
770
|
+
if (mouseEvent) {
|
|
771
|
+
// Prevent the default behavior that might clear the selection
|
|
772
|
+
mouseEvent.preventDefault();
|
|
773
|
+
mouseEvent.stopPropagation();
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// Reapply the selection if it exists
|
|
777
|
+
if (selectedRange && editorInstance) {
|
|
778
|
+
editorInstance.chain()
|
|
779
|
+
.focus()
|
|
780
|
+
.setTextSelection(selectedRange)
|
|
781
|
+
.run();
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// Update handleColorPickerClose function
|
|
786
|
+
function handleColorPickerClose() {
|
|
787
|
+
showColorPicker = false;
|
|
788
|
+
isColorPickerAnimating = false;
|
|
789
|
+
isSelectionActive = false;
|
|
790
|
+
|
|
791
|
+
if (editorInstance) {
|
|
792
|
+
// Create a chain to handle all operations
|
|
793
|
+
const chain = editorInstance.chain();
|
|
794
|
+
|
|
795
|
+
// Remove highlight if it exists
|
|
796
|
+
chain.unsetMark('highlight');
|
|
797
|
+
|
|
798
|
+
// Store current selection before cleanup
|
|
799
|
+
const currentSelection = editorInstance.state.selection;
|
|
800
|
+
const hasSelection = currentSelection.from !== currentSelection.to;
|
|
801
|
+
|
|
802
|
+
// Run the chain
|
|
803
|
+
chain.run();
|
|
804
|
+
|
|
805
|
+
// Remove hide-selection class
|
|
806
|
+
editorInstance.view.dom.classList.remove('hide-selection');
|
|
807
|
+
|
|
808
|
+
// Always focus the editor first
|
|
809
|
+
editorInstance.view.focus();
|
|
810
|
+
|
|
811
|
+
// Then reapply selection if it existed
|
|
812
|
+
if (hasSelection) {
|
|
813
|
+
editorInstance.commands.setTextSelection({
|
|
814
|
+
from: currentSelection.from,
|
|
815
|
+
to: currentSelection.to
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// Add effect to handle clicks outside Color Select
|
|
822
|
+
$effect(() => {
|
|
823
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
824
|
+
const colorSelectModal = document.querySelector('[data-radix-popper-content-wrapper]');
|
|
825
|
+
const isClickingColorSelect = colorSelectModal?.contains(event.target as Node);
|
|
826
|
+
|
|
827
|
+
// Don't do anything if clicking within the color select modal
|
|
828
|
+
if (isClickingColorSelect) {
|
|
829
|
+
// Prevent any focus changes
|
|
830
|
+
event.preventDefault();
|
|
831
|
+
event.stopPropagation();
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
if (isColorSelectOpen) {
|
|
837
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
838
|
+
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
839
|
+
}
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
/**
|
|
843
|
+
* Color Management System
|
|
844
|
+
*
|
|
845
|
+
* Handles text color operations:
|
|
846
|
+
* - Stores and restores text selection during color picker use
|
|
847
|
+
* - Manages color picker visibility state
|
|
848
|
+
* - Applies colors to selected text
|
|
849
|
+
* - Retrieves current text color for selected range
|
|
850
|
+
*
|
|
851
|
+
* Selection Preservation:
|
|
852
|
+
* - Stores selection before opening color picker
|
|
853
|
+
* - Restores selection after color picker closes
|
|
854
|
+
* - Handles edge cases when no text is selected
|
|
855
|
+
*/
|
|
856
|
+
|
|
857
|
+
// Add this with the other function declarations in the script section
|
|
858
|
+
function handleToolbarInteraction(event: MouseEvent) {
|
|
859
|
+
const target = event.target as HTMLElement;
|
|
860
|
+
|
|
861
|
+
// If clicking toolbar elements or color picker, maintain expanded state
|
|
862
|
+
if (target.matches('input, button, [role="button"], [tabindex="0"]') ||
|
|
863
|
+
target.closest('.color-picker-container')) {
|
|
864
|
+
// Keep toolbar enabled since we're interacting with it
|
|
865
|
+
editorHasFocus = true;
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
event.preventDefault();
|
|
870
|
+
event.stopPropagation();
|
|
871
|
+
|
|
872
|
+
// When clicking toolbar itself, maintain focus and enabled state
|
|
873
|
+
if (editorInstance) {
|
|
874
|
+
editorInstance.commands.focus();
|
|
875
|
+
editorHasFocus = true;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// Add this function to handle keyboard interactions
|
|
880
|
+
function handleToolbarKeyDown(event: KeyboardEvent) {
|
|
881
|
+
// Don't handle keyboard events for inputs and interactive elements
|
|
882
|
+
const target = event.target as HTMLElement;
|
|
883
|
+
if (target.matches('input, button, [role="button"], [tabindex="0"]')) {
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// Handle keyboard interactions for toolbar
|
|
888
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
889
|
+
event.preventDefault();
|
|
890
|
+
handleToolbarInteraction(event as unknown as MouseEvent);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// Update the effect that maintains selection during color picker interactions
|
|
895
|
+
$effect(() => {
|
|
896
|
+
if (showColorPicker && selectedTextRange && editorInstance) {
|
|
897
|
+
const { from, to } = selectedTextRange;
|
|
898
|
+
editorInstance.chain()
|
|
899
|
+
.setTextSelection({ from, to })
|
|
900
|
+
.run();
|
|
901
|
+
}
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
// Add these near the top of the script section with other state declarations
|
|
905
|
+
let isColorSelectOpen = $state(false);
|
|
906
|
+
|
|
907
|
+
// Add handleKeyDown function
|
|
908
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
909
|
+
if (event.key === 'Escape') {
|
|
910
|
+
if (showColorPicker) {
|
|
911
|
+
event.preventDefault();
|
|
912
|
+
handleColorPickerClose();
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// Update handleEditorBlur to maintain toolbar state
|
|
918
|
+
function handleEditorBlur(relatedTarget: Node | null) {
|
|
919
|
+
if (!editorInstance) return;
|
|
920
|
+
|
|
921
|
+
const colorSelectModal = document.querySelector('[data-radix-popper-content-wrapper]');
|
|
922
|
+
const popoverContent = document.querySelector('[role="dialog"]');
|
|
923
|
+
const commandMenu = document.querySelector('[cmdk-root]');
|
|
924
|
+
|
|
925
|
+
const isClickingInside = toolbarRef?.contains(relatedTarget) ||
|
|
926
|
+
editorInstance.view.dom.contains(relatedTarget as Node) ||
|
|
927
|
+
colorSelectModal?.contains(relatedTarget as Node) ||
|
|
928
|
+
popoverContent?.contains(relatedTarget as Node) ||
|
|
929
|
+
commandMenu?.contains(relatedTarget as Node);
|
|
930
|
+
|
|
931
|
+
if (!isClickingInside) {
|
|
932
|
+
editorHasFocus = false;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// Add this logging helper
|
|
937
|
+
function logTransitionState(source: string) {
|
|
938
|
+
// console.log(`Transition State [${source}]:`, {
|
|
939
|
+
// isTransitioning,
|
|
940
|
+
// editorHeight,
|
|
941
|
+
// isActive,
|
|
942
|
+
// editorHasFocus,
|
|
943
|
+
// isIdle: editorHeight === IDLE_HEIGHT
|
|
944
|
+
// });
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// Add this function to handle popover state changes
|
|
948
|
+
function handlePopoverOpenChange(isOpen: boolean) {
|
|
949
|
+
textStyleDropdownOpen = isOpen;
|
|
950
|
+
|
|
951
|
+
// Ensure editor maintains focus when popover opens/closes
|
|
952
|
+
if (editorInstance) {
|
|
953
|
+
editorHasFocus = true;
|
|
954
|
+
editorInstance.commands.focus();
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// Add this function to handle command item selection
|
|
959
|
+
function handleCommandSelect(value: string) {
|
|
960
|
+
if (!editorInstance) return;
|
|
961
|
+
|
|
962
|
+
// Change the text style to the selected value
|
|
963
|
+
handleTextStyleChange(value);
|
|
964
|
+
|
|
965
|
+
// Restore editor focus after command selection
|
|
966
|
+
setTimeout(() => {
|
|
967
|
+
if (!editorInstance) return;
|
|
968
|
+
editorHasFocus = true;
|
|
969
|
+
editorInstance.commands.focus();
|
|
970
|
+
}, 0);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// Add these variables to track button position
|
|
974
|
+
let triggerButtonRect = $state<DOMRect | null>(null);
|
|
975
|
+
let triggerButton: any;
|
|
976
|
+
let triggerButtonElement: HTMLButtonElement;
|
|
977
|
+
|
|
978
|
+
// Add function to update position using the button element directly
|
|
979
|
+
function updateTriggerPosition(buttonElement: HTMLButtonElement) {
|
|
980
|
+
triggerButtonRect = buttonElement.getBoundingClientRect();
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// Add these state variables near the top with other state declarations
|
|
984
|
+
let selectedRange: { from: number, to: number } | null = $state(null);
|
|
985
|
+
let isSelectionActive = $state(false);
|
|
986
|
+
|
|
987
|
+
// Add this near the top of the script section with other constants
|
|
988
|
+
const STYLE_DROPDOWN_ID = 'text-style-dropdown';
|
|
989
|
+
|
|
990
|
+
// Add these functions near the top with other event handlers
|
|
991
|
+
function closeTextStyleDropdown() {
|
|
992
|
+
if (textStyleDropdownOpen) {
|
|
993
|
+
textStyleDropdownOpen = false;
|
|
994
|
+
// Restore focus to editor
|
|
995
|
+
if (!editorInstance) return;
|
|
996
|
+
|
|
997
|
+
setTimeout(() => {
|
|
998
|
+
if (!editorInstance) return;
|
|
999
|
+
editorHasFocus = true;
|
|
1000
|
+
editorInstance.commands.focus();
|
|
1001
|
+
}, 0);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
function handleScroll() {
|
|
1006
|
+
closeTextStyleDropdown();
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// Add to lifecycle management
|
|
1010
|
+
onMount(() => {
|
|
1011
|
+
window.addEventListener('scroll', handleScroll, true);
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
onDestroy(() => {
|
|
1015
|
+
window.removeEventListener('scroll', handleScroll, true);
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
// Add cleanup function
|
|
1019
|
+
onDestroy(() => {
|
|
1020
|
+
if (editorInstance) {
|
|
1021
|
+
// Store the current content before unmounting
|
|
1022
|
+
lastKnownContent = editorInstance.getHTML();
|
|
1023
|
+
wasVisible = false;
|
|
1024
|
+
|
|
1025
|
+
// Clean up editor instance
|
|
1026
|
+
editorInstance.destroy();
|
|
1027
|
+
editorInstance = null;
|
|
1028
|
+
}
|
|
1029
|
+
});
|
|
1030
|
+
|
|
1031
|
+
// Update mousedown handler to remove height changes
|
|
1032
|
+
function handleMouseDown(e: MouseEvent) {
|
|
1033
|
+
if (editorInstance) {
|
|
1034
|
+
const target = e.target as HTMLElement;
|
|
1035
|
+
const isInteractive = target.matches('button, input, [role="button"], [tabindex="0"]') ||
|
|
1036
|
+
target.closest('button, input, [role="button"], [tabindex="0"]');
|
|
1037
|
+
|
|
1038
|
+
if (!isInteractive) {
|
|
1039
|
+
e.preventDefault();
|
|
1040
|
+
e.stopPropagation();
|
|
1041
|
+
|
|
1042
|
+
// Just focus the editor
|
|
1043
|
+
editorHasFocus = true;
|
|
1044
|
+
editorInstance.commands.focus();
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// Update hit zone handler to remove transitions
|
|
1050
|
+
function handleHitZone(e: MouseEvent) {
|
|
1051
|
+
e.preventDefault();
|
|
1052
|
+
e.stopPropagation();
|
|
1053
|
+
if (editorInstance) {
|
|
1054
|
+
editorInstance.commands.focus();
|
|
1055
|
+
editorHasFocus = true;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
</script>
|
|
1059
|
+
|
|
1060
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
1061
|
+
<!-- svelte-ignore event_directive_deprecated -->
|
|
1062
|
+
<div
|
|
1063
|
+
class="text-area-input-controller {className ? className + ' ' : ''}flex flex-col"
|
|
1064
|
+
on:keydown={handleKeyDown}
|
|
1065
|
+
tabindex="-1"
|
|
1066
|
+
>
|
|
1067
|
+
<!-- Label section -->
|
|
1068
|
+
<div class="label-container flex items-center justify-between">
|
|
1069
|
+
<!-- svelte-ignore a11y_label_has_associated_control -->
|
|
1070
|
+
<label class="subtext text-grey5 cursor-default select-none truncate">{label}</label>
|
|
1071
|
+
</div>
|
|
1072
|
+
|
|
1073
|
+
<!-- Main editor container -->
|
|
1074
|
+
<div
|
|
1075
|
+
class="main-editor-container group relative w-full h-full rounded-small widgetic-colors bg-grey1 flex flex-col overflow-hidden"
|
|
1076
|
+
style={editorStyle}
|
|
1077
|
+
on:mousedown={handleMouseDown}
|
|
1078
|
+
>
|
|
1079
|
+
<!-- Toolbar at the top -->
|
|
1080
|
+
<div
|
|
1081
|
+
bind:this={toolbarRef}
|
|
1082
|
+
class="toolbar-container sticky top-0 left-0 right-0 h-[56px] bg-grey1 z-10 border-b border-grey2 overflow-hidden select-none"
|
|
1083
|
+
on:mousedown={handleToolbarInteraction}
|
|
1084
|
+
on:click={handleToolbarInteraction}
|
|
1085
|
+
on:keydown={handleToolbarKeyDown}
|
|
1086
|
+
role="toolbar"
|
|
1087
|
+
aria-label="Text formatting toolbar"
|
|
1088
|
+
tabindex="-1"
|
|
1089
|
+
>
|
|
1090
|
+
<div class="flex items-center flex-1 relative h-8">
|
|
1091
|
+
<!-- Normal toolbar items -->
|
|
1092
|
+
<div class={`toolbar-items-container flex items-center flex-1 absolute inset-0 transition-transform duration-[${TRANSITION_DURATION}ms] ease-out ${
|
|
1093
|
+
isColorPickerAnimating ? 'translate-y-[-200%]' :
|
|
1094
|
+
showColorPicker ? 'translate-y-[-200%]' :
|
|
1095
|
+
'translate-y-0'
|
|
1096
|
+
}`}>
|
|
1097
|
+
<!-- Color Picker Preview Button -->
|
|
1098
|
+
<Button
|
|
1099
|
+
variant="outline"
|
|
1100
|
+
size="sm"
|
|
1101
|
+
class="color-picker-preview-button toolbar-button h-9 w-9 p-0 relative overflow-hidden mr-2"
|
|
1102
|
+
onclick={(e) => {
|
|
1103
|
+
e.stopPropagation();
|
|
1104
|
+
e.preventDefault();
|
|
1105
|
+
const button = e.currentTarget as HTMLButtonElement;
|
|
1106
|
+
button.blur();
|
|
1107
|
+
handleColorButtonClick();
|
|
1108
|
+
}}
|
|
1109
|
+
>
|
|
1110
|
+
<div class="toolbar-button-content flex h-full w-full items-center justify-center">
|
|
1111
|
+
<IconBrush
|
|
1112
|
+
class="!text-grey5 hover:!text-grey7"
|
|
1113
|
+
style="width: 20px !important; height: 20px !important; min-width: 20px !important; min-height: 20px !important; max-width: 20px !important; max-height: 20px !important; {selectedRange ? `color: ${currentColor} !important;` : ''}"
|
|
1114
|
+
/>
|
|
1115
|
+
</div>
|
|
1116
|
+
</Button>
|
|
1117
|
+
|
|
1118
|
+
<!-- Text Formatting Toggle Group -->
|
|
1119
|
+
<ToggleGroup.Root
|
|
1120
|
+
type="multiple"
|
|
1121
|
+
size="sm"
|
|
1122
|
+
variant="outline"
|
|
1123
|
+
class="text-formatting-toggle-group inline-flex h-8"
|
|
1124
|
+
value={toggleValues}
|
|
1125
|
+
onValueChange={(value) => {
|
|
1126
|
+
if (!editorInstance) return;
|
|
1127
|
+
if (value === undefined) return;
|
|
1128
|
+
handleToggleChange(value);
|
|
1129
|
+
}}
|
|
1130
|
+
>
|
|
1131
|
+
{#each Object.entries(formatMapping) as [format, { value, icon: Icon }]}
|
|
1132
|
+
<ToggleGroup.Item
|
|
1133
|
+
{value}
|
|
1134
|
+
aria-label={`Toggle ${format}`}
|
|
1135
|
+
data-state={activeFormats[format as FormatType] ? 'on' : 'off'}
|
|
1136
|
+
class="toolbar-button {activeFormats[format as FormatType] ? 'bg-muted' : ''} {format !== 'link' ? 'mr-2' : ''}"
|
|
1137
|
+
>
|
|
1138
|
+
<div class="toolbar-button-content flex h-full w-full items-center justify-center">
|
|
1139
|
+
<Icon
|
|
1140
|
+
class="!text-grey5 hover:!text-grey7"
|
|
1141
|
+
style="width: 20px !important; height: 20px !important; min-width: 20px !important; min-height: 20px !important; max-width: 20px !important; max-height: 20px !important;"
|
|
1142
|
+
/>
|
|
1143
|
+
</div>
|
|
1144
|
+
</ToggleGroup.Item>
|
|
1145
|
+
{/each}
|
|
1146
|
+
</ToggleGroup.Root>
|
|
1147
|
+
|
|
1148
|
+
<!-- Text Style Dropdown -->
|
|
1149
|
+
<div class="pl-2">
|
|
1150
|
+
<Popover.Root
|
|
1151
|
+
bind:open={textStyleDropdownOpen}
|
|
1152
|
+
onOpenChange={(e: any) => {
|
|
1153
|
+
handlePopoverOpenChange(e);
|
|
1154
|
+
textStyleDropdownOpen = e;
|
|
1155
|
+
}}
|
|
1156
|
+
>
|
|
1157
|
+
<Popover.Trigger>
|
|
1158
|
+
<Button
|
|
1159
|
+
bind:this={triggerButton}
|
|
1160
|
+
id="text-style-trigger"
|
|
1161
|
+
variant="outline"
|
|
1162
|
+
size="sm"
|
|
1163
|
+
class="toolbar-button h-8 w-[54px] justify-between"
|
|
1164
|
+
onclick={(e) => {
|
|
1165
|
+
e.stopPropagation();
|
|
1166
|
+
e.preventDefault();
|
|
1167
|
+
|
|
1168
|
+
const buttonElement = e.currentTarget as HTMLButtonElement;
|
|
1169
|
+
updateTriggerPosition(buttonElement);
|
|
1170
|
+
buttonElement.blur();
|
|
1171
|
+
|
|
1172
|
+
textStyleDropdownOpen = !textStyleDropdownOpen;
|
|
1173
|
+
}}
|
|
1174
|
+
>
|
|
1175
|
+
<div class="toolbar-button-content flex h-full w-full items-center justify-between px-2">
|
|
1176
|
+
{#if currentTextStyle === 'text'}
|
|
1177
|
+
<IconPilcrow class="h-4 w-4" />
|
|
1178
|
+
{:else}
|
|
1179
|
+
<span>{textOptions.find(opt => opt.value === currentTextStyle)?.label || 'Text'}</span>
|
|
1180
|
+
{/if}
|
|
1181
|
+
<ChevronDown class="h-4 w-4" />
|
|
1182
|
+
</div>
|
|
1183
|
+
</Button>
|
|
1184
|
+
</Popover.Trigger>
|
|
1185
|
+
|
|
1186
|
+
<Popover.Content
|
|
1187
|
+
class="w-[200px] p-0"
|
|
1188
|
+
>
|
|
1189
|
+
<Command.Root>
|
|
1190
|
+
<Command.List>
|
|
1191
|
+
{#each textOptions as option}
|
|
1192
|
+
<Command.Item
|
|
1193
|
+
value={option.value}
|
|
1194
|
+
onclick={(e: any) => {
|
|
1195
|
+
e.stopPropagation();
|
|
1196
|
+
e.preventDefault();
|
|
1197
|
+
handleCommandSelect(option.value);
|
|
1198
|
+
}}
|
|
1199
|
+
class="flex items-center justify-between px-2 py-1.5 text-sm hover:bg-muted cursor-pointer"
|
|
1200
|
+
>
|
|
1201
|
+
{#if option.icon}
|
|
1202
|
+
<div class="flex items-center gap-2">
|
|
1203
|
+
{#if option.icon}
|
|
1204
|
+
<option.icon class="h-4 w-4" />
|
|
1205
|
+
{/if}
|
|
1206
|
+
<span>{option.label}</span>
|
|
1207
|
+
</div>
|
|
1208
|
+
{:else}
|
|
1209
|
+
<span>{option.label}</span>
|
|
1210
|
+
{/if}
|
|
1211
|
+
{#if option.value === currentTextStyle}
|
|
1212
|
+
<div class="h-4 w-4">✓</div>
|
|
1213
|
+
{/if}
|
|
1214
|
+
</Command.Item>
|
|
1215
|
+
{/each}
|
|
1216
|
+
</Command.List>
|
|
1217
|
+
</Command.Root>
|
|
1218
|
+
</Popover.Content>
|
|
1219
|
+
</Popover.Root>
|
|
1220
|
+
</div>
|
|
1221
|
+
|
|
1222
|
+
<!-- List Style Dropdown -->
|
|
1223
|
+
<div class="pl-2">
|
|
1224
|
+
<Popover.Root
|
|
1225
|
+
bind:open={listStyleDropdownOpen}
|
|
1226
|
+
onOpenChange={(e: any) => {
|
|
1227
|
+
handlePopoverOpenChange(e);
|
|
1228
|
+
listStyleDropdownOpen = e;
|
|
1229
|
+
}}
|
|
1230
|
+
>
|
|
1231
|
+
<Popover.Trigger>
|
|
1232
|
+
<Button
|
|
1233
|
+
variant="outline"
|
|
1234
|
+
size="sm"
|
|
1235
|
+
class="toolbar-button h-8 w-[54px] justify-between"
|
|
1236
|
+
onclick={(e) => {
|
|
1237
|
+
e.stopPropagation();
|
|
1238
|
+
e.preventDefault();
|
|
1239
|
+
const buttonElement = e.currentTarget as HTMLButtonElement;
|
|
1240
|
+
buttonElement.blur();
|
|
1241
|
+
listStyleDropdownOpen = !listStyleDropdownOpen;
|
|
1242
|
+
}}
|
|
1243
|
+
>
|
|
1244
|
+
<div class="toolbar-button-content flex h-full w-full items-center justify-between px-2">
|
|
1245
|
+
{#if currentTextStyle && currentTextStyle.includes('List')}
|
|
1246
|
+
{@const activeListOption = listOptions.find(opt => opt.value === currentTextStyle)}
|
|
1247
|
+
{#if activeListOption?.icon}
|
|
1248
|
+
<activeListOption.icon class="h-4 w-4" />
|
|
1249
|
+
{/if}
|
|
1250
|
+
{:else}
|
|
1251
|
+
<IconList class="h-4 w-4" />
|
|
1252
|
+
{/if}
|
|
1253
|
+
<ChevronDown class="h-4 w-4" />
|
|
1254
|
+
</div>
|
|
1255
|
+
</Button>
|
|
1256
|
+
</Popover.Trigger>
|
|
1257
|
+
|
|
1258
|
+
<Popover.Content
|
|
1259
|
+
class="style-dropdown-container w-[300px] p-0"
|
|
1260
|
+
>
|
|
1261
|
+
<Command.Root>
|
|
1262
|
+
<Command.List>
|
|
1263
|
+
{#each listOptions as option}
|
|
1264
|
+
<Command.Item
|
|
1265
|
+
value={option.value}
|
|
1266
|
+
onclick={(e: any) => {
|
|
1267
|
+
e.stopPropagation();
|
|
1268
|
+
e.preventDefault();
|
|
1269
|
+
handleCommandSelect(option.value);
|
|
1270
|
+
}}
|
|
1271
|
+
class="flex items-center justify-between px-2 py-1.5 text-sm hover:bg-muted cursor-pointer"
|
|
1272
|
+
>
|
|
1273
|
+
<div class="flex items-center gap-2">
|
|
1274
|
+
{#if option.icon}
|
|
1275
|
+
<option.icon class="h-4 w-4" />
|
|
1276
|
+
{/if}
|
|
1277
|
+
<span>{option.label}</span>
|
|
1278
|
+
</div>
|
|
1279
|
+
{#if option.value === currentTextStyle}
|
|
1280
|
+
<div class="h-4 w-4">✓</div>
|
|
1281
|
+
{/if}
|
|
1282
|
+
</Command.Item>
|
|
1283
|
+
{/each}
|
|
1284
|
+
</Command.List>
|
|
1285
|
+
</Command.Root>
|
|
1286
|
+
</Popover.Content>
|
|
1287
|
+
</Popover.Root>
|
|
1288
|
+
</div>
|
|
1289
|
+
</div>
|
|
1290
|
+
|
|
1291
|
+
<!-- Color picker panel -->
|
|
1292
|
+
<div class={`color-picker-parent flex items-center gap-2 absolute inset-0 transition-transform duration-[${TRANSITION_DURATION}ms] ease-out ${
|
|
1293
|
+
isColorPickerAnimating ? 'translate-y-0' :
|
|
1294
|
+
showColorPicker ? 'translate-y-0' :
|
|
1295
|
+
'translate-y-[200%]'
|
|
1296
|
+
}`}>
|
|
1297
|
+
<!-- Close Button First -->
|
|
1298
|
+
<Button
|
|
1299
|
+
variant="outline"
|
|
1300
|
+
size="sm"
|
|
1301
|
+
class="color-picker-close-button toolbar-button h-9 w-9 p-0 ml-4"
|
|
1302
|
+
onclick={(e) => {
|
|
1303
|
+
e.stopPropagation();
|
|
1304
|
+
e.preventDefault();
|
|
1305
|
+
const button = e.currentTarget as HTMLButtonElement;
|
|
1306
|
+
button.blur();
|
|
1307
|
+
handleColorPickerClose();
|
|
1308
|
+
}}
|
|
1309
|
+
>
|
|
1310
|
+
<div class="toolbar-button-content flex h-full w-full items-center justify-center">
|
|
1311
|
+
<X class="h-4 w-4" />
|
|
1312
|
+
</div>
|
|
1313
|
+
</Button>
|
|
1314
|
+
|
|
1315
|
+
<!-- Color Picker Container -->
|
|
1316
|
+
<div class="flex-1">
|
|
1317
|
+
<ColorPickerInputController
|
|
1318
|
+
bind:value={currentColor}
|
|
1319
|
+
onChange={(color) => {
|
|
1320
|
+
currentColor = color;
|
|
1321
|
+
handleColorChange(color);
|
|
1322
|
+
}}
|
|
1323
|
+
controllerId="text-color-picker"
|
|
1324
|
+
{openDropdowns}
|
|
1325
|
+
{toggleDropdown}
|
|
1326
|
+
saveRecentColors={true}
|
|
1327
|
+
showOpacityControls={false}
|
|
1328
|
+
showRing={false}
|
|
1329
|
+
portalTarget="text-area-portal"
|
|
1330
|
+
on:mousedown={handleColorPickerInteraction}
|
|
1331
|
+
on:mousemove={handleColorPickerInteraction}
|
|
1332
|
+
on:focus={() => {
|
|
1333
|
+
if (editorInstance && selectedRange) {
|
|
1334
|
+
editorInstance.chain()
|
|
1335
|
+
.setTextSelection(selectedRange)
|
|
1336
|
+
.setMark('highlight', { color: 'var(--text-background-color)' })
|
|
1337
|
+
.run();
|
|
1338
|
+
editorInstance.view.dom.classList.add('hide-selection');
|
|
1339
|
+
}
|
|
1340
|
+
}}
|
|
1341
|
+
on:blur={() => {
|
|
1342
|
+
if (editorInstance) {
|
|
1343
|
+
editorInstance.chain().unsetMark('highlight').run();
|
|
1344
|
+
}
|
|
1345
|
+
}}
|
|
1346
|
+
/>
|
|
1347
|
+
</div>
|
|
1348
|
+
</div>
|
|
1349
|
+
</div>
|
|
1350
|
+
</div>
|
|
1351
|
+
|
|
1352
|
+
<!-- Scrollable content area -->
|
|
1353
|
+
<div class="editor-content-area h-[calc(100%-56px)] overflow-auto">
|
|
1354
|
+
<ShadEditor
|
|
1355
|
+
bind:content={value}
|
|
1356
|
+
showToolbar={false}
|
|
1357
|
+
class="shad-editor subtext {editorClass} {showColorPicker || textStyleDropdownOpen ? 'color-picking-active' : ''} {editorHasFocus ? 'editor-focused' : ''} {isSelectionActive ? 'selection-active' : ''}"
|
|
1358
|
+
on:mount={handleEditorMount}
|
|
1359
|
+
on:update={handleEditorUpdate}
|
|
1360
|
+
/>
|
|
1361
|
+
</div>
|
|
1362
|
+
</div>
|
|
1363
|
+
|
|
1364
|
+
{#if description}
|
|
1365
|
+
<p class="text-sm text-muted-foreground mt-2">{description}</p>
|
|
1366
|
+
{/if}
|
|
1367
|
+
</div>
|
|
1368
|
+
|
|
1369
|
+
<style>
|
|
1370
|
+
|
|
1371
|
+
/* Update editor styles */
|
|
1372
|
+
:global(.ProseMirror) {
|
|
1373
|
+
outline: none !important;
|
|
1374
|
+
width: 100%;
|
|
1375
|
+
height: calc(100% - 56px);
|
|
1376
|
+
padding: var(--editor-padding-top, 0.35rem) var(--editor-padding-right, 0.35rem)
|
|
1377
|
+
var(--editor-padding-bottom, 0.35rem) var(--editor-padding-left, 0.35rem);
|
|
1378
|
+
background: transparent !important;
|
|
1379
|
+
border: none !important;
|
|
1380
|
+
border-radius: inherit;
|
|
1381
|
+
overflow-y: auto;
|
|
1382
|
+
margin-top: 56px;
|
|
1383
|
+
scrollbar-width: thin;
|
|
1384
|
+
scrollbar-color: transparent transparent;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
:global(.ProseMirror p) {
|
|
1388
|
+
margin: 0;
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
/* Remove height management styles */
|
|
1392
|
+
:global(.editor-height) {
|
|
1393
|
+
border: none;
|
|
1394
|
+
overflow: auto;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
/* Keep scrollbar styles */
|
|
1398
|
+
:global(.ProseMirror::-webkit-scrollbar) {
|
|
1399
|
+
width: 6px;
|
|
1400
|
+
margin-right: 2px;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
:global(.ProseMirror::-webkit-scrollbar-track) {
|
|
1404
|
+
background-color: transparent;
|
|
1405
|
+
margin-right: 2px;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
:global(.ProseMirror::-webkit-scrollbar-thumb) {
|
|
1409
|
+
background-color: hsl(var(--border)) !important;
|
|
1410
|
+
border-radius: 3px;
|
|
1411
|
+
margin-right: 2px;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
:global(.ProseMirror::-webkit-scrollbar-thumb:hover) {
|
|
1415
|
+
background-color: hsl(var(--muted-foreground) / 0.6) !important;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
/* Keep typography styles */
|
|
1419
|
+
:global(.ProseMirror p),
|
|
1420
|
+
:global(.ProseMirror h1),
|
|
1421
|
+
:global(.ProseMirror h2),
|
|
1422
|
+
:global(.ProseMirror h3),
|
|
1423
|
+
:global(.ProseMirror li p),
|
|
1424
|
+
:global(.ProseMirror [data-type="taskItem"] p) {
|
|
1425
|
+
margin: 0;
|
|
1426
|
+
line-height: 1.4;
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
/* Heading specific styles */
|
|
1430
|
+
:global(.ProseMirror h1) { font-size: 2em; font-weight: 700; }
|
|
1431
|
+
:global(.ProseMirror h2) { font-size: 1.5em; font-weight: 700; }
|
|
1432
|
+
:global(.ProseMirror h3) { font-size: 1.17em; font-weight: 700; }
|
|
1433
|
+
|
|
1434
|
+
/* List styles - Consolidated */
|
|
1435
|
+
:global(.ProseMirror ul),
|
|
1436
|
+
:global(.ProseMirror ol),
|
|
1437
|
+
:global(.ProseMirror [data-type="taskList"]) {
|
|
1438
|
+
padding-left: 1.5rem;
|
|
1439
|
+
margin: 0;
|
|
1440
|
+
line-height: 1.4;
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
:global(.ProseMirror li),
|
|
1444
|
+
:global(.ProseMirror [data-type="taskItem"]) {
|
|
1445
|
+
position: relative;
|
|
1446
|
+
padding-left: 0.5rem;
|
|
1447
|
+
margin: 0;
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
/* Task list specific styles */
|
|
1451
|
+
:global(.ProseMirror [data-type="taskList"]) {
|
|
1452
|
+
list-style: none;
|
|
1453
|
+
padding-left: 0rem !important;
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
:global(.ProseMirror [data-type="taskItem"]) {
|
|
1457
|
+
display: flex;
|
|
1458
|
+
align-items: flex-start;
|
|
1459
|
+
padding-left: 0 !important;
|
|
1460
|
+
margin-left: 0 !important;
|
|
1461
|
+
gap: 0.5rem;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
:global(.ProseMirror [data-type="taskItem"] label) {
|
|
1465
|
+
display: flex;
|
|
1466
|
+
align-items: center;
|
|
1467
|
+
margin: 0;
|
|
1468
|
+
padding: 0;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
:global(.ProseMirror [data-type="taskItem"] input[type="checkbox"]) {
|
|
1472
|
+
margin: 0;
|
|
1473
|
+
margin-top: 0.4em;
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
/* Focus states - Consolidated */
|
|
1477
|
+
:global(.ProseMirror),
|
|
1478
|
+
:global(.ProseMirror:hover),
|
|
1479
|
+
:global(.ProseMirror:focus),
|
|
1480
|
+
:global(.ProseMirror:focus-visible),
|
|
1481
|
+
:global(.ProseMirror:focus-within),
|
|
1482
|
+
:global(.ProseMirror *),
|
|
1483
|
+
:global(.ProseMirror * *),
|
|
1484
|
+
:global(.ProseMirror-focused) {
|
|
1485
|
+
border-color: transparent !important;
|
|
1486
|
+
outline: none !important;
|
|
1487
|
+
box-shadow: none !important;
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
/* Container focus styles */
|
|
1491
|
+
:global(.group:focus-within) {
|
|
1492
|
+
outline: none !important;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
/* Toolbar styles - Consolidated */
|
|
1496
|
+
:global(.toolbar-button) {
|
|
1497
|
+
position: relative !important;
|
|
1498
|
+
border: 1px solid hsl(var(--border)) !important;
|
|
1499
|
+
transition: border-color 0.2s ease-in-out, background-color 0.2s ease-in-out !important;
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
:global(.toolbar-button:hover) {
|
|
1503
|
+
border-color: hsl(var(--grey3)) !important;
|
|
1504
|
+
background-color: hsl(var(--grey1)) !important;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
:global(.toolbar-button svg) {
|
|
1508
|
+
transition: color 0.2s ease-in-out !important;
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
:global(.toolbar-button:hover svg) {
|
|
1512
|
+
color: hsl(var(--grey7)) !important;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
:global(.toolbar-button-content) {
|
|
1516
|
+
position: relative !important;
|
|
1517
|
+
z-index: 2 !important;
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
/* Input styles */
|
|
1521
|
+
:global(input.h-8),
|
|
1522
|
+
:global(.input-container) {
|
|
1523
|
+
height: 2rem !important;
|
|
1524
|
+
min-height: 2rem !important;
|
|
1525
|
+
line-height: 2rem !important;
|
|
1526
|
+
padding-top: 0 !important;
|
|
1527
|
+
padding-bottom: 0 !important;
|
|
1528
|
+
font-size: 0.875rem !important;
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
/* Color picker styles */
|
|
1532
|
+
:global(.color-picker-container) {
|
|
1533
|
+
display: flex !important;
|
|
1534
|
+
align-items: center !important;
|
|
1535
|
+
gap: 8px !important;
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
:global(.color-picker-container input[type="text"]) {
|
|
1539
|
+
width: 162px !important;
|
|
1540
|
+
min-width: 162px !important;
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
:global(.color-picker-container:focus-within) {
|
|
1544
|
+
z-index: 10;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
:global(.color-picker-container input[type="text"]),
|
|
1548
|
+
:global(.color-picker-container input[type="text"]:focus),
|
|
1549
|
+
:global(.color-picker-container input[type="text"]:focus-visible),
|
|
1550
|
+
:global(.color-picker-container input[type="text"]:focus-within),
|
|
1551
|
+
:global(.color-picker-container input[type="text"]:hover) {
|
|
1552
|
+
outline: none !important;
|
|
1553
|
+
border: 1px solid hsl(var(--border)) !important;
|
|
1554
|
+
box-shadow: none !important;
|
|
1555
|
+
--tw-ring-offset-width: 0 !important;
|
|
1556
|
+
--tw-ring-offset-color: transparent !important;
|
|
1557
|
+
--tw-ring-color: transparent !important;
|
|
1558
|
+
--tw-ring-offset-shadow: none !important;
|
|
1559
|
+
--tw-ring-shadow: none !important;
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
/* Add these styles to ensure proper positioning */
|
|
1563
|
+
:global(.fixed) {
|
|
1564
|
+
position: fixed;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
:global(.z-50) {
|
|
1568
|
+
z-index: 50;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
/* Update existing styles to handle portaled content */
|
|
1572
|
+
:global([cmdk-root]) {
|
|
1573
|
+
position: relative;
|
|
1574
|
+
background: white;
|
|
1575
|
+
border-radius: 0.375rem;
|
|
1576
|
+
overflow: hidden;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
:global([cmdk-list]) {
|
|
1580
|
+
max-height: 300px;
|
|
1581
|
+
overflow-y: auto;
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
:global([cmdk-item]) {
|
|
1585
|
+
cursor: pointer;
|
|
1586
|
+
padding: 0.5rem 1rem;
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
:global([cmdk-item]:hover) {
|
|
1590
|
+
background-color: hsl(var(--muted));
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
/* Add these styles to handle the portaled color suggestions */
|
|
1594
|
+
:global(.color-suggestions-portal) {
|
|
1595
|
+
position: fixed;
|
|
1596
|
+
z-index: 100;
|
|
1597
|
+
background: white;
|
|
1598
|
+
border-radius: 0.375rem;
|
|
1599
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
1600
|
+
border: 1px solid hsl(var(--border));
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
/* Selection Styles */
|
|
1604
|
+
:global(.ProseMirror mark[data-color="#adcff5"]) {
|
|
1605
|
+
background-color: #b4d5fe !important;
|
|
1606
|
+
color: inherit !important;
|
|
1607
|
+
border-radius: 2px !important;
|
|
1608
|
+
padding: 2px 0 !important;
|
|
1609
|
+
margin: -2px 0 !important;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
/* Selection Styles */
|
|
1613
|
+
:global(.ProseMirror) {
|
|
1614
|
+
--selection-color: #b4d5fe;
|
|
1615
|
+
--text-background-color: #b4d5fe;
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
/* Default selection style */
|
|
1619
|
+
:global(.ProseMirror ::selection) {
|
|
1620
|
+
background-color: var(--selection-color) !important;
|
|
1621
|
+
color: inherit !important;
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
/* Selection style during color picker */
|
|
1625
|
+
:global(.ProseMirror.color-picking-active ::selection) {
|
|
1626
|
+
background-color: var(--selection-color) !important;
|
|
1627
|
+
color: inherit !important;
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
/* Hide selection when text background is active */
|
|
1631
|
+
:global(.ProseMirror.hide-selection ::selection) {
|
|
1632
|
+
background-color: transparent !important;
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
/* Text background highlight */
|
|
1636
|
+
:global(.ProseMirror mark[data-highlight]) {
|
|
1637
|
+
background-color: var(--text-background-color) !important;
|
|
1638
|
+
color: inherit !important;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
:global(.text-area) {
|
|
1642
|
+
height: 100% !important;
|
|
1643
|
+
width: 100% !important;
|
|
1644
|
+
border-radius: var(--radius-small) !important;
|
|
1645
|
+
overflow: hidden !important;
|
|
1646
|
+
background-color: hsl(var(--grey1)) !important;
|
|
1647
|
+
border: 1px solid hsl(var(--grey2));
|
|
1648
|
+
transition: border-color 0.2s ease !important;
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
:global(.text-area .ProseMirror) {
|
|
1652
|
+
margin-top: 0 !important;
|
|
1653
|
+
padding: 8px 16px 24px 16px !important;
|
|
1654
|
+
background-color: transparent !important;
|
|
1655
|
+
cursor: text !important;
|
|
1656
|
+
border: none !important;
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
/* Add portal border radius */
|
|
1660
|
+
:global(.portal-contents) {
|
|
1661
|
+
border-radius: var(--radius-small) !important;
|
|
1662
|
+
overflow: hidden !important;
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
:global(.text-area .toolbar-container) {
|
|
1666
|
+
padding: 10px 0 !important;
|
|
1667
|
+
display: flex !important;
|
|
1668
|
+
gap: 0 !important;
|
|
1669
|
+
border-bottom: 1px solid hsl(var(--grey2)) !important;
|
|
1670
|
+
background-color: hsl(var(--grey1)) !important;
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
:global(.text-area .toolbar-container button) {
|
|
1674
|
+
width: 36px !important;
|
|
1675
|
+
height: 36px !important;
|
|
1676
|
+
background-color: white !important;
|
|
1677
|
+
border: 1px solid hsl(var(--grey2)) !important;
|
|
1678
|
+
border-radius: var(--radius) !important;
|
|
1679
|
+
display: flex !important;
|
|
1680
|
+
align-items: center !important;
|
|
1681
|
+
justify-content: center !important;
|
|
1682
|
+
transition: all 0.2s !important;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
/* Color the brush icon based on selected color */
|
|
1686
|
+
:global(.text-area .color-picker-preview-button svg) {
|
|
1687
|
+
color: var(--selected-color, currentColor) !important;
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
:global(.text-area .color-picker-preview-button:hover svg) {
|
|
1691
|
+
color: var(--selected-color, hsl(var(--grey7))) !important;
|
|
1692
|
+
}
|
|
1693
|
+
/* Button spacing */
|
|
1694
|
+
:global(.text-area .color-picker-preview-button) {
|
|
1695
|
+
margin-left: 16px !important;
|
|
1696
|
+
margin-right: 8px !important;
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
:global(.text-area .text-formatting-toggle-group) {
|
|
1700
|
+
margin-right: 8px !important;
|
|
1701
|
+
display: flex !important;
|
|
1702
|
+
gap: 0 !important;
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
:global(.text-area .text-formatting-toggle-group button) {
|
|
1706
|
+
margin: 0 !important;
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
:global(.text-area .text-formatting-toggle-group button:not(:last-child)) {
|
|
1710
|
+
margin-right: 8px !important;
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
:global(.text-area #text-style-trigger) {
|
|
1714
|
+
width: 54px !important;
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
:global(.text-area .toolbar-container button:hover) {
|
|
1718
|
+
border-color: hsl(var(--grey2)) !important;
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
:global(.text-area .toolbar-container button:active) {
|
|
1722
|
+
transform: scale(0.95) !important;
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
:global(.text-area .toolbar-container button svg) {
|
|
1726
|
+
width: 20px !important;
|
|
1727
|
+
height: 20px !important;
|
|
1728
|
+
color: hsl(var(--grey5)) !important;
|
|
1729
|
+
transition: color 0.2s !important;
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
:global(.text-area .toolbar-container button:hover svg) {
|
|
1733
|
+
color: hsl(var(--grey7)) !important;
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
/* Style for active state */
|
|
1737
|
+
:global(.text-area .toolbar-container button[data-state="on"]) {
|
|
1738
|
+
background-color: hsl(var(--sage-green)) !important;
|
|
1739
|
+
border-color: hsl(var(--grey1)) !important;
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
:global(.text-area .toolbar-container button[data-state="on"] svg) {
|
|
1743
|
+
color: white !important;
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
/* Update the color picker button to show selected color */
|
|
1747
|
+
:global(.text-area .color-picker-preview-button) {
|
|
1748
|
+
position: relative !important;
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
:global(.text-area .color-picker-preview-button::after) {
|
|
1752
|
+
display: none !important;
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
/* Update icon color based on selection */
|
|
1756
|
+
:global(.text-area .color-picker-preview-button .toolbar-button-content svg) {
|
|
1757
|
+
transition: color 0.2s ease !important;
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
:global(.text-area .color-picker-preview-button:hover .toolbar-button-content svg) {
|
|
1761
|
+
color: currentColor !important;
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
/* Remove hit zone since we want clicks everywhere */
|
|
1765
|
+
.input-hit-zone {
|
|
1766
|
+
display: none;
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
/* Make the main container clickable */
|
|
1770
|
+
.main-editor-container {
|
|
1771
|
+
cursor: text;
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
/* Add width for list style dropdown button */
|
|
1775
|
+
:global(.text-area .toolbar-button.h-8) {
|
|
1776
|
+
width: 54px !important;
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
/* Update color picker container styles */
|
|
1780
|
+
:global(.color-picker-parent) {
|
|
1781
|
+
position: absolute;
|
|
1782
|
+
inset: 0;
|
|
1783
|
+
padding: 0;
|
|
1784
|
+
display: flex;
|
|
1785
|
+
align-items: center;
|
|
1786
|
+
gap: 8px;
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
:global(.color-picker-parent input[type="text"]) {
|
|
1790
|
+
width: 162px !important;
|
|
1791
|
+
min-width: 162px !important;
|
|
1792
|
+
height: 36px !important;
|
|
1793
|
+
min-height: 36px !important;
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
:global(.color-picker-close-button) {
|
|
1797
|
+
width: 36px !important;
|
|
1798
|
+
height: 36px !important;
|
|
1799
|
+
min-width: 36px !important;
|
|
1800
|
+
min-height: 36px !important;
|
|
1801
|
+
}
|
|
1802
|
+
</style>
|
|
1803
|
+
|
|
1804
|
+
|