@flamingo-stack/openframe-frontend-core 0.0.204 → 0.0.205
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-4CWSZPXH.cjs → chunk-24KCAECR.cjs} +9 -9
- package/dist/{chunk-4CWSZPXH.cjs.map → chunk-24KCAECR.cjs.map} +1 -1
- package/dist/chunk-27APPAJN.cjs +24 -0
- package/dist/chunk-27APPAJN.cjs.map +1 -0
- package/dist/{chunk-UC43NICZ.cjs → chunk-664KA5FI.cjs} +2 -35
- package/dist/chunk-664KA5FI.cjs.map +1 -0
- package/dist/chunk-6RZYJICV.cjs +24 -0
- package/dist/chunk-6RZYJICV.cjs.map +1 -0
- package/dist/chunk-7L4DWM7P.js +24 -0
- package/dist/chunk-7L4DWM7P.js.map +1 -0
- package/dist/chunk-BZFW3FOF.cjs +21 -0
- package/dist/chunk-BZFW3FOF.cjs.map +1 -0
- package/dist/{chunk-N57KWHDB.js → chunk-CIPO6DXK.js} +5 -5
- package/dist/chunk-EL5YVPD5.js +21 -0
- package/dist/chunk-EL5YVPD5.js.map +1 -0
- package/dist/{chunk-ARQ4XP64.cjs → chunk-FDCFI7YT.cjs} +40080 -31492
- package/dist/chunk-FDCFI7YT.cjs.map +1 -0
- package/dist/chunk-G7UE6RKV.cjs +121 -0
- package/dist/chunk-G7UE6RKV.cjs.map +1 -0
- package/dist/{chunk-25LVV26X.cjs → chunk-JUZGUQMX.cjs} +178 -50
- package/dist/chunk-JUZGUQMX.cjs.map +1 -0
- package/dist/{chunk-SZPJ5R5B.js → chunk-KSOOKNBG.js} +1 -34
- package/dist/chunk-KSOOKNBG.js.map +1 -0
- package/dist/{chunk-RMB5DVED.cjs → chunk-KUZGEA7U.cjs} +83 -66
- package/dist/chunk-KUZGEA7U.cjs.map +1 -0
- package/dist/chunk-LXC6P2EO.js +63 -0
- package/dist/chunk-LXC6P2EO.js.map +1 -0
- package/dist/chunk-MJNXIEV2.js +24 -0
- package/dist/chunk-MJNXIEV2.js.map +1 -0
- package/dist/{chunk-CPXLQ57U.js → chunk-MVGGXOFA.js} +37 -20
- package/dist/chunk-MVGGXOFA.js.map +1 -0
- package/dist/{chunk-LY34ORX6.js → chunk-O55ZUAX7.js} +39920 -31332
- package/dist/chunk-O55ZUAX7.js.map +1 -0
- package/dist/chunk-OHPI2HRK.js +47 -0
- package/dist/chunk-OHPI2HRK.js.map +1 -0
- package/dist/chunk-PLJLE4A4.js +121 -0
- package/dist/chunk-PLJLE4A4.js.map +1 -0
- package/dist/{chunk-XGL5FKIK.js → chunk-SCN5WFIZ.js} +148 -20
- package/dist/chunk-SCN5WFIZ.js.map +1 -0
- package/dist/chunk-WBR7H6E3.cjs +47 -0
- package/dist/chunk-WBR7H6E3.cjs.map +1 -0
- package/dist/chunk-XL4V2PYG.cjs +63 -0
- package/dist/chunk-XL4V2PYG.cjs.map +1 -0
- package/dist/components/announcement-bar.d.ts.map +1 -1
- package/dist/components/chat/chat-attachment-bar.d.ts +66 -0
- package/dist/components/chat/chat-attachment-bar.d.ts.map +1 -0
- package/dist/components/chat/chat-container.d.ts +21 -1
- package/dist/components/chat/chat-container.d.ts.map +1 -1
- package/dist/components/chat/chat-input.d.ts.map +1 -1
- package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
- package/dist/components/chat/chat-message-list.d.ts.map +1 -1
- package/dist/components/chat/chat-panel-context.d.ts +9 -0
- package/dist/components/chat/chat-panel-context.d.ts.map +1 -0
- package/dist/components/chat/chat-ticket-list.d.ts +1 -1
- package/dist/components/chat/chat-ticket-list.d.ts.map +1 -1
- package/dist/components/chat/embeddable-chat.d.ts +42 -0
- package/dist/components/chat/embeddable-chat.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/admin-content-card.d.ts +34 -0
- package/dist/components/chat/entity-cards/admin-content-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/block-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/blog-card.d.ts +30 -0
- package/dist/components/chat/entity-cards/blog-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/blog-image-placeholder.d.ts +26 -0
- package/dist/components/chat/entity-cards/blog-image-placeholder.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/campaign-card-admin.d.ts +33 -0
- package/dist/components/chat/entity-cards/campaign-card-admin.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/case-study-card.d.ts +20 -0
- package/dist/components/chat/entity-cards/case-study-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/chat-ticket-item.d.ts.map +1 -0
- package/dist/components/chat/{chat-video-entity-card.d.ts → entity-cards/chat-video-entity-card.d.ts} +1 -1
- package/dist/components/chat/entity-cards/chat-video-entity-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/customer-interview-card.d.ts +19 -0
- package/dist/components/chat/entity-cards/customer-interview-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/data-room-doc-card.d.ts +47 -0
- package/dist/components/chat/entity-cards/data-room-doc-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/dispatch.d.ts +119 -0
- package/dist/components/chat/entity-cards/dispatch.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/entity-author-card.d.ts +87 -0
- package/dist/components/chat/entity-cards/entity-author-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/generic-entity-card.d.ts +42 -0
- package/dist/components/chat/entity-cards/generic-entity-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/github-activity-card.d.ts +37 -0
- package/dist/components/chat/entity-cards/github-activity-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/hubspot-ticket-card.d.ts +28 -0
- package/dist/components/chat/entity-cards/hubspot-ticket-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/index.d.ts +32 -0
- package/dist/components/chat/entity-cards/index.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/investor-update-card.d.ts +19 -0
- package/dist/components/chat/entity-cards/investor-update-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/onboarding-guide-card.d.ts +20 -0
- package/dist/components/chat/entity-cards/onboarding-guide-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/product-release-card-defaults.d.ts +21 -0
- package/dist/components/chat/entity-cards/product-release-card-defaults.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/product-release-card.d.ts +12 -0
- package/dist/components/chat/entity-cards/product-release-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/program-card-defaults.d.ts +32 -0
- package/dist/components/chat/entity-cards/program-card-defaults.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/program-card.d.ts +37 -0
- package/dist/components/chat/entity-cards/program-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/roadmap-card.d.ts +28 -0
- package/dist/components/chat/entity-cards/roadmap-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/roadmap-vote-button.d.ts +12 -0
- package/dist/components/chat/entity-cards/roadmap-vote-button.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/slack-message-card.d.ts +28 -0
- package/dist/components/chat/entity-cards/slack-message-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/task-type-icon.d.ts +6 -0
- package/dist/components/chat/entity-cards/task-type-icon.d.ts.map +1 -0
- package/dist/components/chat/hooks/index.d.ts +10 -0
- package/dist/components/chat/hooks/index.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-chat-attachment-image-gallery.d.ts +5 -0
- package/dist/components/chat/hooks/use-chat-attachment-image-gallery.d.ts.map +1 -0
- package/dist/components/chat/hooks/use-chat-attachments.d.ts +33 -0
- package/dist/components/chat/hooks/use-chat-attachments.d.ts.map +1 -0
- package/dist/components/chat/hooks/use-chat-card-item.d.ts +7 -0
- package/dist/components/chat/hooks/use-chat-card-item.d.ts.map +1 -0
- package/dist/components/chat/hooks/use-chat-identity.d.ts +44 -0
- package/dist/components/chat/hooks/use-chat-identity.d.ts.map +1 -0
- package/dist/components/chat/hooks/use-chat.d.ts +30 -0
- package/dist/components/chat/hooks/use-chat.d.ts.map +1 -0
- package/dist/components/chat/hooks/use-close-on-navigation.d.ts +2 -0
- package/dist/components/chat/hooks/use-close-on-navigation.d.ts.map +1 -0
- package/dist/components/chat/hooks/use-embedded-chat.d.ts +174 -0
- package/dist/components/chat/hooks/use-embedded-chat.d.ts.map +1 -0
- package/dist/components/chat/hooks/use-proxied-image-url.d.ts +18 -0
- package/dist/components/chat/hooks/use-proxied-image-url.d.ts.map +1 -0
- package/dist/components/chat/hooks/use-slash-commands.d.ts +32 -0
- package/dist/components/chat/hooks/use-slash-commands.d.ts.map +1 -0
- package/dist/components/chat/hooks/use-sse.d.ts +57 -0
- package/dist/components/chat/hooks/use-sse.d.ts.map +1 -0
- package/dist/components/chat/index.cjs +393 -0
- package/dist/components/chat/index.cjs.map +1 -0
- package/dist/components/chat/index.d.ts +5 -3
- package/dist/components/chat/index.d.ts.map +1 -1
- package/dist/components/chat/index.js +393 -0
- package/dist/components/chat/index.js.map +1 -0
- package/dist/components/chat/nav-link-anchor-via-runtime.d.ts +33 -0
- package/dist/components/chat/nav-link-anchor-via-runtime.d.ts.map +1 -0
- package/dist/components/chat/source-action-button.d.ts +39 -0
- package/dist/components/chat/source-action-button.d.ts.map +1 -0
- package/dist/components/chat/types/chat.types.d.ts +36 -0
- package/dist/components/chat/types/chat.types.d.ts.map +1 -1
- package/dist/components/chat/types/component.types.d.ts +56 -11
- package/dist/components/chat/types/component.types.d.ts.map +1 -1
- package/dist/components/chat/types/entities/blog.d.ts +14 -0
- package/dist/components/chat/types/entities/blog.d.ts.map +1 -0
- package/dist/components/chat/types/entities/case-study.d.ts +10 -0
- package/dist/components/chat/types/entities/case-study.d.ts.map +1 -0
- package/dist/components/chat/types/entities/content-ref.d.ts +23 -0
- package/dist/components/chat/types/entities/content-ref.d.ts.map +1 -0
- package/dist/components/chat/types/entities/customer-interview.d.ts +10 -0
- package/dist/components/chat/types/entities/customer-interview.d.ts.map +1 -0
- package/dist/components/chat/types/entities/data-room-doc.d.ts +37 -0
- package/dist/components/chat/types/entities/data-room-doc.d.ts.map +1 -0
- package/dist/components/chat/types/entities/github-activity.d.ts +29 -0
- package/dist/components/chat/types/entities/github-activity.d.ts.map +1 -0
- package/dist/components/chat/types/entities/hubspot-ticket.d.ts +39 -0
- package/dist/components/chat/types/entities/hubspot-ticket.d.ts.map +1 -0
- package/dist/components/chat/types/entities/index.d.ts +28 -0
- package/dist/components/chat/types/entities/index.d.ts.map +1 -0
- package/dist/components/chat/types/entities/investor-update.d.ts +83 -0
- package/dist/components/chat/types/entities/investor-update.d.ts.map +1 -0
- package/dist/components/chat/types/entities/onboarding-guide.d.ts +79 -0
- package/dist/components/chat/types/entities/onboarding-guide.d.ts.map +1 -0
- package/dist/components/chat/types/entities/program-types.d.ts +303 -0
- package/dist/components/chat/types/entities/program-types.d.ts.map +1 -0
- package/dist/components/chat/types/entities/roadmap-item.d.ts +41 -0
- package/dist/components/chat/types/entities/roadmap-item.d.ts.map +1 -0
- package/dist/components/chat/types/entities/slack-message.d.ts +28 -0
- package/dist/components/chat/types/entities/slack-message.d.ts.map +1 -0
- package/dist/components/chat/types/index.d.ts +1 -0
- package/dist/components/chat/types/index.d.ts.map +1 -1
- package/dist/components/chat/utils/agent-status-message.d.ts +18 -0
- package/dist/components/chat/utils/agent-status-message.d.ts.map +1 -0
- package/dist/components/chat/utils/auto-continuation-directive.d.ts +38 -0
- package/dist/components/chat/utils/auto-continuation-directive.d.ts.map +1 -0
- package/dist/components/chat/utils/chat-attachment-markdown.d.ts +114 -0
- package/dist/components/chat/utils/chat-attachment-markdown.d.ts.map +1 -0
- package/dist/components/chat/utils/chat-authed-fetch.d.ts +13 -0
- package/dist/components/chat/utils/chat-authed-fetch.d.ts.map +1 -0
- package/dist/components/chat/utils/chat-nav-resolution.d.ts +72 -0
- package/dist/components/chat/utils/chat-nav-resolution.d.ts.map +1 -0
- package/dist/components/chat/utils/chat-proxy-auth-storage.d.ts +43 -0
- package/dist/components/chat/utils/chat-proxy-auth-storage.d.ts.map +1 -0
- package/dist/components/chat/utils/chip-action-class.d.ts +16 -0
- package/dist/components/chat/utils/chip-action-class.d.ts.map +1 -0
- package/dist/components/chat/utils/chip-styles.d.ts +32 -0
- package/dist/components/chat/utils/chip-styles.d.ts.map +1 -0
- package/dist/components/chat/utils/clickup-task-type-utils.d.ts +38 -0
- package/dist/components/chat/utils/clickup-task-type-utils.d.ts.map +1 -0
- package/dist/components/chat/utils/compact-card-classes.d.ts +50 -0
- package/dist/components/chat/utils/compact-card-classes.d.ts.map +1 -0
- package/dist/components/chat/utils/decide-new-tab.d.ts +39 -0
- package/dist/components/chat/utils/decide-new-tab.d.ts.map +1 -0
- package/dist/components/chat/utils/external-app-urls.d.ts +14 -0
- package/dist/components/chat/utils/external-app-urls.d.ts.map +1 -0
- package/dist/components/chat/utils/flatten-assistant-content.d.ts +25 -0
- package/dist/components/chat/utils/flatten-assistant-content.d.ts.map +1 -0
- package/dist/components/chat/utils/icon-registry.d.ts +67 -0
- package/dist/components/chat/utils/icon-registry.d.ts.map +1 -0
- package/dist/components/chat/utils/index.d.ts +21 -0
- package/dist/components/chat/utils/index.d.ts.map +1 -1
- package/dist/components/chat/utils/is-cross-origin-url.d.ts +22 -0
- package/dist/components/chat/utils/is-cross-origin-url.d.ts.map +1 -0
- package/dist/components/chat/utils/nav-anchor-props.d.ts +54 -0
- package/dist/components/chat/utils/nav-anchor-props.d.ts.map +1 -0
- package/dist/components/chat/utils/nav-click-handler.d.ts +51 -0
- package/dist/components/chat/utils/nav-click-handler.d.ts.map +1 -0
- package/dist/components/chat/utils/scroll-anchor.d.ts +30 -0
- package/dist/components/chat/utils/scroll-anchor.d.ts.map +1 -0
- package/dist/components/chat/utils/slash-dispatch-utils.d.ts +109 -0
- package/dist/components/chat/utils/slash-dispatch-utils.d.ts.map +1 -0
- package/dist/components/chat/utils/source-icons.d.ts +8 -0
- package/dist/components/chat/utils/source-icons.d.ts.map +1 -0
- package/dist/components/chat/utils/source-row-cta.d.ts +111 -0
- package/dist/components/chat/utils/source-row-cta.d.ts.map +1 -0
- package/dist/components/features/figma-prototype-viewer.d.ts.map +1 -1
- package/dist/components/features/index.cjs +12 -6
- package/dist/components/features/index.cjs.map +1 -1
- package/dist/components/features/index.js +11 -5
- package/dist/components/features/video.d.ts.map +1 -1
- package/dist/components/icons/index.cjs +3 -3
- package/dist/components/icons/index.js +2 -2
- package/dist/components/index.cjs +274 -8
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +273 -7
- package/dist/components/interactive-wrapper.d.ts +3 -3
- package/dist/components/navigation/index.cjs +12 -6
- package/dist/components/navigation/index.cjs.map +1 -1
- package/dist/components/navigation/index.js +11 -5
- package/dist/components/resizable.d.ts +1 -1
- package/dist/components/shared/product-release/product-release-card-skeleton.d.ts +1 -1
- package/dist/components/shared/product-release/product-release-card-skeleton.d.ts.map +1 -1
- package/dist/components/shared/product-release/product-release-card.d.ts +19 -12
- package/dist/components/shared/product-release/product-release-card.d.ts.map +1 -1
- package/dist/components/shared/product-release/release-detail-page.d.ts +2 -4
- package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
- package/dist/components/ui/button/button.d.ts +13 -0
- package/dist/components/ui/button/button.d.ts.map +1 -1
- package/dist/components/ui/dashboard-info-card.d.ts.map +1 -1
- package/dist/components/ui/entity-image.d.ts.map +1 -1
- package/dist/components/ui/file-manager/index.cjs +71 -70
- package/dist/components/ui/file-manager/index.cjs.map +1 -1
- package/dist/components/ui/file-manager/index.js +6 -5
- package/dist/components/ui/file-manager/index.js.map +1 -1
- package/dist/components/ui/hover-dropdown.d.ts +66 -0
- package/dist/components/ui/hover-dropdown.d.ts.map +1 -0
- package/dist/components/ui/index.cjs +276 -6
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.d.ts +1 -0
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +278 -8
- package/dist/components/ui/simple-markdown-renderer.d.ts.map +1 -1
- package/dist/components/ui/square-avatar.d.ts.map +1 -1
- package/dist/contexts/chat-runtime-context.d.ts +109 -0
- package/dist/contexts/chat-runtime-context.d.ts.map +1 -0
- package/dist/contexts/endpoints-runtime-context.d.ts +28 -0
- package/dist/contexts/endpoints-runtime-context.d.ts.map +1 -0
- package/dist/contexts/index.cjs +30 -0
- package/dist/contexts/index.cjs.map +1 -0
- package/dist/contexts/index.d.ts +26 -0
- package/dist/contexts/index.d.ts.map +1 -0
- package/dist/contexts/index.js +30 -0
- package/dist/contexts/index.js.map +1 -0
- package/dist/contexts/use-outer-or-default.d.ts +29 -0
- package/dist/contexts/use-outer-or-default.d.ts.map +1 -0
- package/dist/embed-shims/index.cjs +51 -0
- package/dist/embed-shims/index.cjs.map +1 -0
- package/dist/embed-shims/index.d.ts +31 -0
- package/dist/embed-shims/index.d.ts.map +1 -0
- package/dist/embed-shims/index.js +51 -0
- package/dist/embed-shims/index.js.map +1 -0
- package/dist/embed-shims/next-dynamic.cjs +12 -0
- package/dist/embed-shims/next-dynamic.cjs.map +1 -0
- package/dist/embed-shims/next-dynamic.d.ts +47 -0
- package/dist/embed-shims/next-dynamic.d.ts.map +1 -0
- package/dist/embed-shims/next-dynamic.js +12 -0
- package/dist/embed-shims/next-dynamic.js.map +1 -0
- package/dist/embed-shims/next-image.cjs +12 -0
- package/dist/embed-shims/next-image.cjs.map +1 -0
- package/dist/embed-shims/next-image.d.ts +28 -0
- package/dist/embed-shims/next-image.d.ts.map +1 -0
- package/dist/embed-shims/next-image.js +12 -0
- package/dist/embed-shims/next-image.js.map +1 -0
- package/dist/embed-shims/next-link.cjs +14 -0
- package/dist/embed-shims/next-link.cjs.map +1 -0
- package/dist/embed-shims/next-link.d.ts +22 -0
- package/dist/embed-shims/next-link.d.ts.map +1 -0
- package/dist/embed-shims/next-link.js +14 -0
- package/dist/embed-shims/next-link.js.map +1 -0
- package/dist/embed-shims/next-navigation.cjs +30 -0
- package/dist/embed-shims/next-navigation.cjs.map +1 -0
- package/dist/embed-shims/next-navigation.d.ts +46 -0
- package/dist/embed-shims/next-navigation.d.ts.map +1 -0
- package/dist/embed-shims/next-navigation.js +30 -0
- package/dist/embed-shims/next-navigation.js.map +1 -0
- package/dist/hooks/index.cjs +10 -4
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +9 -3
- package/dist/hooks/use-access-code-integration.d.ts +48 -0
- package/dist/hooks/use-access-code-integration.d.ts.map +1 -0
- package/dist/hooks/use-contact-submission.d.ts.map +1 -1
- package/dist/hooks/use-og-placeholder.d.ts +31 -0
- package/dist/hooks/use-og-placeholder.d.ts.map +1 -0
- package/dist/hooks/use-toast.d.ts +1 -1
- package/dist/index.cjs +367 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +378 -18
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/utils/access-code-client.d.ts +21 -37
- package/dist/utils/access-code-client.d.ts.map +1 -1
- package/dist/utils/cn.d.ts +0 -27
- package/dist/utils/cn.d.ts.map +1 -1
- package/dist/utils/color-analysis.d.ts +33 -0
- package/dist/utils/color-analysis.d.ts.map +1 -0
- package/dist/utils/date-formatters.d.ts +16 -5
- package/dist/utils/date-formatters.d.ts.map +1 -1
- package/dist/utils/fetch-priority.d.ts +3 -0
- package/dist/utils/fetch-priority.d.ts.map +1 -0
- package/dist/utils/format.d.ts +192 -1
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/image-proxy.d.ts +67 -2
- package/dist/utils/image-proxy.d.ts.map +1 -1
- package/dist/utils/index.cjs +1274 -155
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.ts +19 -3
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1200 -157
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/local-storage-adapter.d.ts +46 -0
- package/dist/utils/local-storage-adapter.d.ts.map +1 -0
- package/dist/utils/source-icons.d.ts +78 -0
- package/dist/utils/source-icons.d.ts.map +1 -0
- package/package.json +29 -2
- package/src/components/announcement-bar.tsx +26 -4
- package/src/components/categories-cart.tsx +1 -1
- package/src/components/chat/chat-attachment-bar.tsx +323 -0
- package/src/components/chat/chat-container.tsx +39 -5
- package/src/components/chat/chat-input.tsx +7 -1
- package/src/components/chat/chat-message-enhanced.tsx +32 -22
- package/src/components/chat/chat-message-list.tsx +53 -4
- package/src/components/chat/chat-panel-context.tsx +37 -0
- package/src/components/chat/chat-ticket-list.tsx +1 -1
- package/src/components/chat/embeddable-chat.tsx +1106 -0
- package/src/components/chat/entity-cards/admin-content-card.tsx +155 -0
- package/src/components/chat/entity-cards/blog-card.tsx +259 -0
- package/src/components/chat/entity-cards/blog-image-placeholder.tsx +52 -0
- package/src/components/chat/entity-cards/campaign-card-admin.tsx +113 -0
- package/src/components/chat/entity-cards/case-study-card.tsx +192 -0
- package/src/components/chat/{chat-ticket-item.tsx → entity-cards/chat-ticket-item.tsx} +2 -2
- package/src/components/chat/{chat-video-entity-card.tsx → entity-cards/chat-video-entity-card.tsx} +2 -2
- package/src/components/chat/entity-cards/customer-interview-card.tsx +211 -0
- package/src/components/chat/entity-cards/data-room-doc-card.tsx +120 -0
- package/src/components/chat/entity-cards/dispatch.tsx +1093 -0
- package/src/components/chat/entity-cards/entity-author-card.tsx +193 -0
- package/src/components/chat/entity-cards/generic-entity-card.tsx +144 -0
- package/src/components/chat/entity-cards/github-activity-card.tsx +305 -0
- package/src/components/chat/entity-cards/hubspot-ticket-card.tsx +205 -0
- package/src/components/chat/entity-cards/index.ts +125 -0
- package/src/components/chat/entity-cards/investor-update-card.tsx +150 -0
- package/src/components/chat/entity-cards/onboarding-guide-card.tsx +326 -0
- package/src/components/chat/entity-cards/product-release-card-defaults.ts +57 -0
- package/src/components/chat/entity-cards/product-release-card.tsx +19 -0
- package/src/components/chat/entity-cards/program-card-defaults.ts +62 -0
- package/src/components/chat/entity-cards/program-card.tsx +451 -0
- package/src/components/chat/entity-cards/roadmap-card.tsx +356 -0
- package/src/components/chat/entity-cards/roadmap-vote-button.tsx +54 -0
- package/src/components/chat/entity-cards/slack-message-card.tsx +182 -0
- package/src/components/chat/entity-cards/task-type-icon.tsx +60 -0
- package/src/components/chat/hooks/index.ts +22 -0
- package/src/components/chat/hooks/use-chat-attachment-image-gallery.tsx +114 -0
- package/src/components/chat/hooks/use-chat-attachments.ts +429 -0
- package/src/components/chat/hooks/use-chat-card-item.ts +102 -0
- package/src/components/chat/hooks/use-chat-identity.ts +139 -0
- package/src/components/chat/hooks/use-chat.ts +501 -0
- package/src/components/chat/hooks/use-close-on-navigation.ts +87 -0
- package/src/components/chat/hooks/use-embedded-chat.ts +1023 -0
- package/src/components/chat/hooks/use-proxied-image-url.ts +31 -0
- package/src/components/chat/hooks/use-slash-commands.ts +106 -0
- package/src/components/chat/hooks/use-sse.ts +143 -0
- package/src/components/chat/index.ts +30 -4
- package/src/components/chat/nav-link-anchor-via-runtime.tsx +72 -0
- package/src/components/chat/source-action-button.tsx +120 -0
- package/src/components/chat/types/chat.types.ts +61 -0
- package/src/components/chat/types/component.types.ts +57 -11
- package/src/components/chat/types/entities/blog.ts +27 -0
- package/src/components/chat/types/entities/case-study.ts +14 -0
- package/src/components/chat/types/entities/content-ref.ts +23 -0
- package/src/components/chat/types/entities/customer-interview.ts +15 -0
- package/src/components/chat/types/entities/data-room-doc.ts +37 -0
- package/src/components/chat/types/entities/github-activity.ts +36 -0
- package/src/components/chat/types/entities/hubspot-ticket.ts +39 -0
- package/src/components/chat/types/entities/index.ts +28 -0
- package/src/components/chat/types/entities/investor-update.ts +100 -0
- package/src/components/chat/types/entities/onboarding-guide.ts +101 -0
- package/src/components/chat/types/entities/program-types.ts +433 -0
- package/src/components/chat/types/entities/roadmap-item.ts +42 -0
- package/src/components/chat/types/entities/slack-message.ts +28 -0
- package/src/components/chat/types/index.ts +1 -0
- package/src/components/chat/utils/agent-status-message.ts +52 -0
- package/src/components/chat/utils/auto-continuation-directive.ts +70 -0
- package/src/components/chat/utils/chat-attachment-markdown.ts +190 -0
- package/src/components/chat/utils/chat-authed-fetch.ts +73 -0
- package/src/components/chat/utils/chat-nav-resolution.ts +151 -0
- package/src/components/chat/utils/chat-proxy-auth-storage.ts +148 -0
- package/src/components/chat/utils/chip-action-class.ts +19 -0
- package/src/components/chat/utils/chip-styles.ts +51 -0
- package/src/components/chat/utils/clickup-task-type-utils.ts +59 -0
- package/src/components/chat/utils/compact-card-classes.ts +97 -0
- package/src/components/chat/utils/decide-new-tab.ts +57 -0
- package/src/components/chat/utils/external-app-urls.ts +19 -0
- package/src/components/chat/utils/flatten-assistant-content.ts +35 -0
- package/src/components/chat/utils/icon-registry.ts +297 -0
- package/src/components/chat/utils/index.ts +133 -0
- package/src/components/chat/utils/is-cross-origin-url.ts +28 -0
- package/src/components/chat/utils/nav-anchor-props.ts +78 -0
- package/src/components/chat/utils/nav-click-handler.ts +81 -0
- package/src/components/chat/utils/scroll-anchor.ts +35 -0
- package/src/components/chat/utils/slash-dispatch-utils.ts +183 -0
- package/src/components/chat/utils/source-icons.ts +14 -0
- package/src/components/chat/utils/source-row-cta.ts +215 -0
- package/src/components/empty-state.tsx +1 -1
- package/src/components/features/board/ticket-card.tsx +1 -1
- package/src/components/features/figma-prototype-viewer.tsx +2 -1
- package/src/components/features/media-gallery-manager.tsx +1 -1
- package/src/components/features/parallax-image-showcase.tsx +1 -1
- package/src/components/features/release-media-manager.tsx +1 -1
- package/src/components/features/seo-editor-preview.tsx +1 -1
- package/src/components/features/video.tsx +54 -3
- package/src/components/footer-waitlist-button.tsx +1 -1
- package/src/components/navigation/header.tsx +1 -1
- package/src/components/shared/onboarding/onboarding-step-card.tsx +1 -1
- package/src/components/shared/product-release/product-release-card-skeleton.tsx +8 -44
- package/src/components/shared/product-release/product-release-card.tsx +31 -116
- package/src/components/shared/product-release/release-detail-page.tsx +12 -16
- package/src/components/ui/actions-menu.tsx +1 -1
- package/src/components/ui/button/button.tsx +41 -11
- package/src/components/ui/button/split-button.tsx +1 -1
- package/src/components/ui/dashboard-info-card.tsx +2 -3
- package/src/components/ui/data-table/data-table-row.tsx +1 -1
- package/src/components/ui/entity-image.tsx +2 -8
- package/src/components/ui/hover-dropdown.tsx +258 -0
- package/src/components/ui/image-gallery-modal.tsx +1 -1
- package/src/components/ui/index.ts +1 -0
- package/src/components/ui/markdown-editor.tsx +1 -1
- package/src/components/ui/more-actions-menu.tsx +1 -1
- package/src/components/ui/organization-card.tsx +1 -1
- package/src/components/ui/simple-markdown-renderer.tsx +53 -5
- package/src/components/ui/square-avatar.tsx +3 -12
- package/src/components/ui/tab-navigation.tsx +1 -1
- package/src/components/ui/table/table-row.tsx +1 -1
- package/src/components/unified-filter-logic.tsx +1 -1
- package/src/components/unified-pagination.tsx +1 -1
- package/src/components/user-summary-stub.tsx +1 -1
- package/src/components/vendor-display-button.tsx +1 -1
- package/src/components/vendor-icon.tsx +1 -1
- package/src/contexts/chat-runtime-context.tsx +163 -0
- package/src/contexts/endpoints-runtime-context.tsx +68 -0
- package/src/contexts/index.ts +38 -0
- package/src/contexts/use-outer-or-default.ts +42 -0
- package/src/embed-shims/index.ts +42 -0
- package/src/embed-shims/next-dynamic.tsx +70 -0
- package/src/embed-shims/next-image.tsx +114 -0
- package/src/embed-shims/next-link.tsx +91 -0
- package/src/embed-shims/next-navigation.tsx +201 -0
- package/src/hooks/index.ts +9 -0
- package/src/hooks/state/use-api-params.ts +1 -1
- package/src/hooks/state/use-query-params.ts +1 -1
- package/src/hooks/use-access-code-integration.ts +107 -0
- package/src/hooks/use-contact-submission.ts +7 -3
- package/src/hooks/use-og-placeholder.ts +45 -0
- package/src/stories/OnboardingStepCard.stories.tsx +140 -0
- package/src/styles/chat-animations.css +65 -0
- package/src/styles/index.css +1 -0
- package/src/utils/access-code-client.ts +32 -75
- package/src/utils/cn.ts +0 -65
- package/src/utils/color-analysis.ts +205 -0
- package/src/utils/date-formatters.ts +54 -11
- package/src/utils/fetch-priority.ts +41 -0
- package/src/utils/format.ts +525 -1
- package/src/utils/image-proxy.ts +127 -7
- package/src/utils/index.ts +145 -5
- package/src/utils/local-storage-adapter.ts +105 -0
- package/src/utils/source-icons.ts +219 -0
- package/dist/chunk-25LVV26X.cjs.map +0 -1
- package/dist/chunk-ARQ4XP64.cjs.map +0 -1
- package/dist/chunk-CPXLQ57U.js.map +0 -1
- package/dist/chunk-LY34ORX6.js.map +0 -1
- package/dist/chunk-RMB5DVED.cjs.map +0 -1
- package/dist/chunk-SZPJ5R5B.js.map +0 -1
- package/dist/chunk-UC43NICZ.cjs.map +0 -1
- package/dist/chunk-XGL5FKIK.js.map +0 -1
- package/dist/components/chat/block-card.d.ts.map +0 -1
- package/dist/components/chat/chat-ticket-item.d.ts.map +0 -1
- package/dist/components/chat/chat-video-entity-card.d.ts.map +0 -1
- package/dist/utils/dynamic-icons.d.ts +0 -26
- package/dist/utils/dynamic-icons.d.ts.map +0 -1
- package/dist/utils/format-relative-time.d.ts +0 -21
- package/dist/utils/format-relative-time.d.ts.map +0 -1
- package/src/utils/.dynamic-icons.md +0 -30
- package/src/utils/.format-relative-time.md +0 -36
- package/src/utils/dynamic-icons.tsx +0 -120
- package/src/utils/format-relative-time.ts +0 -52
- /package/dist/{chunk-N57KWHDB.js.map → chunk-CIPO6DXK.js.map} +0 -0
- /package/dist/components/chat/{block-card.d.ts → entity-cards/block-card.d.ts} +0 -0
- /package/dist/components/chat/{chat-ticket-item.d.ts → entity-cards/chat-ticket-item.d.ts} +0 -0
- /package/src/components/chat/{block-card.tsx → entity-cards/block-card.tsx} +0 -0
package/src/utils/format.ts
CHANGED
|
@@ -67,6 +67,20 @@ export function formatBytes(bytes: number, decimals = 2): string {
|
|
|
67
67
|
return Number.parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i]
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Compact bytes formatter using the single-letter unit `'B'` (not
|
|
72
|
+
* `'Bytes'`). Used by upload-progress UIs where horizontal space is
|
|
73
|
+
* tight and the longer "Bytes" string wraps. Tops out at `TB`.
|
|
74
|
+
* @example formatBytesShort(0) → "0 B"; formatBytesShort(1536) → "1.5 KB"
|
|
75
|
+
*/
|
|
76
|
+
export function formatBytesShort(bytes: number): string {
|
|
77
|
+
if (bytes === 0) return '0 B'
|
|
78
|
+
const k = 1024
|
|
79
|
+
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
|
|
80
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
81
|
+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`
|
|
82
|
+
}
|
|
83
|
+
|
|
70
84
|
/**
|
|
71
85
|
* Format large numbers to abbreviated form (K, M, B) with no decimal points
|
|
72
86
|
* @param num - The number to format
|
|
@@ -101,7 +115,6 @@ export function formatLargeNumber(num: number): string {
|
|
|
101
115
|
/**
|
|
102
116
|
* Abbreviate large numbers for compact display.
|
|
103
117
|
* 1 200 → 1.2K , 15 000 → 15K , 2 000 000 → 2M
|
|
104
|
-
* Mirrors helper previously embedded in profile components.
|
|
105
118
|
* @param n Number to format
|
|
106
119
|
*/
|
|
107
120
|
export function formatAbbreviatedNumber(n: number): string {
|
|
@@ -119,3 +132,514 @@ export function formatAbbreviatedNumber(n: number): string {
|
|
|
119
132
|
}
|
|
120
133
|
return n.toLocaleString();
|
|
121
134
|
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Two-letter uppercase initials from the FIRST + LAST word of a name.
|
|
138
|
+
* Used by avatar-style fallbacks (SquareAvatar, EntityImage) where
|
|
139
|
+
* "John Michael Doe" → "JD" reads better than "JM".
|
|
140
|
+
*
|
|
141
|
+
* Returns `''` for empty / whitespace-only input. Single-word names
|
|
142
|
+
* return a single uppercase letter. Pure — same input always produces
|
|
143
|
+
* the same output, no locale or timezone surface.
|
|
144
|
+
*/
|
|
145
|
+
export function getFirstLastInitials(name?: string | null): string {
|
|
146
|
+
if (!name) return ''
|
|
147
|
+
const words = name.trim().split(/\s+/)
|
|
148
|
+
if (words.length === 0 || !words[0]) return ''
|
|
149
|
+
if (words.length === 1) return words[0].charAt(0).toUpperCase()
|
|
150
|
+
return (words[0].charAt(0) + words[words.length - 1].charAt(0)).toUpperCase()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Two-letter uppercase initials from the FIRST + SECOND word of a name.
|
|
155
|
+
* Used as SquareAvatar / EntityImage / EntityAuthorCard fallback across
|
|
156
|
+
* admin and public pages in both the lib and the hub. Handles empty
|
|
157
|
+
* strings, all-whitespace input, and single-word names cleanly — always
|
|
158
|
+
* returns at least one character so the fallback slot is never empty.
|
|
159
|
+
*
|
|
160
|
+
* Single source of truth: every "first-letter of each word, uppercase,
|
|
161
|
+
* max 2 chars" computation across hub + lib MUST come through here.
|
|
162
|
+
*/
|
|
163
|
+
export function nameInitials(
|
|
164
|
+
name: string | null | undefined,
|
|
165
|
+
fallback: string = 'E',
|
|
166
|
+
): string {
|
|
167
|
+
const source = typeof name === 'string' ? name.trim() : ''
|
|
168
|
+
const words = source.length > 0 ? source.split(/\s+/) : []
|
|
169
|
+
const letters = words
|
|
170
|
+
.map((w) => w[0])
|
|
171
|
+
.filter(Boolean)
|
|
172
|
+
.slice(0, 2)
|
|
173
|
+
.join('')
|
|
174
|
+
return (letters || fallback).toUpperCase()
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Format seconds to MM:SS or HH:MM:SS format
|
|
179
|
+
* Used for media durations (podcasts, videos)
|
|
180
|
+
* Returns: "MM:SS" or "HH:MM:SS" if hours > 0
|
|
181
|
+
*/
|
|
182
|
+
export function formatDurationMMSS(seconds: number | null | undefined): string {
|
|
183
|
+
if (!seconds) return '';
|
|
184
|
+
const hours = Math.floor(seconds / 3600);
|
|
185
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
186
|
+
const secs = Math.floor(seconds % 60);
|
|
187
|
+
|
|
188
|
+
if (hours > 0) {
|
|
189
|
+
return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
|
190
|
+
}
|
|
191
|
+
return `${minutes}:${secs.toString().padStart(2, '0')}`;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Format seconds to compact human-readable duration
|
|
196
|
+
* Used for displaying duration in cards and headers
|
|
197
|
+
* Returns: "Xh Xm" or "X min"
|
|
198
|
+
*/
|
|
199
|
+
export function formatDurationCompact(seconds: number | null | undefined): string {
|
|
200
|
+
if (!seconds) return '';
|
|
201
|
+
const hours = Math.floor(seconds / 3600);
|
|
202
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
203
|
+
if (hours > 0) {
|
|
204
|
+
return `${hours}h ${minutes}m`;
|
|
205
|
+
}
|
|
206
|
+
return `${minutes} min`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Format time with optional timezone
|
|
211
|
+
* Used for webinar and event times
|
|
212
|
+
* Returns: "10:30 AM EST" or "10:30 AM"
|
|
213
|
+
*/
|
|
214
|
+
export function formatTimeWithTimezone(
|
|
215
|
+
date: Date | string | null | undefined,
|
|
216
|
+
timezone?: string | null
|
|
217
|
+
): string {
|
|
218
|
+
if (!date) return '';
|
|
219
|
+
|
|
220
|
+
const dateObj = typeof date === 'string' ? new Date(date) : date;
|
|
221
|
+
const timeStr = dateObj.toLocaleTimeString('en-US', {
|
|
222
|
+
hour: 'numeric',
|
|
223
|
+
minute: '2-digit',
|
|
224
|
+
hour12: true,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
return timezone ? `${timeStr} ${timezone}` : timeStr;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Calculate and format duration between two timestamps
|
|
232
|
+
* Used for webinar durations
|
|
233
|
+
* Returns: "1h 30m" or "45m"
|
|
234
|
+
*/
|
|
235
|
+
export function formatDurationFromRange(
|
|
236
|
+
startAt: string | Date | null | undefined,
|
|
237
|
+
endAt: string | Date | null | undefined
|
|
238
|
+
): string {
|
|
239
|
+
if (!startAt || !endAt) return '';
|
|
240
|
+
|
|
241
|
+
const start = typeof startAt === 'string' ? new Date(startAt) : startAt;
|
|
242
|
+
const end = typeof endAt === 'string' ? new Date(endAt) : endAt;
|
|
243
|
+
const durationMs = end.getTime() - start.getTime();
|
|
244
|
+
const minutes = Math.round(durationMs / 60000);
|
|
245
|
+
|
|
246
|
+
if (minutes >= 60) {
|
|
247
|
+
const hours = Math.floor(minutes / 60);
|
|
248
|
+
const mins = minutes % 60;
|
|
249
|
+
return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
|
|
250
|
+
}
|
|
251
|
+
return `${minutes}m`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* UTC-anchored date formatter — distinct from the local-rendering
|
|
256
|
+
* `formatDate` above. RAG mappers + audit-style consumers want UTC anchor
|
|
257
|
+
* so date-only strings (`'2024-08-15'`) don't drift across timezones, AND
|
|
258
|
+
* a `'N/A'` (or caller-supplied) fallback for missing values so the LLM
|
|
259
|
+
* never sees a blank.
|
|
260
|
+
*
|
|
261
|
+
* Accepts:
|
|
262
|
+
* - ISO date or timestamp string (`'2024-08-15'` or `'2024-08-15T10:30:00Z'`)
|
|
263
|
+
* - Numeric epoch milliseconds
|
|
264
|
+
* - Numeric string (treated as epoch ms — Slack and GitHub timestamps
|
|
265
|
+
* sometimes arrive that way)
|
|
266
|
+
* - `null` / `undefined` / empty / unparseable → returns `options.fallback`
|
|
267
|
+
*
|
|
268
|
+
* Date-only strings (no `'T'`) get `T00:00:00Z` appended so they anchor to
|
|
269
|
+
* UTC midnight rather than midnight in the runtime's local timezone.
|
|
270
|
+
*/
|
|
271
|
+
export interface FormatDateUTCOptions {
|
|
272
|
+
/** Returned for null/undefined/empty/unparseable input. Defaults to 'N/A'. */
|
|
273
|
+
fallback?: string
|
|
274
|
+
/** 'UTC' (default) or 'local' — switch off the UTC anchor when audit
|
|
275
|
+
* stability matters less than local relevance (chat-card timestamps). */
|
|
276
|
+
timezone?: 'UTC' | 'local'
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function formatDateUTC(
|
|
280
|
+
value: string | number | null | undefined,
|
|
281
|
+
options: FormatDateUTCOptions = {},
|
|
282
|
+
): string {
|
|
283
|
+
const { fallback = 'N/A', timezone = 'UTC' } = options
|
|
284
|
+
|
|
285
|
+
if (value === null || value === undefined || value === '') return fallback
|
|
286
|
+
|
|
287
|
+
let ms: number
|
|
288
|
+
if (typeof value === 'number') {
|
|
289
|
+
ms = value
|
|
290
|
+
} else {
|
|
291
|
+
// String input — first try to interpret as a numeric epoch (Slack/GitHub
|
|
292
|
+
// sometimes arrive that way). Number('') is 0, but we already ruled out
|
|
293
|
+
// empty strings above.
|
|
294
|
+
const n = Number(value)
|
|
295
|
+
if (Number.isFinite(n) && n > 0 && /^-?\d+(\.\d+)?$/.test(value.trim())) {
|
|
296
|
+
ms = n
|
|
297
|
+
} else {
|
|
298
|
+
// ISO string — for date-only forms, anchor to UTC midnight to avoid
|
|
299
|
+
// timezone-offset drift (preserves the original RAG-mapper contract).
|
|
300
|
+
ms = Date.parse(value.includes('T') ? value : value + 'T00:00:00Z')
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (!Number.isFinite(ms)) return fallback
|
|
305
|
+
|
|
306
|
+
return new Date(ms).toLocaleDateString('en-US', {
|
|
307
|
+
year: 'numeric',
|
|
308
|
+
month: 'short',
|
|
309
|
+
day: 'numeric',
|
|
310
|
+
timeZone: timezone === 'local' ? undefined : 'UTC',
|
|
311
|
+
})
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Format a date string as `MM/DD/YYYY` for legal-document display
|
|
316
|
+
* (privacy policy, terms of service). Locale-stable: always en-US.
|
|
317
|
+
*/
|
|
318
|
+
export function formatLegalDate(dateInput: string): string {
|
|
319
|
+
return new Intl.DateTimeFormat('en-US', {
|
|
320
|
+
year: 'numeric',
|
|
321
|
+
month: '2-digit',
|
|
322
|
+
day: '2-digit',
|
|
323
|
+
}).format(new Date(dateInput))
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Format a currency value as `$1,234`. Returns `'N/A'` for null/undefined.
|
|
328
|
+
* USD-rounded (no cents). Used on KPI cards + investor pages.
|
|
329
|
+
*/
|
|
330
|
+
export function formatCurrency(value: number | null | undefined): string {
|
|
331
|
+
if (value == null) return 'N/A'
|
|
332
|
+
return new Intl.NumberFormat('en-US', {
|
|
333
|
+
style: 'currency',
|
|
334
|
+
currency: 'USD',
|
|
335
|
+
maximumFractionDigits: 0,
|
|
336
|
+
}).format(value)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Format a percent value as `12.50%`. Returns `'N/A'` for null/undefined.
|
|
341
|
+
*/
|
|
342
|
+
export function formatPercent(value: number | null | undefined): string {
|
|
343
|
+
if (value == null) return 'N/A'
|
|
344
|
+
return `${value.toFixed(2)}%`
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Whole-dollar price (no cents) — `$1,234`. Configurable currency code.
|
|
349
|
+
*/
|
|
350
|
+
export function formatWholeDollars(price: number, currency = 'USD'): string {
|
|
351
|
+
return new Intl.NumberFormat('en-US', {
|
|
352
|
+
style: 'currency',
|
|
353
|
+
currency,
|
|
354
|
+
maximumFractionDigits: 0,
|
|
355
|
+
}).format(price)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// =============================================================================
|
|
359
|
+
// Metric Formatting (for KPI cards / investor updates)
|
|
360
|
+
// =============================================================================
|
|
361
|
+
|
|
362
|
+
export type MetricFormat = 'number' | 'currency' | 'percentage' | 'months'
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Polarity determines whether an increase is good or bad.
|
|
366
|
+
* - 'positive': higher is better (revenue, users, MRR) → up = green, down = red
|
|
367
|
+
* - 'negative': higher is worse (burn rate, churn, CAC) → up = red, down = green
|
|
368
|
+
* - 'neutral': no judgment (headcount, runway) → always gray
|
|
369
|
+
*/
|
|
370
|
+
export type TrendPolarity = 'positive' | 'negative' | 'neutral'
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Format a metric value with compact notation ($1.2M, 150K, 12 months).
|
|
374
|
+
*/
|
|
375
|
+
export function formatCompactMetric(
|
|
376
|
+
value: number,
|
|
377
|
+
format: MetricFormat = 'number',
|
|
378
|
+
options?: { prefix?: string; suffix?: string },
|
|
379
|
+
): string {
|
|
380
|
+
if (value === 0 || value === null || value === undefined) {
|
|
381
|
+
if (format === 'currency') return `${options?.prefix || '$'}0`
|
|
382
|
+
if (format === 'percentage') return '0%'
|
|
383
|
+
if (format === 'months') return `0 ${options?.suffix || 'months'}`
|
|
384
|
+
return '0'
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const absValue = Math.abs(value)
|
|
388
|
+
const sign = value < 0 ? '-' : ''
|
|
389
|
+
|
|
390
|
+
if (format === 'currency') {
|
|
391
|
+
const prefix = options?.prefix || '$'
|
|
392
|
+
const compact = (val: number, divisor: number, suffix: string) => {
|
|
393
|
+
const divided = val / divisor
|
|
394
|
+
const formatted = divided % 1 === 0 ? divided.toFixed(0) : divided.toFixed(1)
|
|
395
|
+
return `${sign}${prefix}${formatted}${suffix}`
|
|
396
|
+
}
|
|
397
|
+
if (absValue >= 1_000_000_000) return compact(absValue, 1_000_000_000, 'B')
|
|
398
|
+
if (absValue >= 1_000_000) return compact(absValue, 1_000_000, 'M')
|
|
399
|
+
if (absValue >= 1_000) return `${sign}${prefix}${(absValue / 1_000).toFixed(0)}K`
|
|
400
|
+
return `${sign}${prefix}${absValue.toLocaleString()}`
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (format === 'percentage') {
|
|
404
|
+
return `${sign}${absValue}%`
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (format === 'months') {
|
|
408
|
+
const rounded = Math.round(value * 10) / 10
|
|
409
|
+
return `${rounded} ${options?.suffix || 'months'}`
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (absValue >= 1_000_000) return `${sign}${(absValue / 1_000_000).toFixed(1)}M`
|
|
413
|
+
if (absValue >= 1_000) return `${sign}${(absValue / 1_000).toFixed(1)}K`
|
|
414
|
+
return value.toLocaleString()
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Get ODS trend colors based on direction AND polarity. Single source
|
|
419
|
+
* of truth for trend coloring across hub + lib.
|
|
420
|
+
*/
|
|
421
|
+
export function getTrendColors(
|
|
422
|
+
direction: 'up' | 'down' | 'neutral',
|
|
423
|
+
polarity: TrendPolarity = 'positive',
|
|
424
|
+
): { textClass: string; badgeClass: string } {
|
|
425
|
+
if (direction === 'neutral' || polarity === 'neutral') {
|
|
426
|
+
return {
|
|
427
|
+
textClass: 'text-ods-text-secondary',
|
|
428
|
+
badgeClass: 'bg-ods-border text-ods-text-secondary',
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const isPositiveOutcome =
|
|
433
|
+
(direction === 'up' && polarity === 'positive') ||
|
|
434
|
+
(direction === 'down' && polarity === 'negative')
|
|
435
|
+
|
|
436
|
+
if (isPositiveOutcome) {
|
|
437
|
+
return {
|
|
438
|
+
textClass: 'text-ods-success',
|
|
439
|
+
badgeClass: 'bg-ods-success-secondary text-ods-success',
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return {
|
|
444
|
+
textClass: 'text-ods-error',
|
|
445
|
+
badgeClass: 'bg-ods-error-secondary text-ods-error',
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Format a date range as `Apr 20, 2026 — Jul 20, 2026`. Used on review
|
|
451
|
+
* cycle list/detail rows and summary headers.
|
|
452
|
+
*
|
|
453
|
+
* Accepts either full ISO timestamps or bare `YYYY-MM-DD` dates. Bare
|
|
454
|
+
* dates are interpreted in the viewer's LOCAL timezone — otherwise
|
|
455
|
+
* `"2026-04-20"` renders as `"Apr 19"` west of UTC. DB values for cycle
|
|
456
|
+
* period are stored as plain dates, so local-tz parsing is correct.
|
|
457
|
+
*/
|
|
458
|
+
export function formatDateRange(
|
|
459
|
+
start: string | null | undefined,
|
|
460
|
+
end: string | null | undefined,
|
|
461
|
+
): string {
|
|
462
|
+
if (!start || !end) return ''
|
|
463
|
+
const fmt = (s: string): string => {
|
|
464
|
+
const bareMatch = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s)
|
|
465
|
+
const d = bareMatch
|
|
466
|
+
? new Date(Number(bareMatch[1]), Number(bareMatch[2]) - 1, Number(bareMatch[3]))
|
|
467
|
+
: new Date(s)
|
|
468
|
+
if (Number.isNaN(d.getTime())) return s
|
|
469
|
+
return d.toLocaleDateString(undefined, {
|
|
470
|
+
month: 'short',
|
|
471
|
+
day: 'numeric',
|
|
472
|
+
year: 'numeric',
|
|
473
|
+
})
|
|
474
|
+
}
|
|
475
|
+
return `${fmt(start)} — ${fmt(end)}`
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Format an ISO date string as `"Jan 5, 2025 at 10:30 AM"`. Used by
|
|
480
|
+
* admin podcast/webinar cards where the display needs both the short
|
|
481
|
+
* date AND wall-clock time in a single readable phrase.
|
|
482
|
+
*
|
|
483
|
+
* Uses `new Date(...).toLocale*` (NOT the TZ-safe split) because the
|
|
484
|
+
* source columns store full timestamps + the wall-clock half MUST
|
|
485
|
+
* render in the viewer's local timezone (a podcast scheduled "10:30 AM
|
|
486
|
+
* EST" should display "10:30 AM" for the Eastern admin, "7:30 AM" for
|
|
487
|
+
* the Pacific admin — viewer-local is the right semantics here, unlike
|
|
488
|
+
* date-only fields).
|
|
489
|
+
*/
|
|
490
|
+
export function formatDateTimeAt(dateString: string): string {
|
|
491
|
+
const date = new Date(dateString)
|
|
492
|
+
const dateStr = date.toLocaleDateString('en-US', {
|
|
493
|
+
month: 'short',
|
|
494
|
+
day: 'numeric',
|
|
495
|
+
year: 'numeric',
|
|
496
|
+
})
|
|
497
|
+
const timeStr = date.toLocaleTimeString('en-US', {
|
|
498
|
+
hour: 'numeric',
|
|
499
|
+
minute: '2-digit',
|
|
500
|
+
hour12: true,
|
|
501
|
+
})
|
|
502
|
+
return `${dateStr} at ${timeStr}`
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Format a duration measured in MILLISECONDS as a compact human-readable
|
|
507
|
+
* string. Returns `"0ms"` for null/NaN/negative, then `Xms` → `X.Xs` →
|
|
508
|
+
* `X.Xm` as the input grows. Used by job-runs / orchestrator dashboards
|
|
509
|
+
* where elapsed milliseconds is the natural unit.
|
|
510
|
+
*
|
|
511
|
+
* Distinct from `formatDuration(seconds)` (verbose `"X hours Y minutes"`)
|
|
512
|
+
* and `formatDurationMMSS(seconds)` (media timecode `MM:SS`).
|
|
513
|
+
*/
|
|
514
|
+
export function formatDurationFromMs(ms: number | null | undefined): string {
|
|
515
|
+
if (!ms || isNaN(ms) || ms < 0) return '0ms'
|
|
516
|
+
if (ms < 1000) return `${ms}ms`
|
|
517
|
+
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`
|
|
518
|
+
return `${(ms / 60000).toFixed(1)}m`
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Format seconds to verbose human-readable duration: `"X seconds"`,
|
|
523
|
+
* `"X minutes"`, `"X hours Y minutes"`. Use this for human-readable
|
|
524
|
+
* spans; for media timecodes use `formatDurationMMSS`; for compact
|
|
525
|
+
* media labels use `formatDurationCompact`; for elapsed milliseconds
|
|
526
|
+
* use `formatDurationFromMs`.
|
|
527
|
+
*/
|
|
528
|
+
export function formatDuration(seconds: number): string {
|
|
529
|
+
if (seconds < 60) return `${seconds} seconds`
|
|
530
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)} minutes`
|
|
531
|
+
|
|
532
|
+
const hours = Math.floor(seconds / 3600)
|
|
533
|
+
const minutes = Math.floor((seconds % 3600) / 60)
|
|
534
|
+
|
|
535
|
+
if (minutes === 0) return `${hours} hour${hours > 1 ? 's' : ''}`
|
|
536
|
+
return `${hours} hour${hours > 1 ? 's' : ''} ${minutes} minute${minutes > 1 ? 's' : ''}`
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// =============================================================================
|
|
540
|
+
// Text formatting helpers (proper-case, HTML strip, bio cleanup)
|
|
541
|
+
// =============================================================================
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Format underscore-separated text into proper case.
|
|
545
|
+
* `"self_hosted"` → `"Self Hosted"`
|
|
546
|
+
* `"open_source"` → `"Open Source"`
|
|
547
|
+
*/
|
|
548
|
+
export function formatUnderscoreText(text: string): string {
|
|
549
|
+
return text
|
|
550
|
+
.split('_')
|
|
551
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
552
|
+
.join(' ')
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Strip HTML tags and decode common HTML entities from a string.
|
|
557
|
+
* Useful for cleaning API responses that contain HTML content.
|
|
558
|
+
*
|
|
559
|
+
* @example
|
|
560
|
+
* stripHtml('<p>Hello <strong>World</strong></p>') // "Hello World"
|
|
561
|
+
* stripHtml('Test & Example') // "Test & Example"
|
|
562
|
+
*/
|
|
563
|
+
export function stripHtml(html: string): string {
|
|
564
|
+
// Iterative tag-strip — a single pass is bypass-able by inputs like
|
|
565
|
+
// `<scr<script>ipt>`: stripping the inner `<script>` leaves the outer
|
|
566
|
+
// `<script>` reassembled. Loop until the string is stable so multi-
|
|
567
|
+
// character bypasses cannot survive (CodeQL
|
|
568
|
+
// `js/incomplete-multi-character-sanitization`). `<[^<>]*>` rejects
|
|
569
|
+
// `<` inside the tag body so each pass is itself ReDoS-safe (no
|
|
570
|
+
// backtracking on `<<<<<...<>` inputs).
|
|
571
|
+
let noTags = html
|
|
572
|
+
let prev: string
|
|
573
|
+
do {
|
|
574
|
+
prev = noTags
|
|
575
|
+
noTags = noTags.replace(/<[^<>]*>/g, '')
|
|
576
|
+
} while (noTags !== prev)
|
|
577
|
+
|
|
578
|
+
// Decode entities. `&` MUST come LAST so we don't double-decode
|
|
579
|
+
// sequences like `&lt;` (which should render as the LITERAL text
|
|
580
|
+
// `<`, not as `<`). All other named/numeric entities decode first;
|
|
581
|
+
// only after those have been replaced do we collapse `&` → `&`.
|
|
582
|
+
return noTags
|
|
583
|
+
.replace(/ /g, ' ')
|
|
584
|
+
.replace(/</g, '<')
|
|
585
|
+
.replace(/>/g, '>')
|
|
586
|
+
.replace(/"/g, '"')
|
|
587
|
+
.replace(/'/g, "'")
|
|
588
|
+
.replace(/'/g, "'")
|
|
589
|
+
.replace(/'/g, "'")
|
|
590
|
+
.replace(/&/g, '&')
|
|
591
|
+
.replace(/\s+/g, ' ')
|
|
592
|
+
.trim()
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Display label for the openmsp `vendors.classification` enum.
|
|
597
|
+
* Falls back to proper-cased underscore split for values not in the
|
|
598
|
+
* curated mapping.
|
|
599
|
+
*/
|
|
600
|
+
export function formatClassification(classification: string): string {
|
|
601
|
+
const customMappings: Record<string, string> = {
|
|
602
|
+
openframe_selected: 'OpenFrame Selected',
|
|
603
|
+
}
|
|
604
|
+
return customMappings[classification] || formatUnderscoreText(classification)
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Display label for the openmsp `vendors.pricing_model` enum.
|
|
609
|
+
* Falls back to proper-cased underscore split for values not in the
|
|
610
|
+
* curated mapping.
|
|
611
|
+
*/
|
|
612
|
+
export function formatPricingModel(pricingModel: string): string {
|
|
613
|
+
const customMappings: Record<string, string> = {
|
|
614
|
+
one_time: 'One-time Purchase',
|
|
615
|
+
self_hosted: 'Self-hosted',
|
|
616
|
+
}
|
|
617
|
+
return customMappings[pricingModel] || formatUnderscoreText(pricingModel)
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Format a bio/about text from the profiles table for display.
|
|
622
|
+
* Handles HTML content (e.g. `<p>` tags from rich text editors),
|
|
623
|
+
* plain text passthrough, and null/undefined values.
|
|
624
|
+
*/
|
|
625
|
+
export function formatBioText(
|
|
626
|
+
aboutHtml: string | null | undefined,
|
|
627
|
+
fallback: string = '',
|
|
628
|
+
): string {
|
|
629
|
+
if (!aboutHtml || !aboutHtml.trim()) return fallback
|
|
630
|
+
|
|
631
|
+
if (aboutHtml.includes('<p')) {
|
|
632
|
+
// `<p[^<>]*>` rejects `<` inside the tag so the automaton can't
|
|
633
|
+
// backtrack on `<<<<<...<p>` inputs (ReDoS class fix vs. `<p[^>]*>`).
|
|
634
|
+
const paragraphs = aboutHtml
|
|
635
|
+
.split(/<p[^<>]*>/)
|
|
636
|
+
.slice(1)
|
|
637
|
+
.map((part) => part.split('</p>')[0])
|
|
638
|
+
.map((text) => stripHtml(text).trim())
|
|
639
|
+
.filter((text) => text.length > 0)
|
|
640
|
+
|
|
641
|
+
if (paragraphs.length > 0) return paragraphs.join(' ')
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
return stripHtml(aboutHtml).trim() || fallback
|
|
645
|
+
}
|
package/src/utils/image-proxy.ts
CHANGED
|
@@ -1,9 +1,129 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Pure image-proxy URL builder + helpers.
|
|
3
|
+
*
|
|
4
|
+
* Lib-side replacement for the hub's `lib/utils/image-proxy.ts`. The
|
|
5
|
+
* hub used to hardcode `/api/image-proxy` + an `openmsp.ai` skip-domain;
|
|
6
|
+
* this version takes BOTH as parameters so embedded apps (and other
|
|
7
|
+
* platforms that host the lib) can wire their own proxy prefix + skip
|
|
8
|
+
* list at the runtime layer.
|
|
9
|
+
*
|
|
10
|
+
* Pure function — no side effects, no env reads. Callers thread the
|
|
11
|
+
* proxy config through (typically from `ChatRuntime.endpoints.imageProxyUrlPrefix`).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export type GetProxiedImageUrlOptions = {
|
|
15
|
+
/**
|
|
16
|
+
* URL prefix for the image proxy (`<prefix>?url=<encoded>`). When unset,
|
|
17
|
+
* `getProxiedImageUrl` returns the original URL unchanged — relative
|
|
18
|
+
* URLs always pass through. Hub default: `/api/image-proxy`.
|
|
19
|
+
*/
|
|
20
|
+
proxyPrefix?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Domains that should bypass the proxy (e.g. own-CDN hosts whose
|
|
23
|
+
* `Content-Type` is reliable and that already serve CORS-permitting
|
|
24
|
+
* headers). Matched as `imageUrl.includes(domain)` so subdomains
|
|
25
|
+
* inherit. Default: `[]`.
|
|
26
|
+
*/
|
|
27
|
+
skipDomains?: string[];
|
|
28
|
+
/**
|
|
29
|
+
* Return the original `https://` URL so the browser loads it directly
|
|
30
|
+
* (no proxy). Use when upstream `Content-Type` breaks the proxy (common
|
|
31
|
+
* with SVG on some CDNs) or you want the origin to see the client
|
|
32
|
+
* request. HTTP stays proxied (mixed content / legacy).
|
|
33
|
+
*/
|
|
34
|
+
directHttps?: boolean;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Resolve an external image URL through (or around) the image proxy.
|
|
39
|
+
*
|
|
40
|
+
* Resolution order:
|
|
41
|
+
* 1. `imageUrl` already contains `proxyPrefix` → return unchanged
|
|
42
|
+
* (self-skip — prevents double-wrap).
|
|
43
|
+
* 2. `directHttps` set AND `imageUrl` starts with `https://` → return
|
|
44
|
+
* unchanged.
|
|
45
|
+
* 3. `imageUrl` doesn't start with `http://` or `https://` → return
|
|
46
|
+
* unchanged (relative URLs pass through).
|
|
47
|
+
* 4. `proxyPrefix` unset → return unchanged.
|
|
48
|
+
* 5. `skipDomains` matches the URL's host → return unchanged.
|
|
49
|
+
* 6. Else return `<proxyPrefix>?url=<encoded>`.
|
|
50
|
+
*/
|
|
51
|
+
export function getProxiedImageUrl(
|
|
52
|
+
imageUrl: string | null,
|
|
53
|
+
options?: GetProxiedImageUrlOptions,
|
|
54
|
+
): string | null {
|
|
55
|
+
if (!imageUrl) return null;
|
|
56
|
+
|
|
57
|
+
const proxyPrefix = options?.proxyPrefix;
|
|
58
|
+
const skipDomains = options?.skipDomains ?? [];
|
|
59
|
+
const directHttps = options?.directHttps ?? false;
|
|
60
|
+
|
|
61
|
+
// (1) Self-skip — already proxied. Check this BEFORE the http/https
|
|
62
|
+
// gate so an absolute proxy URL passed in (e.g. `https://hub.example/api/image-proxy?url=…`)
|
|
63
|
+
// is treated as already-proxied even though it starts with https://.
|
|
64
|
+
if (proxyPrefix && imageUrl.includes(proxyPrefix)) {
|
|
65
|
+
return imageUrl;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// (3) Relative URLs / data: / blob: — return as-is.
|
|
69
|
+
if (!imageUrl.startsWith('http://') && !imageUrl.startsWith('https://')) {
|
|
70
|
+
return imageUrl;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// (2) Direct-https opt-out — only applies to https (http stays proxied).
|
|
74
|
+
if (directHttps && imageUrl.startsWith('https://')) {
|
|
75
|
+
return imageUrl;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// (4) No proxy configured — return as-is.
|
|
79
|
+
if (!proxyPrefix) {
|
|
80
|
+
return imageUrl;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// (5) Skip-list match.
|
|
84
|
+
for (const domain of skipDomains) {
|
|
85
|
+
if (imageUrl.includes(domain)) {
|
|
86
|
+
return imageUrl;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// (6) Proxy.
|
|
91
|
+
return `${proxyPrefix}?url=${encodeURIComponent(imageUrl)}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Heuristic: URL path looks like an SVG. Useful with `{ directHttps: true }`
|
|
96
|
+
* when only SVGs misbehave through the proxy; raster images can stay
|
|
97
|
+
* proxied if you prefer.
|
|
98
|
+
*/
|
|
99
|
+
export function urlPathLooksLikeSvg(imageUrl: string): boolean {
|
|
100
|
+
try {
|
|
101
|
+
return /\.svg$/i.test(new URL(imageUrl).pathname);
|
|
102
|
+
} catch {
|
|
103
|
+
return /\.svg(\?|#|$)/i.test(imageUrl);
|
|
104
|
+
}
|
|
4
105
|
}
|
|
5
106
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
107
|
+
/**
|
|
108
|
+
* Check if an image URL needs to be proxied. `proxyPrefix` is the same
|
|
109
|
+
* value passed to `getProxiedImageUrl` — used to short-circuit the
|
|
110
|
+
* self-skip case.
|
|
111
|
+
*/
|
|
112
|
+
export function shouldProxyImage(
|
|
113
|
+
imageUrl: string | null,
|
|
114
|
+
proxyPrefix?: string,
|
|
115
|
+
): boolean {
|
|
116
|
+
if (!imageUrl) return false;
|
|
117
|
+
if (!imageUrl.startsWith('http://') && !imageUrl.startsWith('https://')) return false;
|
|
118
|
+
if (proxyPrefix && imageUrl.includes(proxyPrefix)) return false;
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Generate a responsive `sizes` attribute for `<img>` / `<Image>` tags.
|
|
124
|
+
* Pure utility — kept here for backwards-compatibility with the previous
|
|
125
|
+
* stub.
|
|
126
|
+
*/
|
|
127
|
+
export function generateImageSizes(_url: string): string {
|
|
128
|
+
return `(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw`;
|
|
129
|
+
}
|