@widgetic/editor 3.11.3 → 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,1283 @@
|
|
|
1
|
+
import { fontUtils, fontCategories } from "./fontUtils";
|
|
2
|
+
import FontFaceObserver from 'fontfaceobserver';
|
|
3
|
+
import { shouldSkipFont } from './skipFonts';
|
|
4
|
+
import fontsJson from '../assets/fonts.json';
|
|
5
|
+
import { deleteKvValue, readKvValue, writeKvValue } from '@widgetic/cache-layer';
|
|
6
|
+
const fontWeightOrder = {
|
|
7
|
+
Thin: 1,
|
|
8
|
+
ExtraLight: 2,
|
|
9
|
+
Light: 3,
|
|
10
|
+
Regular: 4,
|
|
11
|
+
Medium: 5,
|
|
12
|
+
SemiBold: 6,
|
|
13
|
+
Bold: 7,
|
|
14
|
+
ExtraBold: 8,
|
|
15
|
+
Black: 9,
|
|
16
|
+
};
|
|
17
|
+
/** Maps Google Fonts API variant ids to display weight labels. */
|
|
18
|
+
export function mapGoogleVariantIdsToWeightNames(variants) {
|
|
19
|
+
return variants
|
|
20
|
+
.filter((v) => !v.includes('italic'))
|
|
21
|
+
.map((v) => {
|
|
22
|
+
switch (v) {
|
|
23
|
+
case '100':
|
|
24
|
+
return 'Thin';
|
|
25
|
+
case '200':
|
|
26
|
+
return 'ExtraLight';
|
|
27
|
+
case '300':
|
|
28
|
+
return 'Light';
|
|
29
|
+
case '400':
|
|
30
|
+
case 'regular':
|
|
31
|
+
return 'Regular';
|
|
32
|
+
case '500':
|
|
33
|
+
return 'Medium';
|
|
34
|
+
case '600':
|
|
35
|
+
return 'SemiBold';
|
|
36
|
+
case '700':
|
|
37
|
+
case 'bold':
|
|
38
|
+
return 'Bold';
|
|
39
|
+
case '800':
|
|
40
|
+
return 'ExtraBold';
|
|
41
|
+
case '900':
|
|
42
|
+
return 'Black';
|
|
43
|
+
default:
|
|
44
|
+
return v;
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
.sort((a, b) => (fontWeightOrder[a] || 99) - (fontWeightOrder[b] || 99));
|
|
48
|
+
}
|
|
49
|
+
class FontManager {
|
|
50
|
+
// Debug configuration
|
|
51
|
+
static DEBUG_MODE = false;
|
|
52
|
+
static WAIT_TIME = FontManager.DEBUG_MODE ? 2500 : 250;
|
|
53
|
+
// Singleton instance
|
|
54
|
+
static instance = null;
|
|
55
|
+
// Track loaded and pending fonts to prevent duplicates
|
|
56
|
+
loadedFonts = new Set();
|
|
57
|
+
// Map of intersection observers keyed by container ID
|
|
58
|
+
observers = new Map();
|
|
59
|
+
// Debounce timer for scroll events
|
|
60
|
+
scrollTimeout = null;
|
|
61
|
+
// Google Fonts API key
|
|
62
|
+
apiKey;
|
|
63
|
+
// Cache promise to prevent multiple simultaneous API calls
|
|
64
|
+
fontListPromise = null;
|
|
65
|
+
/**
|
|
66
|
+
* Catalog lifecycle as an explicit union. Replaces the former
|
|
67
|
+
* `initialFontsLoaded`/`allFontsLoaded` boolean pair whose ordering
|
|
68
|
+
* ('initial' must precede 'complete') was only convention. Only the
|
|
69
|
+
* 'complete' stage is ever persisted to the durable cache, preventing
|
|
70
|
+
* partial catalogs from being written.
|
|
71
|
+
*/
|
|
72
|
+
catalog = { stage: 'empty' };
|
|
73
|
+
/** Current catalog contents regardless of stage (empty until loaded). */
|
|
74
|
+
get catalogFonts() {
|
|
75
|
+
return this.catalog.stage === 'empty' ? [] : this.catalog.fonts;
|
|
76
|
+
}
|
|
77
|
+
// Cache configuration — the font catalog is global (not user-owned), so it
|
|
78
|
+
// lives in the L1 kv table without an owner prefix.
|
|
79
|
+
static CACHE_KEY = 'fonts.catalog';
|
|
80
|
+
// Controls how long we keep the font list in the durable L1 store
|
|
81
|
+
static CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours
|
|
82
|
+
static LOADED_FONTS_KEY = 'fonts.loaded';
|
|
83
|
+
// Legacy localStorage keys read once for migration
|
|
84
|
+
static LEGACY_CACHE_KEY = 'google_fonts_cache';
|
|
85
|
+
static LEGACY_LOADED_FONTS_KEY = 'loaded_fonts_cache';
|
|
86
|
+
// Controls how long browsers should cache the actual font files and stylesheets
|
|
87
|
+
static CACHE_CONTROL = 'public, max-age=31536000'; // 1 year
|
|
88
|
+
// Add new property to store visible fonts
|
|
89
|
+
visibleFonts = new Set();
|
|
90
|
+
static initialPreloadComplete = false;
|
|
91
|
+
static preloadPromise = null;
|
|
92
|
+
completeFontList = [];
|
|
93
|
+
// Add new property to track initialization
|
|
94
|
+
static isInitialized = false;
|
|
95
|
+
static initialFontNumber = 8; // 8 - how many fonts to load initially
|
|
96
|
+
// Add a static property to track initialization state
|
|
97
|
+
static initializationPromise = null;
|
|
98
|
+
// Add this property
|
|
99
|
+
remainingTime = 0;
|
|
100
|
+
// Add a Map to store pre-initialized observers
|
|
101
|
+
preInitializedObservers = new Map();
|
|
102
|
+
// Add batch size constant
|
|
103
|
+
static BATCH_SIZE = 100;
|
|
104
|
+
currentBatchIndex = FontManager.initialFontNumber; // Start after initial fonts
|
|
105
|
+
// Add a map to track batch indices per controller
|
|
106
|
+
batchIndices = new Map();
|
|
107
|
+
// Add a map to store preloaded fonts per category
|
|
108
|
+
categoryFonts = new Map();
|
|
109
|
+
// Add to existing properties
|
|
110
|
+
customFonts = new Map();
|
|
111
|
+
static missingApiKeyWarned = false;
|
|
112
|
+
static forbiddenApiKeyWarned = false;
|
|
113
|
+
static badRequestApiKeyWarned = false;
|
|
114
|
+
catalogFetchPromise = null;
|
|
115
|
+
hasGoogleFontsApiKey() {
|
|
116
|
+
return Boolean(this.apiKey?.trim());
|
|
117
|
+
}
|
|
118
|
+
warnMissingApiKey() {
|
|
119
|
+
if (FontManager.missingApiKeyWarned)
|
|
120
|
+
return;
|
|
121
|
+
FontManager.missingApiKeyWarned = true;
|
|
122
|
+
console.warn('[FontManager] Google Fonts API key is missing. Set PUBLIC_GOOGLE_FONTS_API_KEY in Frontend/props-editor/.env ' +
|
|
123
|
+
'(or VITE_GOOGLE_FONTS_API_KEY in widget-creator/.env when using the embedded editor), then restart the dev server.');
|
|
124
|
+
}
|
|
125
|
+
warnForbiddenApiKey() {
|
|
126
|
+
if (FontManager.forbiddenApiKeyWarned)
|
|
127
|
+
return;
|
|
128
|
+
FontManager.forbiddenApiKeyWarned = true;
|
|
129
|
+
console.warn('[FontManager] Google Fonts API returned 403 Forbidden. Fix in Google Cloud Console: ' +
|
|
130
|
+
'(1) Enable "Web Fonts Developer API" on the project, ' +
|
|
131
|
+
'(2) API key → Application restrictions → HTTP referrers: http://localhost:5180/*, http://localhost:5174/*, http://127.0.0.1:5180/*, https://*.widgetic.com/*, ' +
|
|
132
|
+
'(3) use a key created for Web Fonts (not Gemini/other APIs). Font picker falls back to cached/custom fonts.');
|
|
133
|
+
}
|
|
134
|
+
warnBadRequestApiKey() {
|
|
135
|
+
if (FontManager.badRequestApiKeyWarned)
|
|
136
|
+
return;
|
|
137
|
+
FontManager.badRequestApiKeyWarned = true;
|
|
138
|
+
console.warn('[FontManager] Google Fonts API returned 400 Bad Request. Usually: invalid API key, Web Fonts Developer API not enabled, or wrong key type. ' +
|
|
139
|
+
'Use the "Widgetic-Fonts" key from Google Cloud (Web Fonts API only), set VITE_GOOGLE_FONTS_API_KEY in widget-creator/.env, restart dev server. ' +
|
|
140
|
+
'Font picker falls back to cached/custom fonts.');
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Fetches the full Google Fonts catalog (Web Fonts Developer API).
|
|
144
|
+
* Returns null when key is missing or the request fails — callers use cache/custom fonts.
|
|
145
|
+
*/
|
|
146
|
+
async fetchGoogleFontsCatalog() {
|
|
147
|
+
if (!this.catalogFetchPromise) {
|
|
148
|
+
this.catalogFetchPromise = this.fetchGoogleFontsCatalogOnce();
|
|
149
|
+
}
|
|
150
|
+
return this.catalogFetchPromise;
|
|
151
|
+
}
|
|
152
|
+
async fetchGoogleFontsCatalogOnce() {
|
|
153
|
+
if (!this.hasGoogleFontsApiKey()) {
|
|
154
|
+
this.warnMissingApiKey();
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
const response = await fetch(`https://www.googleapis.com/webfonts/v1/webfonts?key=${this.apiKey}&sort=popularity&fields=items(family,category,variants)&prettyPrint=false`);
|
|
159
|
+
if (!response.ok) {
|
|
160
|
+
if (response.status === 403) {
|
|
161
|
+
this.warnForbiddenApiKey();
|
|
162
|
+
}
|
|
163
|
+
else if (response.status === 400) {
|
|
164
|
+
this.warnBadRequestApiKey();
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
console.warn('[FontManager] Google Fonts API request failed:', response.status, response.statusText);
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
const data = await response.json();
|
|
172
|
+
const filteredItems = (data.items || []).filter((font) => !shouldSkipFont(font.family));
|
|
173
|
+
return filteredItems.map((font) => ({
|
|
174
|
+
family: font.family,
|
|
175
|
+
category: fontCategories.normalize(font.category) || this.categorizeFont(font.family),
|
|
176
|
+
isCustom: false,
|
|
177
|
+
variants: font.variants,
|
|
178
|
+
}));
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
console.warn('[FontManager] Google Fonts API fetch error:', error);
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/** Variant ids from cached catalog (avoids per-font API calls). */
|
|
186
|
+
getGoogleFontVariantIds(family) {
|
|
187
|
+
const normalized = family.trim().toLowerCase();
|
|
188
|
+
const match = this.catalogFonts.find((f) => f.family.toLowerCase() === normalized) ||
|
|
189
|
+
this.completeFontList.find((f) => f.family.toLowerCase() === normalized);
|
|
190
|
+
return match?.variants?.length ? match.variants : null;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Private constructor to enforce singleton pattern
|
|
194
|
+
* @param apiKey - Google Fonts API key
|
|
195
|
+
*/
|
|
196
|
+
constructor(apiKey) {
|
|
197
|
+
this.apiKey = apiKey; // Initialize apiKey in constructor
|
|
198
|
+
// console.log('🏗️ [FontManager] Creating FontManager instance...');
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Gets or creates the singleton instance and initializes it
|
|
202
|
+
*/
|
|
203
|
+
static async getInstance(apiKey) {
|
|
204
|
+
if (!FontManager.instance) {
|
|
205
|
+
FontManager.instance = new FontManager(apiKey);
|
|
206
|
+
// Initialize only once
|
|
207
|
+
if (!FontManager.initializationPromise) {
|
|
208
|
+
FontManager.initializationPromise = FontManager.instance.initialize();
|
|
209
|
+
}
|
|
210
|
+
// Wait for initialization to complete
|
|
211
|
+
await FontManager.initializationPromise;
|
|
212
|
+
}
|
|
213
|
+
return FontManager.instance;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Initializes the FontManager with initial fonts and setup
|
|
217
|
+
*/
|
|
218
|
+
async initialize() {
|
|
219
|
+
if (FontManager.isInitialized) {
|
|
220
|
+
console.log('✅ [FontManager] Already initialized');
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
// console.log('🚀 [FontManager] Starting initialization...');
|
|
225
|
+
// Load custom fonts first, before any other font operations
|
|
226
|
+
const customFonts = await this.loadCustomFonts();
|
|
227
|
+
// console.log('✅ [FontManager] Loaded custom fonts:', customFonts.length);
|
|
228
|
+
// First check browser's cached fonts
|
|
229
|
+
const cachedFonts = await this.getBrowserCachedFonts();
|
|
230
|
+
// console.log('📦 [FontManager] Browser cached fonts:', {
|
|
231
|
+
// total: cachedFonts.length,
|
|
232
|
+
// fonts: cachedFonts
|
|
233
|
+
// });
|
|
234
|
+
// Add cached fonts to loadedFonts set
|
|
235
|
+
cachedFonts.forEach(font => this.loadedFonts.add(font));
|
|
236
|
+
// Save to localStorage for persistence
|
|
237
|
+
this.saveLoadedFonts();
|
|
238
|
+
// Get recent fonts first
|
|
239
|
+
const recentFonts = fontUtils.getRecentFonts()
|
|
240
|
+
.filter(font => !shouldSkipFont(font.family))
|
|
241
|
+
.map(font => ({
|
|
242
|
+
family: font.family,
|
|
243
|
+
category: fontCategories.normalize(font.category) || this.categorizeFont(font.family)
|
|
244
|
+
}));
|
|
245
|
+
// Get initial font list and cache it
|
|
246
|
+
const initialFontList = await this.getInitialFontList();
|
|
247
|
+
// console.log('📋 [FontManager] Initial font list loaded:', initialFontList.length);
|
|
248
|
+
// Cache the complete font list (skip duplicate fetch if getInitialFontList already loaded catalog)
|
|
249
|
+
try {
|
|
250
|
+
const catalog = this.catalogFonts.length > 0 && this.catalog.stage !== 'empty'
|
|
251
|
+
? this.catalogFonts
|
|
252
|
+
: await this.fetchGoogleFontsCatalog();
|
|
253
|
+
if (catalog?.length) {
|
|
254
|
+
this.catalog = { stage: 'complete', fonts: catalog };
|
|
255
|
+
// Preload initial fonts for each category
|
|
256
|
+
const categories = ['Serif', 'Sans Serif', 'Script', 'Other'];
|
|
257
|
+
// Preload fonts for each category
|
|
258
|
+
await Promise.all(categories.map(async (category) => {
|
|
259
|
+
const categoryFonts = this.catalogFonts
|
|
260
|
+
.filter(font => fontCategories.normalize(font.category) === category)
|
|
261
|
+
.slice(0, FontManager.initialFontNumber);
|
|
262
|
+
this.categoryFonts.set(category, categoryFonts);
|
|
263
|
+
// Preload the fonts by inserting their stylesheets
|
|
264
|
+
await Promise.all(categoryFonts.map(async (font) => {
|
|
265
|
+
try {
|
|
266
|
+
await this.insertFontStylesheet(font.family);
|
|
267
|
+
await this.loadFont(font.family, ['400', '500', '600', '700']);
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
console.warn(`⚠️ [FontManager] Failed to preload font: ${font.family}`, error);
|
|
271
|
+
}
|
|
272
|
+
}));
|
|
273
|
+
// console.log(`📚 [FontManager] Preloaded fonts for ${category}:`, {
|
|
274
|
+
// total: categoryFonts.length,
|
|
275
|
+
// sample: categoryFonts.slice(0, 3).map(f => f.family)
|
|
276
|
+
// });
|
|
277
|
+
}));
|
|
278
|
+
// Save filtered list to localStorage cache
|
|
279
|
+
this.saveFontListToCache(this.catalogFonts);
|
|
280
|
+
// console.log('📚 [FontManager] Complete font list cached:', {
|
|
281
|
+
// total: this.cachedFontList.length,
|
|
282
|
+
// categoriesPreloaded: Array.from(this.categoryFonts.keys())
|
|
283
|
+
// });
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
// console.error('❌ [FontManager] Error caching complete font list:', error);
|
|
288
|
+
}
|
|
289
|
+
// Define initial fonts that must be loaded (excluding skipped fonts)
|
|
290
|
+
const initialFonts = [
|
|
291
|
+
'Roboto', 'Open Sans', 'Montserrat', 'Poppins',
|
|
292
|
+
'Lato', 'Inter'
|
|
293
|
+
].filter(font => !shouldSkipFont(font));
|
|
294
|
+
// Add popular fonts from the initial list
|
|
295
|
+
const popularFonts = initialFontList
|
|
296
|
+
.slice(0, FontManager.initialFontNumber)
|
|
297
|
+
.map(font => font.family)
|
|
298
|
+
.filter(family => !initialFonts.includes(family));
|
|
299
|
+
// Combine all fonts to preload: initial fonts + recent fonts + popular fonts
|
|
300
|
+
const fontsToLoad = [...new Set([
|
|
301
|
+
...initialFonts,
|
|
302
|
+
...recentFonts.map(font => font.family),
|
|
303
|
+
...popularFonts
|
|
304
|
+
])].filter(font => !this.loadedFonts.has(font));
|
|
305
|
+
if (fontsToLoad.length > 0) {
|
|
306
|
+
// console.log('🔄 [FontManager] Preloading fonts:', fontsToLoad);
|
|
307
|
+
// Load all fonts in parallel with all weights
|
|
308
|
+
const loadPromises = fontsToLoad.map(async (family) => {
|
|
309
|
+
try {
|
|
310
|
+
await this.insertFontStylesheet(family);
|
|
311
|
+
await this.loadFont(family, ['400', '500', '600', '700']);
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
console.warn(`⚠️ [FontManager] Failed to load font: ${family}`, error);
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
await Promise.all(loadPromises);
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
console.log('✨ [FontManager] All initial fonts already cached');
|
|
321
|
+
}
|
|
322
|
+
// Only set initialized after everything is complete
|
|
323
|
+
FontManager.isInitialized = true;
|
|
324
|
+
// console.log('✅ [FontManager] Initialization complete. Font status:', {
|
|
325
|
+
// totalLoaded: this.loadedFonts.size,
|
|
326
|
+
// loadedFonts: Array.from(this.loadedFonts),
|
|
327
|
+
// cachedFonts: this.cachedFontList.length
|
|
328
|
+
// });
|
|
329
|
+
// Pre-initialize observers for font controllers
|
|
330
|
+
this.preInitializeObservers();
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
console.error('❌ [FontManager] Initialization failed:', error);
|
|
334
|
+
throw error;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Pre-initializes observers during FontManager initialization
|
|
339
|
+
*/
|
|
340
|
+
preInitializeObservers() {
|
|
341
|
+
// Create a default observer configuration
|
|
342
|
+
const observerConfig = {
|
|
343
|
+
root: null,
|
|
344
|
+
rootMargin: '50px',
|
|
345
|
+
threshold: 0
|
|
346
|
+
};
|
|
347
|
+
// Create and store the observer
|
|
348
|
+
const observer = new IntersectionObserver((entries) => {
|
|
349
|
+
entries.forEach(entry => {
|
|
350
|
+
const fontFamily = entry.target.getAttribute('data-font');
|
|
351
|
+
if (fontFamily) {
|
|
352
|
+
if (entry.isIntersecting) {
|
|
353
|
+
this.addVisibleFont(fontFamily);
|
|
354
|
+
if (!this.isScrolling()) {
|
|
355
|
+
this.loadInitialVisibleFonts();
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
this.removeVisibleFont(fontFamily);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
}, observerConfig);
|
|
364
|
+
// Store it with a default key
|
|
365
|
+
this.preInitializedObservers.set('default', observer);
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Gets a pre-initialized observer
|
|
369
|
+
*/
|
|
370
|
+
getPreInitializedObserver() {
|
|
371
|
+
return this.preInitializedObservers.get('default') || null;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Loads previously cached fonts from the durable L1 store (legacy localStorage fallback)
|
|
375
|
+
*/
|
|
376
|
+
async loadCachedFonts() {
|
|
377
|
+
try {
|
|
378
|
+
let cachedLoadedFonts = await readKvValue(FontManager.LOADED_FONTS_KEY);
|
|
379
|
+
if (!cachedLoadedFonts) {
|
|
380
|
+
const legacyRaw = localStorage.getItem(FontManager.LEGACY_LOADED_FONTS_KEY);
|
|
381
|
+
if (legacyRaw) {
|
|
382
|
+
try {
|
|
383
|
+
cachedLoadedFonts = JSON.parse(legacyRaw);
|
|
384
|
+
await writeKvValue(FontManager.LOADED_FONTS_KEY, cachedLoadedFonts);
|
|
385
|
+
}
|
|
386
|
+
catch {
|
|
387
|
+
/* ignore malformed legacy data */
|
|
388
|
+
}
|
|
389
|
+
localStorage.removeItem(FontManager.LEGACY_LOADED_FONTS_KEY);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (cachedLoadedFonts && Array.isArray(cachedLoadedFonts)) {
|
|
393
|
+
// Only load the first fonts initially
|
|
394
|
+
const initialFonts = cachedLoadedFonts.slice(0, FontManager.initialFontNumber);
|
|
395
|
+
const loadPromises = initialFonts.map(async (font) => {
|
|
396
|
+
const family = typeof font === 'string' ? font : font.family;
|
|
397
|
+
if (family) {
|
|
398
|
+
try {
|
|
399
|
+
await this.loadFont(family, ['400', '500', '600', '700']);
|
|
400
|
+
}
|
|
401
|
+
catch (error) {
|
|
402
|
+
console.warn(`⚠️ [FontManager] Failed to load cached font: ${family}`, error);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
await Promise.all(loadPromises);
|
|
407
|
+
}
|
|
408
|
+
// console.log('📊 [FontManager] Initial cache initialization complete:', {
|
|
409
|
+
// loadedFontsSize: this.loadedFonts.size,
|
|
410
|
+
// loadedFontsList: Array.from(this.loadedFonts)
|
|
411
|
+
// });
|
|
412
|
+
}
|
|
413
|
+
catch (error) {
|
|
414
|
+
console.error('❌ [FontManager] Error loading cached fonts:', error);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Saves the current state of loaded fonts to the durable L1 store
|
|
419
|
+
*/
|
|
420
|
+
saveLoadedFonts() {
|
|
421
|
+
try {
|
|
422
|
+
const fontsToCache = Array.from(this.loadedFonts);
|
|
423
|
+
void writeKvValue(FontManager.LOADED_FONTS_KEY, fontsToCache);
|
|
424
|
+
// Add cache control headers for font files
|
|
425
|
+
const styleSheets = document.querySelectorAll('link[href*="fonts.googleapis.com"]');
|
|
426
|
+
styleSheets.forEach(sheet => {
|
|
427
|
+
if (sheet instanceof HTMLLinkElement) {
|
|
428
|
+
sheet.setAttribute('crossorigin', 'anonymous');
|
|
429
|
+
// Add cache control header through meta tag since we can't directly set headers
|
|
430
|
+
const meta = document.createElement('meta');
|
|
431
|
+
meta.setAttribute('http-equiv', 'Cache-Control');
|
|
432
|
+
meta.setAttribute('content', FontManager.CACHE_CONTROL);
|
|
433
|
+
document.head.appendChild(meta);
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
catch (error) {
|
|
438
|
+
console.error('❌ [FontManager] Error saving loaded fonts:', error);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Gets the stylesheet href for a font family with optimized parameters
|
|
443
|
+
*/
|
|
444
|
+
getFontStylesheetHref(family) {
|
|
445
|
+
try {
|
|
446
|
+
const encodedFamily = encodeURIComponent(String(family).trim());
|
|
447
|
+
// Use text parameter to only load needed characters
|
|
448
|
+
const text = encodeURIComponent('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.,!?@#$%^&*()');
|
|
449
|
+
return `https://fonts.googleapis.com/css2?family=${encodedFamily}:wght@400;500;600;700&display=swap&text=${text}`;
|
|
450
|
+
}
|
|
451
|
+
catch (error) {
|
|
452
|
+
console.error('Error creating font stylesheet href:', error);
|
|
453
|
+
return '';
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Checks if a font is available in Google Fonts
|
|
458
|
+
*/
|
|
459
|
+
async isGoogleFont(family) {
|
|
460
|
+
// Ensure we have the complete font list
|
|
461
|
+
if (this.completeFontList.length === 0) {
|
|
462
|
+
const catalog = await this.fetchGoogleFontsCatalog();
|
|
463
|
+
if (catalog?.length) {
|
|
464
|
+
this.completeFontList = catalog;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return this.completeFontList.some(font => font.family.toLowerCase() === family.toLowerCase());
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Inserts a font stylesheet into the document
|
|
471
|
+
* @param family - Font family name
|
|
472
|
+
* @param subset - Whether to use text subsetting (for preview)
|
|
473
|
+
*/
|
|
474
|
+
async insertFontStylesheet(family, subset = true) {
|
|
475
|
+
try {
|
|
476
|
+
// Skip if already loaded with full character set
|
|
477
|
+
const fullFontLink = document.querySelector(`link[href*="${encodeURIComponent(family)}"][data-full-font="true"]`);
|
|
478
|
+
if (fullFontLink) {
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
// Skip Google Fonts API for custom fonts
|
|
482
|
+
if (this.isCustomFont(family)) {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
// Add preconnect for Google Fonts
|
|
486
|
+
if (!document.querySelector('link[href="https://fonts.gstatic.com"][rel="preconnect"]')) {
|
|
487
|
+
const preconnect = document.createElement('link');
|
|
488
|
+
preconnect.rel = 'preconnect';
|
|
489
|
+
preconnect.href = 'https://fonts.gstatic.com';
|
|
490
|
+
preconnect.crossOrigin = 'anonymous';
|
|
491
|
+
document.head.appendChild(preconnect);
|
|
492
|
+
}
|
|
493
|
+
// Build URL based on whether we want subset or full font
|
|
494
|
+
const baseUrl = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(family)}:wght@400&display=swap`;
|
|
495
|
+
const url = subset
|
|
496
|
+
? `${baseUrl}&text=${encodeURIComponent('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789')}`
|
|
497
|
+
: baseUrl;
|
|
498
|
+
const finalUrl = `${url}&capability=WOFF2`;
|
|
499
|
+
// Create and append stylesheet link first
|
|
500
|
+
const link = document.createElement('link');
|
|
501
|
+
link.rel = 'stylesheet';
|
|
502
|
+
link.href = finalUrl;
|
|
503
|
+
link.crossOrigin = 'anonymous';
|
|
504
|
+
if (!subset) {
|
|
505
|
+
link.setAttribute('data-full-font', 'true');
|
|
506
|
+
}
|
|
507
|
+
// Add to document
|
|
508
|
+
document.head.appendChild(link);
|
|
509
|
+
// Add preload hint with correct attributes after stylesheet
|
|
510
|
+
const preloadLink = document.createElement('link');
|
|
511
|
+
preloadLink.rel = 'preload';
|
|
512
|
+
preloadLink.as = 'style'; // Changed from 'font' to 'style'
|
|
513
|
+
preloadLink.href = finalUrl;
|
|
514
|
+
preloadLink.crossOrigin = 'anonymous';
|
|
515
|
+
preloadLink.setAttribute('onload', "this.onload=null;this.rel='stylesheet'");
|
|
516
|
+
document.head.appendChild(preloadLink);
|
|
517
|
+
// Wait for stylesheet to load
|
|
518
|
+
await new Promise((resolve, reject) => {
|
|
519
|
+
link.onload = () => resolve();
|
|
520
|
+
link.onerror = () => reject(new Error(`Failed to load stylesheet for ${family}`));
|
|
521
|
+
// Add a timeout to prevent hanging
|
|
522
|
+
setTimeout(resolve, 3000);
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
catch (error) {
|
|
526
|
+
console.warn(`⚠️ [FontManager] Error inserting stylesheet for ${family}, continuing anyway:`, error);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Loads a font with full character set
|
|
531
|
+
*/
|
|
532
|
+
async loadFullFont(family) {
|
|
533
|
+
try {
|
|
534
|
+
// Skip if it's a custom font
|
|
535
|
+
if (this.isCustomFont(family)) {
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
// console.log(`📥 [FontManager] Loading full font: ${family}`);
|
|
539
|
+
await this.insertFontStylesheet(family, false);
|
|
540
|
+
// Remove any subset version
|
|
541
|
+
const subsetLink = document.querySelector(`link[href*="${encodeURIComponent(family)}"][data-full-font="false"]`);
|
|
542
|
+
if (subsetLink) {
|
|
543
|
+
subsetLink.remove();
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
catch (error) {
|
|
547
|
+
console.error(`❌ [FontManager] Error loading full font: ${family}`, error);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Checks if a font is in the initial fonts list
|
|
552
|
+
*/
|
|
553
|
+
isInitialFont(family) {
|
|
554
|
+
const initialFonts = ['Roboto', 'Open Sans', 'Montserrat', 'Poppins', 'Lato', 'Inter', 'Material Icons'];
|
|
555
|
+
return initialFonts.includes(family);
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Checks if a font is in the browser cache
|
|
559
|
+
*/
|
|
560
|
+
async checkFontInBrowserCache(family) {
|
|
561
|
+
try {
|
|
562
|
+
const href = this.getFontStylesheetHref(family);
|
|
563
|
+
const response = await fetch(href, {
|
|
564
|
+
method: 'HEAD',
|
|
565
|
+
cache: 'force-cache'
|
|
566
|
+
});
|
|
567
|
+
// Check if response came from browser cache
|
|
568
|
+
const fromCache = response.headers.get('cf-cache-status') === 'HIT' ||
|
|
569
|
+
response.headers.get('x-cache') === 'HIT' ||
|
|
570
|
+
response.headers.get('age') !== null;
|
|
571
|
+
return fromCache;
|
|
572
|
+
}
|
|
573
|
+
catch (error) {
|
|
574
|
+
console.warn('⚠️ [FontManager] Error checking browser cache:', error);
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Maps Google Fonts categories to our simplified category system
|
|
580
|
+
* @param category - The category from Google Fonts API
|
|
581
|
+
* @returns Simplified category name
|
|
582
|
+
*/
|
|
583
|
+
mapCategory(category) {
|
|
584
|
+
switch (category?.toLowerCase()) {
|
|
585
|
+
case 'serif':
|
|
586
|
+
return 'Serif';
|
|
587
|
+
case 'sans-serif':
|
|
588
|
+
return 'Sans Serif';
|
|
589
|
+
case 'handwriting':
|
|
590
|
+
case 'display':
|
|
591
|
+
return 'Script';
|
|
592
|
+
case 'monospace':
|
|
593
|
+
default:
|
|
594
|
+
return 'Other';
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Ensures fonts are sorted by popularity
|
|
599
|
+
*/
|
|
600
|
+
ensureFontSorting(fonts) {
|
|
601
|
+
// Keep original order from Google Fonts API as it's already sorted by popularity
|
|
602
|
+
return fonts;
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Gets initial font list (initial number of fonts + recent fonts)
|
|
606
|
+
* @returns Promise resolving to initial font list
|
|
607
|
+
*/
|
|
608
|
+
async getInitialFontList() {
|
|
609
|
+
try {
|
|
610
|
+
if (this.catalog.stage !== 'empty') {
|
|
611
|
+
return this.catalogFonts;
|
|
612
|
+
}
|
|
613
|
+
// Load recent fonts first (keep existing code)
|
|
614
|
+
const recentFonts = fontUtils.getRecentFonts()
|
|
615
|
+
.filter(font => !shouldSkipFont(font.family))
|
|
616
|
+
.map(font => ({
|
|
617
|
+
family: font.family,
|
|
618
|
+
category: fontCategories.normalize(font.category),
|
|
619
|
+
isCustom: font.isCustom
|
|
620
|
+
}));
|
|
621
|
+
// Add custom fonts from fonts.json
|
|
622
|
+
const customFonts = await this.loadCustomFonts();
|
|
623
|
+
const catalog = await this.fetchGoogleFontsCatalog();
|
|
624
|
+
const googleFonts = catalog || [];
|
|
625
|
+
// Combine all fonts. A full Google catalog means the list is already
|
|
626
|
+
// complete; otherwise keep the 'initial' stage so a later fetch can
|
|
627
|
+
// still upgrade it.
|
|
628
|
+
const fonts = [
|
|
629
|
+
...recentFonts,
|
|
630
|
+
...customFonts,
|
|
631
|
+
...googleFonts.filter((font) => !recentFonts.some((recent) => recent.family === font.family) &&
|
|
632
|
+
!customFonts.some((custom) => custom.family === font.family))
|
|
633
|
+
];
|
|
634
|
+
this.catalog = catalog?.length
|
|
635
|
+
? { stage: 'complete', fonts }
|
|
636
|
+
: { stage: 'initial', fonts };
|
|
637
|
+
return this.catalogFonts;
|
|
638
|
+
}
|
|
639
|
+
catch (error) {
|
|
640
|
+
console.error('Error getting initial fonts:', error);
|
|
641
|
+
return [];
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Saves font list to the durable L1 cache.
|
|
646
|
+
* Only 'complete' catalogs are persisted — a partial ('initial') catalog
|
|
647
|
+
* written here used to be served for the full 24h cache duration.
|
|
648
|
+
*/
|
|
649
|
+
saveFontListToCache(fonts) {
|
|
650
|
+
try {
|
|
651
|
+
if (this.catalog.stage !== 'complete') {
|
|
652
|
+
console.warn('⚠️ [FontManager] Skipping cache write: catalog is not complete yet');
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
const cacheData = {
|
|
656
|
+
timestamp: Date.now(),
|
|
657
|
+
fonts: fonts
|
|
658
|
+
};
|
|
659
|
+
void writeKvValue(FontManager.CACHE_KEY, cacheData);
|
|
660
|
+
}
|
|
661
|
+
catch (error) {
|
|
662
|
+
console.error('❌ [FontManager] Error caching font list:', error);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Gets font list from the durable L1 cache (legacy localStorage fallback)
|
|
667
|
+
*/
|
|
668
|
+
async getFontListFromCache() {
|
|
669
|
+
try {
|
|
670
|
+
let cache = await readKvValue(FontManager.CACHE_KEY, FontManager.CACHE_DURATION);
|
|
671
|
+
if (!cache) {
|
|
672
|
+
const legacyRaw = localStorage.getItem(FontManager.LEGACY_CACHE_KEY);
|
|
673
|
+
if (legacyRaw) {
|
|
674
|
+
try {
|
|
675
|
+
cache = JSON.parse(legacyRaw);
|
|
676
|
+
}
|
|
677
|
+
catch {
|
|
678
|
+
/* ignore malformed legacy data */
|
|
679
|
+
}
|
|
680
|
+
localStorage.removeItem(FontManager.LEGACY_CACHE_KEY);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
if (!cache)
|
|
684
|
+
return null;
|
|
685
|
+
// Check if cache is expired
|
|
686
|
+
if (Date.now() - cache.timestamp > FontManager.CACHE_DURATION) {
|
|
687
|
+
console.log('⚠️ [FontManager] Cache expired, removing');
|
|
688
|
+
void deleteKvValue(FontManager.CACHE_KEY);
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
// console.log('📦 [FontManager] Using cached font list:', {
|
|
692
|
+
// fonts: cache.fonts.length,
|
|
693
|
+
// age: Math.round((now - cache.timestamp) / 1000 / 60) + ' minutes'
|
|
694
|
+
// });
|
|
695
|
+
return cache.fonts;
|
|
696
|
+
}
|
|
697
|
+
catch (error) {
|
|
698
|
+
console.error('❌ [FontManager] Error reading font cache:', error);
|
|
699
|
+
return null;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Gets complete font list (called when dropdown opens)
|
|
704
|
+
*/
|
|
705
|
+
async getCompleteFontList(force = false) {
|
|
706
|
+
try {
|
|
707
|
+
if (this.catalog.stage === 'complete' && !force) {
|
|
708
|
+
return this.catalogFonts;
|
|
709
|
+
}
|
|
710
|
+
if (!force) {
|
|
711
|
+
const cachedFonts = await this.getFontListFromCache();
|
|
712
|
+
if (cachedFonts) {
|
|
713
|
+
this.catalog = { stage: 'complete', fonts: cachedFonts };
|
|
714
|
+
return cachedFonts;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
this.fontListPromise = (async () => {
|
|
718
|
+
const catalog = await this.fetchGoogleFontsCatalog();
|
|
719
|
+
if (!catalog?.length) {
|
|
720
|
+
return this.catalogFonts.length ? this.catalogFonts : [];
|
|
721
|
+
}
|
|
722
|
+
const allFonts = catalog;
|
|
723
|
+
this.catalog = { stage: 'complete', fonts: allFonts };
|
|
724
|
+
this.saveFontListToCache(allFonts);
|
|
725
|
+
// console.log('✅ [FontManager] Complete font list loaded:', {
|
|
726
|
+
// total: allFonts.length,
|
|
727
|
+
// filtered: data.items.length - allFonts.length,
|
|
728
|
+
// format: 'WOFF2'
|
|
729
|
+
// });
|
|
730
|
+
return allFonts;
|
|
731
|
+
})();
|
|
732
|
+
return this.fontListPromise;
|
|
733
|
+
}
|
|
734
|
+
catch (error) {
|
|
735
|
+
console.error('❌ [FontManager] Error getting complete font list:', error);
|
|
736
|
+
return this.catalogFonts;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Preloads initial fonts with optimized loading strategy
|
|
741
|
+
* Returns existing promise if preload is in progress
|
|
742
|
+
*/
|
|
743
|
+
async preloadInitialFonts() {
|
|
744
|
+
// Return existing promise if preload is in progress
|
|
745
|
+
if (FontManager.preloadPromise) {
|
|
746
|
+
return FontManager.preloadPromise;
|
|
747
|
+
}
|
|
748
|
+
// Return immediately if preload is already complete
|
|
749
|
+
if (FontManager.initialPreloadComplete) {
|
|
750
|
+
console.log('✅ [FontManager] Initial fonts already preloaded');
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
// Create new preload promise
|
|
754
|
+
FontManager.preloadPromise = (async () => {
|
|
755
|
+
try {
|
|
756
|
+
console.log('🚀 [FontManager] Preloading initial fonts...');
|
|
757
|
+
const initialFonts = await this.getInitialFontList();
|
|
758
|
+
const loadPromises = initialFonts.map(font => {
|
|
759
|
+
this.insertFontStylesheet(font.family);
|
|
760
|
+
return this.loadFont(font.family);
|
|
761
|
+
});
|
|
762
|
+
await Promise.all(loadPromises);
|
|
763
|
+
FontManager.initialPreloadComplete = true;
|
|
764
|
+
console.log('✅ [FontManager] Initial fonts preloaded');
|
|
765
|
+
}
|
|
766
|
+
catch (error) {
|
|
767
|
+
console.error('❌ [FontManager] Error preloading initial fonts:', error);
|
|
768
|
+
}
|
|
769
|
+
finally {
|
|
770
|
+
FontManager.preloadPromise = null;
|
|
771
|
+
}
|
|
772
|
+
})();
|
|
773
|
+
return FontManager.preloadPromise;
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Loads initial visible fonts
|
|
777
|
+
*/
|
|
778
|
+
async loadInitialVisibleFonts() {
|
|
779
|
+
const visibleFonts = Array.from(this.visibleFonts);
|
|
780
|
+
// Get initial fonts list
|
|
781
|
+
const initialFonts = this.catalogFonts.slice(0, FontManager.initialFontNumber);
|
|
782
|
+
const initialFontFamilies = new Set(initialFonts.map(font => font.family));
|
|
783
|
+
// Filter visible fonts to only include those in the initial batch
|
|
784
|
+
const fontsToLoad = visibleFonts.filter(font => initialFontFamilies.has(font));
|
|
785
|
+
if (fontsToLoad.length > 0) {
|
|
786
|
+
// console.log('🔄 [FontManager] Loading initial visible fonts:', {
|
|
787
|
+
// visible: visibleFonts.length,
|
|
788
|
+
// toLoad: fontsToLoad.length,
|
|
789
|
+
// initialBatchSize: initialFonts.length
|
|
790
|
+
// });
|
|
791
|
+
const loadPromises = fontsToLoad.map(async (family) => {
|
|
792
|
+
try {
|
|
793
|
+
await this.loadFont(family, ['400', '500', '600', '700']);
|
|
794
|
+
}
|
|
795
|
+
catch (error) {
|
|
796
|
+
console.warn(`⚠️ [FontManager] Failed to load font: ${family}`, error);
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
await Promise.all(loadPromises);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Creates or retrieves an intersection observer for a container
|
|
804
|
+
* @param container - The HTML element to observe
|
|
805
|
+
* @returns IntersectionObserver instance or null if container is invalid
|
|
806
|
+
*/
|
|
807
|
+
getObserver(container) {
|
|
808
|
+
if (!container) {
|
|
809
|
+
console.warn('❌ [FontManager] No container provided for observer');
|
|
810
|
+
return null;
|
|
811
|
+
}
|
|
812
|
+
// Generate or retrieve unique ID for container
|
|
813
|
+
const key = container.dataset.observerId || `font-observer-${Math.random().toString(36).slice(2)}`;
|
|
814
|
+
container.dataset.observerId = key;
|
|
815
|
+
// Return existing observer if one exists
|
|
816
|
+
if (this.observers.has(key)) {
|
|
817
|
+
return this.observers.get(key) || null;
|
|
818
|
+
}
|
|
819
|
+
// Create new observer
|
|
820
|
+
// console.log('🔧 [FontManager] Creating new observer for container:', key);
|
|
821
|
+
const observer = new IntersectionObserver((entries) => {
|
|
822
|
+
entries.forEach(entry => {
|
|
823
|
+
const fontFamily = entry.target.getAttribute('data-font');
|
|
824
|
+
if (fontFamily) {
|
|
825
|
+
if (entry.isIntersecting) {
|
|
826
|
+
this.addVisibleFont(fontFamily);
|
|
827
|
+
// Load initial fonts immediately when they become visible
|
|
828
|
+
if (!this.isScrolling()) {
|
|
829
|
+
this.loadInitialVisibleFonts();
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
else {
|
|
833
|
+
this.removeVisibleFont(fontFamily);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
}, {
|
|
838
|
+
root: container,
|
|
839
|
+
rootMargin: '50px' // Pre-load fonts slightly before they come into view
|
|
840
|
+
});
|
|
841
|
+
this.observers.set(key, observer);
|
|
842
|
+
return observer;
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Gets the font sources for a given family, including fallbacks
|
|
846
|
+
* @param fontFamily - Font family name
|
|
847
|
+
* @returns CSS src string with font sources
|
|
848
|
+
*/
|
|
849
|
+
getFontSources(fontFamily) {
|
|
850
|
+
try {
|
|
851
|
+
const encodedFamily = encodeURIComponent(fontFamily);
|
|
852
|
+
// Use Google Fonts CSS API instead of direct file access
|
|
853
|
+
return `url(https://fonts.googleapis.com/css2?family=${encodedFamily}&display=swap)`;
|
|
854
|
+
}
|
|
855
|
+
catch (error) {
|
|
856
|
+
console.error('Error generating font sources:', error);
|
|
857
|
+
return '';
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Generates a complete @font-face declaration
|
|
862
|
+
* @param fontFamily - Font family name
|
|
863
|
+
* @param options - Font face configuration options
|
|
864
|
+
* @returns Complete @font-face CSS rule
|
|
865
|
+
*/
|
|
866
|
+
generateFontFaceRule(fontFamily, options = {}) {
|
|
867
|
+
const weight = options.weight || 'normal';
|
|
868
|
+
const style = options.style || 'normal';
|
|
869
|
+
return `
|
|
870
|
+
@font-face {
|
|
871
|
+
font-family: '${fontFamily}';
|
|
872
|
+
src: ${this.getFontSources(fontFamily)};
|
|
873
|
+
font-weight: ${weight};
|
|
874
|
+
font-style: ${style};
|
|
875
|
+
font-display: swap;
|
|
876
|
+
}
|
|
877
|
+
`;
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Loads a font with specified weights using FontFaceObserver
|
|
881
|
+
* Simplified version with better error handling
|
|
882
|
+
*/
|
|
883
|
+
async loadFont(family, weights = ['400']) {
|
|
884
|
+
if (this.loadedFonts.has(family)) {
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
try {
|
|
888
|
+
// First ensure the stylesheet is loaded
|
|
889
|
+
await this.insertFontStylesheet(family);
|
|
890
|
+
// Wait a bit for stylesheet to be properly processed
|
|
891
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
892
|
+
// Load only 400 weight initially for faster loading
|
|
893
|
+
const observer = new FontFaceObserver(family);
|
|
894
|
+
await observer.load(null, 3000); // Reduced timeout
|
|
895
|
+
// Mark as loaded after basic weight is loaded
|
|
896
|
+
this.loadedFonts.add(family);
|
|
897
|
+
this.saveLoadedFonts();
|
|
898
|
+
// Load additional weights in the background
|
|
899
|
+
if (weights.length > 1) {
|
|
900
|
+
Promise.all(weights
|
|
901
|
+
.filter(weight => weight !== '400')
|
|
902
|
+
.map(weight => {
|
|
903
|
+
const observer = new FontFaceObserver(family, { weight });
|
|
904
|
+
return observer.load(null, 3000).catch(err => {
|
|
905
|
+
console.warn(`⚠️ [FontManager] Non-critical weight ${weight} failed for ${family}:`, err);
|
|
906
|
+
return false;
|
|
907
|
+
});
|
|
908
|
+
})).then(results => {
|
|
909
|
+
const loadedCount = results.filter(Boolean).length;
|
|
910
|
+
// console.log(`📦 [FontManager] Additional weights loaded for ${family}:`, loadedCount);
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
// console.log('✅ [FontManager] Font loaded successfully:', family);
|
|
914
|
+
}
|
|
915
|
+
catch (error) {
|
|
916
|
+
console.warn(`⚠️ [FontManager] Error loading font ${family}, falling back to system fonts:`, error);
|
|
917
|
+
// Still mark as loaded to prevent retries
|
|
918
|
+
this.loadedFonts.add(family);
|
|
919
|
+
this.saveLoadedFonts();
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Handles scroll optimization for font loading
|
|
924
|
+
* Implements debouncing to prevent excessive font loading
|
|
925
|
+
* @param callback - Optional callback after fonts are loaded
|
|
926
|
+
*/
|
|
927
|
+
handleScroll(callback) {
|
|
928
|
+
if (this.scrollTimeout) {
|
|
929
|
+
window.clearTimeout(this.scrollTimeout);
|
|
930
|
+
this.scrollTimeout = null;
|
|
931
|
+
}
|
|
932
|
+
this.remainingTime = FontManager.WAIT_TIME;
|
|
933
|
+
this.scrollTimeout = window.setTimeout(async () => {
|
|
934
|
+
const visibleFonts = Array.from(this.visibleFonts);
|
|
935
|
+
// console.log('🔄 [FontManager] Loading visible fonts:', {
|
|
936
|
+
// total: visibleFonts.length,
|
|
937
|
+
// fonts: visibleFonts
|
|
938
|
+
// });
|
|
939
|
+
try {
|
|
940
|
+
// Load all visible fonts in parallel
|
|
941
|
+
const loadPromises = visibleFonts.map(async (family) => {
|
|
942
|
+
if (this.loadedFonts.has(family)) {
|
|
943
|
+
const status = await this.verifyFontCache(family);
|
|
944
|
+
if (!status.cached) {
|
|
945
|
+
// console.log('⚠️ [FontManager] Reloading uncached font:', family);
|
|
946
|
+
await this.insertFontStylesheet(family);
|
|
947
|
+
return this.loadFont(family);
|
|
948
|
+
}
|
|
949
|
+
return Promise.resolve();
|
|
950
|
+
}
|
|
951
|
+
// console.log('📥 [FontManager] Loading new font:', family);
|
|
952
|
+
await this.insertFontStylesheet(family);
|
|
953
|
+
return this.loadFont(family);
|
|
954
|
+
});
|
|
955
|
+
await Promise.all(loadPromises);
|
|
956
|
+
// Log final cache status
|
|
957
|
+
const finalStatus = await Promise.all(visibleFonts.map(async (family) => ({
|
|
958
|
+
font: family,
|
|
959
|
+
status: await this.verifyFontCache(family)
|
|
960
|
+
})));
|
|
961
|
+
// console.log('✅ [FontManager] Font loading complete:', {
|
|
962
|
+
// total: finalStatus.length,
|
|
963
|
+
// fonts: finalStatus.map(({ font, status }) => ({
|
|
964
|
+
// font,
|
|
965
|
+
// cached: status.cached,
|
|
966
|
+
// stylesheet: status.stylesheet,
|
|
967
|
+
// fontFace: status.fontFace
|
|
968
|
+
// }))
|
|
969
|
+
// });
|
|
970
|
+
}
|
|
971
|
+
catch (error) {
|
|
972
|
+
console.error('❌ [FontManager] Error loading fonts:', error);
|
|
973
|
+
}
|
|
974
|
+
this.visibleFonts.clear();
|
|
975
|
+
this.scrollTimeout = null;
|
|
976
|
+
if (callback)
|
|
977
|
+
callback();
|
|
978
|
+
}, FontManager.WAIT_TIME);
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Checks if a font is already loaded
|
|
982
|
+
*/
|
|
983
|
+
isFontLoaded(family) {
|
|
984
|
+
return this.loadedFonts.has(family);
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
987
|
+
* Gets stats about loaded fonts
|
|
988
|
+
*/
|
|
989
|
+
getLoadedFontsStats() {
|
|
990
|
+
return {
|
|
991
|
+
total: this.loadedFonts.size,
|
|
992
|
+
fonts: Array.from(this.loadedFonts)
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* Cleans up resources when a font container is removed
|
|
997
|
+
* Prevents memory leaks from orphaned observers
|
|
998
|
+
* @param containerId - ID of the container being cleaned up
|
|
999
|
+
*/
|
|
1000
|
+
cleanup(containerId) {
|
|
1001
|
+
const observer = this.observers.get(containerId);
|
|
1002
|
+
if (observer) {
|
|
1003
|
+
observer.disconnect();
|
|
1004
|
+
this.observers.delete(containerId);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
/**
|
|
1008
|
+
* Categorizes a font based on its family name
|
|
1009
|
+
* Delegates to fontUtils for consistent categorization
|
|
1010
|
+
* @param family - Font family name to categorize
|
|
1011
|
+
*/
|
|
1012
|
+
categorizeFont(family) {
|
|
1013
|
+
return fontUtils.categorizeFont(family);
|
|
1014
|
+
}
|
|
1015
|
+
// Add public method to check scroll state
|
|
1016
|
+
isScrolling() {
|
|
1017
|
+
return this.scrollTimeout !== null;
|
|
1018
|
+
}
|
|
1019
|
+
// Method to track fonts that become visible
|
|
1020
|
+
addVisibleFont(fontFamily) {
|
|
1021
|
+
if (!this.visibleFonts.has(fontFamily)) {
|
|
1022
|
+
this.visibleFonts.add(fontFamily);
|
|
1023
|
+
// console.log(`👁️ [FontManager] Font visible: ${fontFamily}`);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
removeVisibleFont(family) {
|
|
1027
|
+
if (this.visibleFonts.has(family)) {
|
|
1028
|
+
this.visibleFonts.delete(family);
|
|
1029
|
+
// console.log('👋 [FontManager] Font hidden:', family);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Verifies if a font is actually cached in the browser
|
|
1034
|
+
*/
|
|
1035
|
+
async verifyFontCache(family) {
|
|
1036
|
+
const status = {
|
|
1037
|
+
family,
|
|
1038
|
+
cached: false,
|
|
1039
|
+
headers: {},
|
|
1040
|
+
stylesheet: false,
|
|
1041
|
+
fontFace: false
|
|
1042
|
+
};
|
|
1043
|
+
try {
|
|
1044
|
+
// Check the durable loaded-fonts registry (legacy localStorage fallback)
|
|
1045
|
+
let cachedFonts = await readKvValue(FontManager.LOADED_FONTS_KEY);
|
|
1046
|
+
if (!cachedFonts) {
|
|
1047
|
+
const legacyRaw = localStorage.getItem(FontManager.LEGACY_LOADED_FONTS_KEY);
|
|
1048
|
+
cachedFonts = legacyRaw ? JSON.parse(legacyRaw) : [];
|
|
1049
|
+
}
|
|
1050
|
+
const loadedFonts = Array.isArray(cachedFonts) ? cachedFonts : [];
|
|
1051
|
+
const isCached = loadedFonts.includes(family);
|
|
1052
|
+
// Check if stylesheet exists in DOM
|
|
1053
|
+
status.stylesheet = !!document.querySelector(`link[href*="${encodeURIComponent(family)}"]`);
|
|
1054
|
+
// Consider font loaded if it's both in localStorage and has a stylesheet
|
|
1055
|
+
status.fontFace = isCached;
|
|
1056
|
+
status.cached = status.stylesheet && status.fontFace;
|
|
1057
|
+
// Set standard cache headers
|
|
1058
|
+
status.headers = {
|
|
1059
|
+
cacheControl: 'max-age=31536000', // 1 year
|
|
1060
|
+
age: '0',
|
|
1061
|
+
cfCache: null,
|
|
1062
|
+
xCache: null
|
|
1063
|
+
};
|
|
1064
|
+
return status;
|
|
1065
|
+
}
|
|
1066
|
+
catch (error) {
|
|
1067
|
+
console.warn('⚠️ [FontManager] Error verifying font cache:', family, error);
|
|
1068
|
+
return status;
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Gets list of fonts cached in browser with detailed status
|
|
1073
|
+
*/
|
|
1074
|
+
async getBrowserCachedFonts() {
|
|
1075
|
+
try {
|
|
1076
|
+
// Get fonts from the durable loaded-fonts registry (legacy fallback)
|
|
1077
|
+
let cachedFonts = await readKvValue(FontManager.LOADED_FONTS_KEY);
|
|
1078
|
+
if (!cachedFonts) {
|
|
1079
|
+
const legacyRaw = localStorage.getItem(FontManager.LEGACY_LOADED_FONTS_KEY);
|
|
1080
|
+
cachedFonts = legacyRaw ? JSON.parse(legacyRaw) : [];
|
|
1081
|
+
}
|
|
1082
|
+
cachedFonts = Array.isArray(cachedFonts) ? cachedFonts : [];
|
|
1083
|
+
// console.log('📦 [FontManager] Fonts in durable registry:', cachedFonts);
|
|
1084
|
+
// Verify each font has its stylesheet in the DOM
|
|
1085
|
+
const verifiedFonts = await Promise.all(cachedFonts.map(async (font) => {
|
|
1086
|
+
const status = await this.verifyFontCache(font);
|
|
1087
|
+
return { font, status };
|
|
1088
|
+
}));
|
|
1089
|
+
// Filter to only include fonts that are properly cached
|
|
1090
|
+
const validCachedFonts = verifiedFonts
|
|
1091
|
+
.filter(({ status }) => status.cached)
|
|
1092
|
+
.map(({ font }) => font);
|
|
1093
|
+
// console.log('✅ [FontManager] Valid cached fonts:', {
|
|
1094
|
+
// total: validCachedFonts.length,
|
|
1095
|
+
// fonts: validCachedFonts
|
|
1096
|
+
// });
|
|
1097
|
+
return validCachedFonts;
|
|
1098
|
+
}
|
|
1099
|
+
catch (error) {
|
|
1100
|
+
console.error('❌ [FontManager] Error getting browser cached fonts:', error);
|
|
1101
|
+
return [];
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
/**
|
|
1105
|
+
* Gets the cached font list without making an API call
|
|
1106
|
+
*/
|
|
1107
|
+
getCachedFontList() {
|
|
1108
|
+
return this.catalogFonts;
|
|
1109
|
+
}
|
|
1110
|
+
/**
|
|
1111
|
+
* Gets the next batch of fonts
|
|
1112
|
+
*/
|
|
1113
|
+
getNextBatch(controllerId, category) {
|
|
1114
|
+
// Get or initialize batch index for this controller
|
|
1115
|
+
let currentIndex = this.batchIndices.get(controllerId) || FontManager.initialFontNumber;
|
|
1116
|
+
// Filter fonts by category first
|
|
1117
|
+
const categoryFonts = category === 'All'
|
|
1118
|
+
? this.catalogFonts
|
|
1119
|
+
: category === 'Mine'
|
|
1120
|
+
? Array.from(this.customFonts.values())
|
|
1121
|
+
: this.catalogFonts.filter(font => fontCategories.normalize(font.category) === category);
|
|
1122
|
+
const start = currentIndex;
|
|
1123
|
+
const end = start + FontManager.BATCH_SIZE;
|
|
1124
|
+
// Get next batch from category-filtered list
|
|
1125
|
+
const batch = categoryFonts.slice(start, end);
|
|
1126
|
+
// Only update index if we actually got more fonts
|
|
1127
|
+
if (batch.length > 0) {
|
|
1128
|
+
this.batchIndices.set(controllerId, end);
|
|
1129
|
+
}
|
|
1130
|
+
// console.log('📦 [FontManager] Loading next batch:', {
|
|
1131
|
+
// controllerId,
|
|
1132
|
+
// category,
|
|
1133
|
+
// start,
|
|
1134
|
+
// end,
|
|
1135
|
+
// batchSize: batch.length,
|
|
1136
|
+
// totalInCategory: categoryFonts.length,
|
|
1137
|
+
// remainingFonts: categoryFonts.length - end
|
|
1138
|
+
// });
|
|
1139
|
+
return batch;
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Checks if there are more fonts to load
|
|
1143
|
+
*/
|
|
1144
|
+
hasMoreFonts(controllerId, category) {
|
|
1145
|
+
const currentIndex = this.batchIndices.get(controllerId) || FontManager.initialFontNumber;
|
|
1146
|
+
// Filter fonts by category first
|
|
1147
|
+
const categoryFonts = category === 'All'
|
|
1148
|
+
? this.catalogFonts
|
|
1149
|
+
: category === 'Mine'
|
|
1150
|
+
? Array.from(this.customFonts.values())
|
|
1151
|
+
: this.catalogFonts.filter(font => fontCategories.normalize(font.category) === category);
|
|
1152
|
+
const remainingItems = categoryFonts.length - currentIndex;
|
|
1153
|
+
console.log('🔍 [FontManager] Checking remaining fonts:', {
|
|
1154
|
+
category,
|
|
1155
|
+
currentIndex,
|
|
1156
|
+
totalInCategory: categoryFonts.length,
|
|
1157
|
+
remainingItems
|
|
1158
|
+
});
|
|
1159
|
+
// Only return true if we have more items in this category
|
|
1160
|
+
return remainingItems > 0;
|
|
1161
|
+
}
|
|
1162
|
+
/**
|
|
1163
|
+
* Resets the batch index to initial number
|
|
1164
|
+
* This allows each font input controller to start from the beginning
|
|
1165
|
+
*/
|
|
1166
|
+
resetBatchIndex(controllerId) {
|
|
1167
|
+
this.batchIndices.set(controllerId, FontManager.initialFontNumber);
|
|
1168
|
+
// console.log('🔄 [FontManager] Reset batch index for:', controllerId);
|
|
1169
|
+
}
|
|
1170
|
+
// Add method to get preloaded fonts for a category
|
|
1171
|
+
getPreloadedFonts(category) {
|
|
1172
|
+
if (category === 'Mine') {
|
|
1173
|
+
return Array.from(this.customFonts.values());
|
|
1174
|
+
}
|
|
1175
|
+
const preloadedFonts = this.categoryFonts.get(category) || [];
|
|
1176
|
+
// Ensure fonts are loaded
|
|
1177
|
+
preloadedFonts.forEach(font => {
|
|
1178
|
+
if (!this.loadedFonts.has(font.family)) {
|
|
1179
|
+
this.insertFontStylesheet(font.family)
|
|
1180
|
+
.then(() => this.loadFont(font.family, ['400']))
|
|
1181
|
+
.catch(error => console.warn(`⚠️ [FontManager] Failed to load font: ${font.family}`, error));
|
|
1182
|
+
}
|
|
1183
|
+
});
|
|
1184
|
+
return preloadedFonts;
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* Gets the complete list of Google Fonts
|
|
1188
|
+
* @returns Promise resolving to FontData array
|
|
1189
|
+
*/
|
|
1190
|
+
async getGoogleFontsList() {
|
|
1191
|
+
const catalog = await this.fetchGoogleFontsCatalog();
|
|
1192
|
+
return catalog || [];
|
|
1193
|
+
}
|
|
1194
|
+
// Add new method to handle custom font removal
|
|
1195
|
+
async removeCustomFont(family) {
|
|
1196
|
+
try {
|
|
1197
|
+
if (this.customFonts.has(family)) {
|
|
1198
|
+
this.customFonts.delete(family);
|
|
1199
|
+
this.loadedFonts.delete(family);
|
|
1200
|
+
// Update cached font list
|
|
1201
|
+
if (this.catalog.stage !== 'empty') {
|
|
1202
|
+
this.catalog = {
|
|
1203
|
+
...this.catalog,
|
|
1204
|
+
fonts: this.catalog.fonts.filter(font => font.family !== family || font.category !== 'Mine'),
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
console.log('✅ [FontManager] Custom font removed:', family);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
catch (error) {
|
|
1211
|
+
console.error('❌ [FontManager] Error removing custom font:', error);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
async loadCustomFonts() {
|
|
1215
|
+
try {
|
|
1216
|
+
const fontsData = fontsJson;
|
|
1217
|
+
const families = Array.isArray(fontsData.fontFamilies) ? fontsData.fontFamilies : [];
|
|
1218
|
+
const customFonts = [];
|
|
1219
|
+
for (const familyData of families) {
|
|
1220
|
+
try {
|
|
1221
|
+
const fontData = {
|
|
1222
|
+
family: familyData.family,
|
|
1223
|
+
category: 'Mine',
|
|
1224
|
+
isCustom: true
|
|
1225
|
+
};
|
|
1226
|
+
this.customFonts.set(familyData.family, fontData);
|
|
1227
|
+
customFonts.push(fontData);
|
|
1228
|
+
// Process each variant
|
|
1229
|
+
for (const variant of familyData.variants || []) {
|
|
1230
|
+
try {
|
|
1231
|
+
const isItalic = variant.weight.includes('Italic');
|
|
1232
|
+
// Skip local/relative font files — they only exist in standalone mode
|
|
1233
|
+
const fontUrl = variant.url;
|
|
1234
|
+
if (fontUrl.startsWith('/') && !fontUrl.startsWith('//')) {
|
|
1235
|
+
continue;
|
|
1236
|
+
}
|
|
1237
|
+
const fontFace = new FontFace(familyData.family, `url(${fontUrl})`, {
|
|
1238
|
+
style: isItalic ? 'italic' : 'normal',
|
|
1239
|
+
display: 'swap'
|
|
1240
|
+
});
|
|
1241
|
+
await document.fonts.add(fontFace);
|
|
1242
|
+
await fontFace.load();
|
|
1243
|
+
this.loadedFonts.add(familyData.family);
|
|
1244
|
+
// console.log(`✅ [FontManager] Added font face with swap:`, {
|
|
1245
|
+
// family: familyData.family,
|
|
1246
|
+
// weight: variant.weight,
|
|
1247
|
+
// style: isItalic ? 'italic' : 'normal'
|
|
1248
|
+
// });
|
|
1249
|
+
}
|
|
1250
|
+
catch (error) {
|
|
1251
|
+
console.warn(`⚠️ [FontManager] Failed to add font face:`, {
|
|
1252
|
+
family: familyData.family,
|
|
1253
|
+
weight: variant.weight,
|
|
1254
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
catch (error) {
|
|
1260
|
+
console.warn(`⚠️ [FontManager] Failed to load font family: ${familyData.family}`, error);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
return customFonts;
|
|
1264
|
+
}
|
|
1265
|
+
catch (error) {
|
|
1266
|
+
console.error('❌ [FontManager] Error loading custom fonts:', error);
|
|
1267
|
+
return [];
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Checks if a font is a custom font
|
|
1272
|
+
*/
|
|
1273
|
+
isCustomFont(family) {
|
|
1274
|
+
return this.customFonts.has(family);
|
|
1275
|
+
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Gets custom font data if available
|
|
1278
|
+
*/
|
|
1279
|
+
getCustomFont(family) {
|
|
1280
|
+
return this.customFonts.get(family);
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
export default FontManager;
|