@flamingo-stack/openframe-frontend-core 0.0.296-snapshot.20260621021605 → 0.0.296
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 +0 -9
- package/dist/chunk-26PKDALD.js +2379 -0
- package/dist/chunk-26PKDALD.js.map +1 -0
- package/dist/chunk-3MCHAFHB.js +89 -0
- package/dist/chunk-3MCHAFHB.js.map +1 -0
- package/dist/{chunk-PI4WSYQV.js → chunk-3ZXUQQL4.js} +2 -2
- package/dist/{chunk-WMSTJAZT.cjs → chunk-5E2HOSSH.cjs} +51 -913
- package/dist/chunk-5E2HOSSH.cjs.map +1 -0
- package/dist/{chunk-IL47XWV5.js → chunk-5P3B2LZW.js} +14 -8
- package/dist/{chunk-IL47XWV5.js.map → chunk-5P3B2LZW.js.map} +1 -1
- package/dist/chunk-66AANIOC.cjs +619 -0
- package/dist/chunk-66AANIOC.cjs.map +1 -0
- package/dist/{chunk-AD6C23QY.js → chunk-6GCI7JOE.js} +7 -8
- package/dist/{chunk-AD6C23QY.js.map → chunk-6GCI7JOE.js.map} +1 -1
- package/dist/chunk-6JINAOI7.cjs +311 -0
- package/dist/chunk-6JINAOI7.cjs.map +1 -0
- package/dist/{chunk-2QG57XOJ.js → chunk-7RIYT7ZH.js} +205 -1067
- package/dist/chunk-7RIYT7ZH.js.map +1 -0
- package/dist/{chunk-L6PSSIUQ.cjs → chunk-AQOWFSMB.cjs} +1 -1
- package/dist/chunk-AQOWFSMB.cjs.map +1 -0
- package/dist/chunk-BOCFIKYS.cjs +3009 -0
- package/dist/chunk-BOCFIKYS.cjs.map +1 -0
- package/dist/{chunk-54KNMC2R.cjs → chunk-D3LEFMOA.cjs} +3 -3
- package/dist/{chunk-54KNMC2R.cjs.map → chunk-D3LEFMOA.cjs.map} +1 -1
- package/dist/chunk-D652TJBQ.js +3009 -0
- package/dist/chunk-D652TJBQ.js.map +1 -0
- package/dist/{chunk-PWQUAVA3.js → chunk-E4XABBSU.js} +98 -338
- package/dist/chunk-E4XABBSU.js.map +1 -0
- package/dist/{chunk-JALO4TAZ.js → chunk-EL6QLAWX.js} +55 -357
- package/dist/chunk-EL6QLAWX.js.map +1 -0
- package/dist/{chunk-6C526VNN.cjs → chunk-EYEW6PTA.cjs} +118 -358
- package/dist/chunk-EYEW6PTA.cjs.map +1 -0
- package/dist/chunk-FQJK446R.js +1606 -0
- package/dist/chunk-FQJK446R.js.map +1 -0
- package/dist/{chunk-4PSQS3SW.cjs → chunk-GLLDTKZK.cjs} +9 -7
- package/dist/chunk-GLLDTKZK.cjs.map +1 -0
- package/dist/{chunk-FQOTC3UU.cjs → chunk-IE6OU3WQ.cjs} +16 -318
- package/dist/chunk-IE6OU3WQ.cjs.map +1 -0
- package/dist/chunk-J54Z3OCR.cjs +1606 -0
- package/dist/chunk-J54Z3OCR.cjs.map +1 -0
- package/dist/{chunk-PC746XCO.js → chunk-K2PFPBMF.js} +5563 -15048
- package/dist/chunk-K2PFPBMF.js.map +1 -0
- package/dist/chunk-KXCRGTRN.cjs +2379 -0
- package/dist/chunk-KXCRGTRN.cjs.map +1 -0
- package/dist/{chunk-IZ7JSBFP.js → chunk-LCNMR277.js} +1 -1
- package/dist/chunk-LCNMR277.js.map +1 -0
- package/dist/chunk-LFGGF7OT.cjs +449 -0
- package/dist/chunk-LFGGF7OT.cjs.map +1 -0
- package/dist/chunk-M2OCXTNT.js +311 -0
- package/dist/chunk-M2OCXTNT.js.map +1 -0
- package/dist/{chunk-L7ULJKG7.js → chunk-MBFWU2EM.js} +10 -6
- package/dist/{chunk-L7ULJKG7.js.map → chunk-MBFWU2EM.js.map} +1 -1
- package/dist/chunk-ME4EVDFP.js +619 -0
- package/dist/chunk-ME4EVDFP.js.map +1 -0
- package/dist/chunk-OQ6X7ZOC.js +449 -0
- package/dist/chunk-OQ6X7ZOC.js.map +1 -0
- package/dist/{chunk-4TLE6VLU.js → chunk-OY7OF7E7.js} +24 -30
- package/dist/chunk-OY7OF7E7.js.map +1 -0
- package/dist/chunk-POKKCWKF.js +354 -0
- package/dist/chunk-POKKCWKF.js.map +1 -0
- package/dist/{chunk-GUTS7HGA.cjs → chunk-QHIXS3W2.cjs} +2514 -11999
- package/dist/chunk-QHIXS3W2.cjs.map +1 -0
- package/dist/chunk-TFSYSWPS.cjs +89 -0
- package/dist/chunk-TFSYSWPS.cjs.map +1 -0
- package/dist/{chunk-53FUMSZ5.cjs → chunk-W6M2FLLT.cjs} +46 -40
- package/dist/chunk-W6M2FLLT.cjs.map +1 -0
- package/dist/{chunk-3JIQVE7T.js → chunk-WHMATDVP.js} +15 -9
- package/dist/{chunk-3JIQVE7T.js.map → chunk-WHMATDVP.js.map} +1 -1
- package/dist/{chunk-YBYI62OE.cjs → chunk-X647HY3F.cjs} +37 -33
- package/dist/chunk-X647HY3F.cjs.map +1 -0
- package/dist/{chunk-UNVE2SDJ.cjs → chunk-X6BV7MB7.cjs} +31 -37
- package/dist/chunk-X6BV7MB7.cjs.map +1 -0
- package/dist/{chunk-7OVGB2DQ.cjs → chunk-XREEV72C.cjs} +25 -19
- package/dist/chunk-XREEV72C.cjs.map +1 -0
- package/dist/chunk-YETA25JW.cjs +354 -0
- package/dist/chunk-YETA25JW.cjs.map +1 -0
- package/dist/{chunk-FCDQNTDG.cjs → chunk-YIGPRLQY.cjs} +20 -21
- package/dist/chunk-YIGPRLQY.cjs.map +1 -0
- package/dist/{chunk-X4DOXQRT.js → chunk-ZP4AVIZP.js} +6 -4
- package/dist/{chunk-X4DOXQRT.js.map → chunk-ZP4AVIZP.js.map} +1 -1
- package/dist/components/chat/index.cjs +18 -8
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.js +85 -75
- package/dist/components/contact/index.cjs +15 -8
- package/dist/components/contact/index.cjs.map +1 -1
- package/dist/components/contact/index.js +14 -7
- package/dist/components/docs/doc-viewer.d.ts +2 -39
- package/dist/components/docs/doc-viewer.d.ts.map +1 -1
- package/dist/components/docs/index.cjs +9 -17
- package/dist/components/docs/index.cjs.map +1 -1
- package/dist/components/docs/index.d.ts +0 -4
- package/dist/components/docs/index.d.ts.map +1 -1
- package/dist/components/docs/index.js +8 -16
- package/dist/components/docs/use-document-tree.d.ts.map +1 -1
- package/dist/components/embeds/embed-iframe.d.ts.map +1 -1
- package/dist/components/embeds/index.cjs +15 -38
- package/dist/components/embeds/index.cjs.map +1 -1
- package/dist/components/embeds/index.d.ts +0 -8
- package/dist/components/embeds/index.d.ts.map +1 -1
- package/dist/components/embeds/index.js +17 -40
- package/dist/components/faq/index.cjs +16 -9
- package/dist/components/faq/index.cjs.map +1 -1
- package/dist/components/faq/index.js +15 -8
- package/dist/components/features/index.cjs +16 -8
- package/dist/components/features/index.cjs.map +1 -1
- package/dist/components/features/index.js +32 -24
- package/dist/components/index.cjs +452 -257
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +976 -781
- package/dist/components/index.js.map +1 -1
- package/dist/components/layout/page-layout.d.ts +1 -10
- package/dist/components/layout/page-layout.d.ts.map +1 -1
- package/dist/components/layout/title-block.d.ts +1 -17
- package/dist/components/layout/title-block.d.ts.map +1 -1
- package/dist/components/navigation/index.cjs +15 -7
- package/dist/components/navigation/index.cjs.map +1 -1
- package/dist/components/navigation/index.js +17 -9
- package/dist/components/onboarding-guides/index.cjs +36 -35
- package/dist/components/onboarding-guides/index.cjs.map +1 -1
- package/dist/components/onboarding-guides/index.js +14 -13
- package/dist/components/onboarding-guides/index.js.map +1 -1
- package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts +1 -1
- package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts.map +1 -1
- package/dist/components/related-content/index.cjs +16 -9
- package/dist/components/related-content/index.cjs.map +1 -1
- package/dist/components/related-content/index.js +15 -8
- package/dist/components/shared/dev-section/dev-section-page.d.ts +0 -9
- package/dist/components/shared/dev-section/dev-section-page.d.ts.map +1 -1
- package/dist/components/shared/dev-section/dev-section-view.d.ts.map +1 -1
- package/dist/components/shared/dev-section/index.d.ts +1 -1
- package/dist/components/shared/dev-section/index.d.ts.map +1 -1
- package/dist/components/shared/doc-search/use-doc-search.d.ts.map +1 -1
- package/dist/components/shared/legal-document/legal-document-page.d.ts.map +1 -1
- package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
- package/dist/components/tickets/index.cjs +112 -100
- package/dist/components/tickets/index.cjs.map +1 -1
- package/dist/components/tickets/index.js +32 -20
- package/dist/components/tickets/index.js.map +1 -1
- package/dist/components/ui/file-manager/index.cjs +52 -50
- package/dist/components/ui/file-manager/index.cjs.map +1 -1
- package/dist/components/ui/file-manager/index.js +6 -4
- package/dist/components/ui/file-manager/index.js.map +1 -1
- package/dist/components/ui/index.cjs +19 -13
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.d.ts +0 -2
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +139 -133
- package/dist/components/ui/release-changelog-section.d.ts +2 -6
- package/dist/components/ui/release-changelog-section.d.ts.map +1 -1
- package/dist/components/ui/simple-markdown-renderer.d.ts +8 -2
- package/dist/components/ui/simple-markdown-renderer.d.ts.map +1 -1
- package/dist/contexts/chat-runtime-context.d.ts +0 -14
- package/dist/contexts/chat-runtime-context.d.ts.map +1 -1
- package/dist/contexts/index.cjs +3 -3
- package/dist/contexts/index.js +5 -5
- package/dist/embed-shims/index.cjs +3 -3
- package/dist/embed-shims/index.cjs.map +1 -1
- package/dist/embed-shims/index.js +4 -4
- package/dist/hooks/index.cjs +9 -4
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.js +11 -6
- package/dist/index.cjs +20 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +364 -358
- package/dist/types/doc-source.d.ts +1 -31
- package/dist/types/doc-source.d.ts.map +1 -1
- package/dist/utils/index.cjs +0 -4
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -4
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -7
- package/src/components/chat/embeddable-chat.tsx +1 -1
- package/src/components/docs/doc-viewer.tsx +19 -111
- package/src/components/docs/index.ts +0 -17
- package/src/components/docs/use-document-tree.ts +0 -21
- package/src/components/embeds/embed-iframe.tsx +9 -7
- package/src/components/embeds/index.ts +0 -30
- package/src/components/embeds/og-link-preview.tsx +13 -13
- package/src/components/layout/page-layout.tsx +1 -14
- package/src/components/layout/title-block.tsx +62 -40
- package/src/components/onboarding-guides/onboarding-guide-detail-view.tsx +3 -3
- package/src/components/shared/dev-section/dev-section-page.tsx +1 -9
- package/src/components/shared/dev-section/dev-section-view.tsx +9 -14
- package/src/components/shared/dev-section/index.ts +1 -1
- package/src/components/shared/doc-search/use-doc-search.ts +3 -7
- package/src/components/shared/legal-document/legal-document-page.tsx +2 -2
- package/src/components/shared/product-release/release-detail-page.tsx +4 -6
- package/src/components/ui/index.ts +0 -2
- package/src/components/ui/release-changelog-section.tsx +2 -7
- package/src/components/ui/simple-markdown-renderer.tsx +11 -7
- package/src/contexts/chat-runtime-context.tsx +0 -14
- package/src/types/doc-source.ts +1 -33
- package/src/utils/index.ts +0 -1
- package/dist/chunk-2QG57XOJ.js.map +0 -1
- package/dist/chunk-4PSQS3SW.cjs.map +0 -1
- package/dist/chunk-4TLE6VLU.js.map +0 -1
- package/dist/chunk-53FUMSZ5.cjs.map +0 -1
- package/dist/chunk-6C526VNN.cjs.map +0 -1
- package/dist/chunk-7OVGB2DQ.cjs.map +0 -1
- package/dist/chunk-F5OB2YAL.cjs +0 -144
- package/dist/chunk-F5OB2YAL.cjs.map +0 -1
- package/dist/chunk-FBWXMMRB.cjs +0 -2
- package/dist/chunk-FBWXMMRB.cjs.map +0 -1
- package/dist/chunk-FCDQNTDG.cjs.map +0 -1
- package/dist/chunk-FQOTC3UU.cjs.map +0 -1
- package/dist/chunk-GUTS7HGA.cjs.map +0 -1
- package/dist/chunk-GZ4C3XW6.js +0 -2
- package/dist/chunk-GZ4C3XW6.js.map +0 -1
- package/dist/chunk-IZ7JSBFP.js.map +0 -1
- package/dist/chunk-JALO4TAZ.js.map +0 -1
- package/dist/chunk-L6PSSIUQ.cjs.map +0 -1
- package/dist/chunk-PC746XCO.js.map +0 -1
- package/dist/chunk-PWQUAVA3.js.map +0 -1
- package/dist/chunk-SA2WPJVO.js +0 -144
- package/dist/chunk-SA2WPJVO.js.map +0 -1
- package/dist/chunk-UNVE2SDJ.cjs.map +0 -1
- package/dist/chunk-WMSTJAZT.cjs.map +0 -1
- package/dist/chunk-YBYI62OE.cjs.map +0 -1
- package/dist/components/case-studies/index.cjs +0 -126
- package/dist/components/case-studies/index.cjs.map +0 -1
- package/dist/components/case-studies/index.d.ts +0 -2
- package/dist/components/case-studies/index.d.ts.map +0 -1
- package/dist/components/case-studies/index.js +0 -126
- package/dist/components/case-studies/index.js.map +0 -1
- package/dist/components/case-studies/share-experience-section.d.ts +0 -48
- package/dist/components/case-studies/share-experience-section.d.ts.map +0 -1
- package/dist/components/docs/docs-hub-page.d.ts +0 -46
- package/dist/components/docs/docs-hub-page.d.ts.map +0 -1
- package/dist/components/docs/skeletons.d.ts +0 -32
- package/dist/components/docs/skeletons.d.ts.map +0 -1
- package/dist/components/docs/use-docs-resolve-link.d.ts +0 -20
- package/dist/components/docs/use-docs-resolve-link.d.ts.map +0 -1
- package/dist/components/embeds/embed-container.d.ts +0 -37
- package/dist/components/embeds/embed-container.d.ts.map +0 -1
- package/dist/components/embeds/file-download-card.d.ts +0 -18
- package/dist/components/embeds/file-download-card.d.ts.map +0 -1
- package/dist/components/embeds/linkedin-embed-client.d.ts +0 -8
- package/dist/components/embeds/linkedin-embed-client.d.ts.map +0 -1
- package/dist/components/embeds/markdown-image.d.ts +0 -5
- package/dist/components/embeds/markdown-image.d.ts.map +0 -1
- package/dist/components/embeds/reddit-embed-client.d.ts +0 -7
- package/dist/components/embeds/reddit-embed-client.d.ts.map +0 -1
- package/dist/components/embeds/rich-markdown-runtime.d.ts +0 -46
- package/dist/components/embeds/rich-markdown-runtime.d.ts.map +0 -1
- package/dist/components/embeds/twitter-embed-client.d.ts +0 -8
- package/dist/components/embeds/twitter-embed-client.d.ts.map +0 -1
- package/dist/components/layout/page-header.d.ts +0 -78
- package/dist/components/layout/page-header.d.ts.map +0 -1
- package/dist/components/layout/page-with-header.d.ts +0 -67
- package/dist/components/layout/page-with-header.d.ts.map +0 -1
- package/dist/components/ui/rich-markdown-renderer.d.ts +0 -34
- package/dist/components/ui/rich-markdown-renderer.d.ts.map +0 -1
- package/dist/utils/page-header-constants.d.ts +0 -15
- package/dist/utils/page-header-constants.d.ts.map +0 -1
- package/dist/utils/social-embed-cache.d.ts +0 -29
- package/dist/utils/social-embed-cache.d.ts.map +0 -1
- package/src/components/case-studies/index.ts +0 -4
- package/src/components/case-studies/share-experience-section.tsx +0 -185
- package/src/components/docs/docs-hub-page.tsx +0 -149
- package/src/components/docs/skeletons.tsx +0 -138
- package/src/components/docs/use-docs-resolve-link.ts +0 -52
- package/src/components/embeds/embed-container.tsx +0 -80
- package/src/components/embeds/file-download-card.tsx +0 -54
- package/src/components/embeds/linkedin-embed-client.tsx +0 -100
- package/src/components/embeds/markdown-image.tsx +0 -88
- package/src/components/embeds/reddit-embed-client.tsx +0 -550
- package/src/components/embeds/rich-markdown-runtime.tsx +0 -79
- package/src/components/embeds/twitter-embed-client.tsx +0 -308
- package/src/components/layout/page-header.tsx +0 -182
- package/src/components/layout/page-with-header.tsx +0 -110
- package/src/components/ui/rich-markdown-renderer.tsx +0 -1203
- package/src/utils/page-header-constants.ts +0 -15
- package/src/utils/social-embed-cache.ts +0 -391
- /package/dist/{chunk-PI4WSYQV.js.map → chunk-3ZXUQQL4.js.map} +0 -0
|
@@ -1,550 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useState, useEffect, useRef } from 'react';
|
|
4
|
-
import { socialCache } from '../../utils/social-embed-cache';
|
|
5
|
-
import { MediaCarousel } from '../media-carousel';
|
|
6
|
-
import { RedditContainer } from './embed-container';
|
|
7
|
-
import { formatLargeNumber } from '../../utils/format';
|
|
8
|
-
import { useRichMarkdownRuntime } from './rich-markdown-runtime';
|
|
9
|
-
import type { MediaItem as CarouselMediaItem } from '../../utils/media-carousel-utils-stub';
|
|
10
|
-
|
|
11
|
-
// Using inline SVG icons to avoid dependency issues
|
|
12
|
-
const MessageCircleIcon = () => (
|
|
13
|
-
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
14
|
-
<path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/>
|
|
15
|
-
</svg>
|
|
16
|
-
);
|
|
17
|
-
|
|
18
|
-
const ExternalLinkIcon = () => (
|
|
19
|
-
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
20
|
-
<path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6"/>
|
|
21
|
-
<polyline points="15,3 21,3 21,9"/>
|
|
22
|
-
<line x1="10" y1="14" x2="21" y2="3"/>
|
|
23
|
-
</svg>
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
const ArrowUpIcon = () => (
|
|
27
|
-
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
28
|
-
<polyline points="18,15 12,9 6,15"/>
|
|
29
|
-
</svg>
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
const ClockIcon = () => (
|
|
33
|
-
<svg width="12" height="12" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
34
|
-
<circle cx="12" cy="12" r="10"/>
|
|
35
|
-
<polyline points="12,6 12,12 16,14"/>
|
|
36
|
-
</svg>
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
const UserIcon = () => (
|
|
40
|
-
<svg width="12" height="12" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
41
|
-
<path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/>
|
|
42
|
-
<circle cx="12" cy="7" r="4"/>
|
|
43
|
-
</svg>
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
const RedditIcon = () => (
|
|
47
|
-
<svg width="20" height="20" fill="#FF4500" viewBox="0 0 24 24">
|
|
48
|
-
<circle cx="9" cy="12" r="1"/>
|
|
49
|
-
<circle cx="15" cy="12" r="1"/>
|
|
50
|
-
<path d="M22 12a2 2 0 1 0-4 0c0 5.5-4.5 10-10 10S-2 17.5-2 12a2 2 0 1 0-4 0c0 7.7 6.3 14 14 14s14-6.3 14-14z"/>
|
|
51
|
-
<path d="M8 10c0-1.1.9-2 2-2h4c1.1 0 2 .9 2 2"/>
|
|
52
|
-
</svg>
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
// Simplified Reddit profile picture component
|
|
56
|
-
const RedditProfilePic = ({ username }: { username: string }) => {
|
|
57
|
-
return (
|
|
58
|
-
<div className="w-8 h-8 bg-[#FF4500] rounded-full flex items-center justify-center flex-shrink-0">
|
|
59
|
-
<span className="text-white font-bold text-xs">u/</span>
|
|
60
|
-
</div>
|
|
61
|
-
);
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
interface RedditPost {
|
|
65
|
-
title: string;
|
|
66
|
-
selftext: string;
|
|
67
|
-
author: string;
|
|
68
|
-
subreddit: string;
|
|
69
|
-
created_utc: number;
|
|
70
|
-
ups: number;
|
|
71
|
-
num_comments: number;
|
|
72
|
-
url: string;
|
|
73
|
-
permalink: string;
|
|
74
|
-
preview?: {
|
|
75
|
-
images: Array<{
|
|
76
|
-
source: {
|
|
77
|
-
url: string;
|
|
78
|
-
width: number;
|
|
79
|
-
height: number;
|
|
80
|
-
};
|
|
81
|
-
resolutions: Array<{
|
|
82
|
-
url: string;
|
|
83
|
-
width: number;
|
|
84
|
-
height: number;
|
|
85
|
-
}>;
|
|
86
|
-
}>;
|
|
87
|
-
};
|
|
88
|
-
media?: {
|
|
89
|
-
reddit_video?: {
|
|
90
|
-
fallback_url: string;
|
|
91
|
-
height: number;
|
|
92
|
-
width: number;
|
|
93
|
-
is_gif: boolean;
|
|
94
|
-
};
|
|
95
|
-
};
|
|
96
|
-
secure_media?: {
|
|
97
|
-
reddit_video?: {
|
|
98
|
-
fallback_url: string;
|
|
99
|
-
height: number;
|
|
100
|
-
width: number;
|
|
101
|
-
is_gif: boolean;
|
|
102
|
-
};
|
|
103
|
-
};
|
|
104
|
-
post_hint?: string;
|
|
105
|
-
is_video?: boolean;
|
|
106
|
-
domain?: string;
|
|
107
|
-
gallery_data?: {
|
|
108
|
-
items: Array<{
|
|
109
|
-
media_id: string;
|
|
110
|
-
}>;
|
|
111
|
-
};
|
|
112
|
-
media_metadata?: Record<string, {
|
|
113
|
-
s: {
|
|
114
|
-
u: string;
|
|
115
|
-
x: number;
|
|
116
|
-
y: number;
|
|
117
|
-
};
|
|
118
|
-
}>;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Internal media-item shape; we cast to the carousel's expected MediaItem at
|
|
122
|
-
// the render boundary so we don't have to fabricate `id`s/`alt` for every push.
|
|
123
|
-
interface MediaItem {
|
|
124
|
-
type: 'image' | 'video';
|
|
125
|
-
src: string;
|
|
126
|
-
width: number;
|
|
127
|
-
height: number;
|
|
128
|
-
alt?: string;
|
|
129
|
-
isGif?: boolean;
|
|
130
|
-
poster?: string;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
interface RedditEmbedProps {
|
|
134
|
-
url: string;
|
|
135
|
-
maxWidth?: number;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export function RedditEmbedClient({ url, maxWidth = 700 }: RedditEmbedProps) {
|
|
139
|
-
const { redditProxyUrl } = useRichMarkdownRuntime();
|
|
140
|
-
const [redditData, setRedditData] = useState<RedditPost | null>(null);
|
|
141
|
-
const [loading, setLoading] = useState(true);
|
|
142
|
-
const [error, setError] = useState<string | null>(null);
|
|
143
|
-
const initializationDone = useRef(false);
|
|
144
|
-
|
|
145
|
-
useEffect(() => {
|
|
146
|
-
// Only run once
|
|
147
|
-
if (initializationDone.current) return;
|
|
148
|
-
initializationDone.current = true;
|
|
149
|
-
|
|
150
|
-
// Normalize the Reddit URL to JSON format
|
|
151
|
-
const jsonUrl = url.endsWith('.json') ? url : `${url.replace(/\/$/, '')}.json`;
|
|
152
|
-
|
|
153
|
-
// Reddit-specific data validator
|
|
154
|
-
const validateRedditData = (data: any): boolean => {
|
|
155
|
-
return data && Array.isArray(data) && data[0] && data[0].data && data[0].data.children && data[0].data.children[0];
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
// Use centralized cache hierarchy
|
|
159
|
-
socialCache.fetchWithHierarchy({
|
|
160
|
-
platform: 'reddit',
|
|
161
|
-
url: jsonUrl,
|
|
162
|
-
apiEndpoint: redditProxyUrl,
|
|
163
|
-
dataValidator: validateRedditData,
|
|
164
|
-
onDataUpdate: (data) => {
|
|
165
|
-
if (data[0]?.data?.children?.[0]?.data) {
|
|
166
|
-
setRedditData(data[0].data.children[0].data);
|
|
167
|
-
}
|
|
168
|
-
},
|
|
169
|
-
onError: (errorMsg) => setError(errorMsg),
|
|
170
|
-
onLoading: (loading) => setLoading(loading)
|
|
171
|
-
});
|
|
172
|
-
}, []); // Empty dependency array - only run once
|
|
173
|
-
|
|
174
|
-
if (loading) {
|
|
175
|
-
return (
|
|
176
|
-
<RedditContainer>
|
|
177
|
-
<div className="border border-ods-border rounded-lg p-6 bg-ods-card animate-pulse">
|
|
178
|
-
<div className="flex items-center space-x-3 mb-4">
|
|
179
|
-
<div className="w-12 h-12 bg-ods-border rounded-full"></div>
|
|
180
|
-
<div>
|
|
181
|
-
<div className="h-4 bg-ods-border rounded w-32 mb-2"></div>
|
|
182
|
-
<div className="h-3 bg-ods-border rounded w-24"></div>
|
|
183
|
-
</div>
|
|
184
|
-
</div>
|
|
185
|
-
<div className="space-y-2 mb-4">
|
|
186
|
-
<div className="h-4 bg-ods-border rounded w-full"></div>
|
|
187
|
-
<div className="h-4 bg-ods-border rounded w-3/4"></div>
|
|
188
|
-
</div>
|
|
189
|
-
<div className="flex items-center space-x-4">
|
|
190
|
-
<div className="h-4 bg-ods-border rounded w-16"></div>
|
|
191
|
-
<div className="h-4 bg-ods-border rounded w-16"></div>
|
|
192
|
-
<div className="h-4 bg-ods-border rounded w-16"></div>
|
|
193
|
-
</div>
|
|
194
|
-
</div>
|
|
195
|
-
</RedditContainer>
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (error || !redditData) {
|
|
200
|
-
return (
|
|
201
|
-
<RedditContainer>
|
|
202
|
-
<div className="border border-ods-border rounded-lg p-6 bg-ods-card">
|
|
203
|
-
<div className="flex items-center space-x-3 text-ods-text-secondary mb-4">
|
|
204
|
-
<RedditIcon />
|
|
205
|
-
<span>Reddit post unavailable</span>
|
|
206
|
-
</div>
|
|
207
|
-
|
|
208
|
-
<div className="text-center">
|
|
209
|
-
<p className="text-ods-text-secondary text-sm mb-4">
|
|
210
|
-
This Reddit post could not be loaded. It may have been deleted, made private, or the subreddit may be restricted.
|
|
211
|
-
</p>
|
|
212
|
-
<a
|
|
213
|
-
href={url}
|
|
214
|
-
target="_blank"
|
|
215
|
-
rel="noopener noreferrer"
|
|
216
|
-
className="inline-flex items-center space-x-2 px-4 py-2 bg-[#FF4500] text-white rounded-md text-sm font-medium hover:bg-[#E03D00] transition-colors"
|
|
217
|
-
>
|
|
218
|
-
<RedditIcon />
|
|
219
|
-
<span>View on Reddit</span>
|
|
220
|
-
</a>
|
|
221
|
-
</div>
|
|
222
|
-
</div>
|
|
223
|
-
</RedditContainer>
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Enhanced media extraction from Reddit post data
|
|
228
|
-
const getMediaContent = (): MediaItem[] => {
|
|
229
|
-
// FIRST: Check if the post has been removed or deleted - if so, don't extract media
|
|
230
|
-
// Reddit API exact-match indicators only. Previously this also did
|
|
231
|
-
// `title.toLowerCase().includes('removed' | 'deleted')` which suppressed
|
|
232
|
-
// legitimate posts whose titles mention those words (e.g. "Comment was
|
|
233
|
-
// removed by mods", "Deleted scenes from my favorite movie").
|
|
234
|
-
const isRemovedOrDeleted = redditData.selftext === '[removed]' ||
|
|
235
|
-
redditData.selftext === '[deleted]' ||
|
|
236
|
-
redditData.author === '[deleted]' ||
|
|
237
|
-
(redditData.title && redditData.title.includes('[removed]'));
|
|
238
|
-
|
|
239
|
-
if (isRemovedOrDeleted) {
|
|
240
|
-
console.log('🚫 Post content removed - skipping all media extraction for:', redditData.title);
|
|
241
|
-
return []; // Return empty media array for removed posts
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const media: MediaItem[] = [];
|
|
245
|
-
|
|
246
|
-
console.log('🔍 Reddit media extraction for:', redditData.title);
|
|
247
|
-
console.log('📊 Full Reddit data structure:', {
|
|
248
|
-
url: redditData.url,
|
|
249
|
-
domain: redditData.domain,
|
|
250
|
-
post_hint: redditData.post_hint,
|
|
251
|
-
is_video: redditData.is_video,
|
|
252
|
-
media: redditData.media,
|
|
253
|
-
secure_media: redditData.secure_media,
|
|
254
|
-
preview: redditData.preview,
|
|
255
|
-
gallery_data: redditData.gallery_data,
|
|
256
|
-
media_metadata: redditData.media_metadata
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
// 1. Check for Reddit hosted video (v.redd.it) - PRIORITY
|
|
260
|
-
const video = redditData.media?.reddit_video || redditData.secure_media?.reddit_video;
|
|
261
|
-
if (video && video.fallback_url) {
|
|
262
|
-
console.log('📹 Found Reddit video:', video);
|
|
263
|
-
|
|
264
|
-
// Generate poster URL from video URL and preview data
|
|
265
|
-
let posterUrl = '';
|
|
266
|
-
|
|
267
|
-
// Try to get poster from preview images first
|
|
268
|
-
if (redditData.preview?.images?.[0]?.source?.url) {
|
|
269
|
-
posterUrl = redditData.preview.images[0].source.url.replace(/&/g, '&');
|
|
270
|
-
console.log('✅ Using preview image as video poster:', posterUrl);
|
|
271
|
-
} else {
|
|
272
|
-
// Fallback: try to generate from video URL
|
|
273
|
-
try {
|
|
274
|
-
const baseUrl = video.fallback_url.replace(/DASH_\d+\.mp4.*$/, '');
|
|
275
|
-
posterUrl = `${baseUrl}DASH_720.jpg`;
|
|
276
|
-
console.log('🎯 Generated poster URL:', posterUrl);
|
|
277
|
-
} catch (e) {
|
|
278
|
-
console.log('Could not generate poster URL');
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Try to get a better video URL by replacing DASH format
|
|
283
|
-
let videoUrl = video.fallback_url;
|
|
284
|
-
|
|
285
|
-
// If it's a DASH URL, try to get a direct MP4 format
|
|
286
|
-
if (videoUrl.includes('DASH_')) {
|
|
287
|
-
// Try different quality levels for Reddit videos
|
|
288
|
-
const baseUrl = videoUrl.replace(/DASH_\d+\.mp4.*$/, '');
|
|
289
|
-
const qualities = ['480', '360', '720', '240']; // Start with 480p for better compatibility
|
|
290
|
-
|
|
291
|
-
// Use 480p as default for better compatibility
|
|
292
|
-
videoUrl = `${baseUrl}DASH_480.mp4`;
|
|
293
|
-
console.log('🎯 Optimized Reddit video URL for compatibility:', videoUrl);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
media.push({
|
|
297
|
-
type: 'video',
|
|
298
|
-
src: videoUrl,
|
|
299
|
-
width: video.width || 640,
|
|
300
|
-
height: video.height || 480,
|
|
301
|
-
isGif: video.is_gif || false,
|
|
302
|
-
poster: posterUrl
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
// Return early for videos to avoid showing preview images as well
|
|
306
|
-
console.log('📋 Final Reddit media (video):', media);
|
|
307
|
-
return media;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// 2. Check for Reddit gallery (multiple images)
|
|
311
|
-
if (redditData.media_metadata && redditData.gallery_data) {
|
|
312
|
-
console.log('🖼️ Found Reddit gallery');
|
|
313
|
-
const galleryItems = redditData.gallery_data.items || [];
|
|
314
|
-
|
|
315
|
-
for (const item of galleryItems) {
|
|
316
|
-
const mediaId = item.media_id;
|
|
317
|
-
const mediaInfo = redditData.media_metadata[mediaId];
|
|
318
|
-
|
|
319
|
-
if (mediaInfo && mediaInfo.s && mediaInfo.s.u) {
|
|
320
|
-
// Reddit encodes URLs, need to decode
|
|
321
|
-
const imageUrl = mediaInfo.s.u.replace(/&/g, '&');
|
|
322
|
-
console.log('✅ Adding gallery image:', imageUrl);
|
|
323
|
-
|
|
324
|
-
media.push({
|
|
325
|
-
type: 'image',
|
|
326
|
-
src: imageUrl,
|
|
327
|
-
width: mediaInfo.s.x || 0,
|
|
328
|
-
height: mediaInfo.s.y || 0,
|
|
329
|
-
alt: redditData.title
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
if (media.length > 0) {
|
|
335
|
-
console.log('📋 Final Reddit media (gallery):', media);
|
|
336
|
-
return media;
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// 3. Check for single image preview (but not if it's actually a video)
|
|
341
|
-
if (redditData.preview?.images?.[0] && !redditData.is_video) {
|
|
342
|
-
const imageData = redditData.preview.images[0];
|
|
343
|
-
console.log('🖼️ Found preview image data:', imageData);
|
|
344
|
-
|
|
345
|
-
// Use best resolution that fits our constraints
|
|
346
|
-
let source = imageData.source;
|
|
347
|
-
if (imageData.resolutions && imageData.resolutions.length > 0) {
|
|
348
|
-
// Find best resolution under 1200px width, or use source
|
|
349
|
-
const bestResolution = imageData.resolutions
|
|
350
|
-
.filter(r => r.width <= 1200)
|
|
351
|
-
.sort((a, b) => b.width - a.width)[0];
|
|
352
|
-
source = bestResolution || imageData.source;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
if (source && source.url) {
|
|
356
|
-
const cleanUrl = source.url.replace(/&/g, '&');
|
|
357
|
-
console.log('✅ Adding preview image:', cleanUrl);
|
|
358
|
-
media.push({
|
|
359
|
-
type: 'image',
|
|
360
|
-
src: cleanUrl,
|
|
361
|
-
width: source.width,
|
|
362
|
-
height: source.height,
|
|
363
|
-
alt: redditData.title
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// 4. Check for direct media URLs (imgur, i.redd.it, etc.) - only if no other media found
|
|
369
|
-
if (media.length === 0 && redditData.url) {
|
|
370
|
-
const directUrl = redditData.url.toLowerCase();
|
|
371
|
-
console.log('🔗 Checking direct URL:', directUrl);
|
|
372
|
-
|
|
373
|
-
// Image formats
|
|
374
|
-
if (directUrl.match(/\.(jpg|jpeg|png|gif|webp)(\?.*)?$/i)) {
|
|
375
|
-
console.log('📸 Found direct image URL');
|
|
376
|
-
media.push({
|
|
377
|
-
type: 'image',
|
|
378
|
-
src: redditData.url,
|
|
379
|
-
width: 0,
|
|
380
|
-
height: 0,
|
|
381
|
-
alt: redditData.title
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
// Video formats
|
|
385
|
-
else if (directUrl.match(/\.(mp4|webm|mov|avi)(\?.*)?$/i)) {
|
|
386
|
-
console.log('🎬 Found direct video URL');
|
|
387
|
-
|
|
388
|
-
// Try to generate poster from preview if available
|
|
389
|
-
let posterUrl = '';
|
|
390
|
-
if (redditData.preview?.images?.[0]?.source?.url) {
|
|
391
|
-
posterUrl = redditData.preview.images[0].source.url.replace(/&/g, '&');
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
media.push({
|
|
395
|
-
type: 'video',
|
|
396
|
-
src: redditData.url,
|
|
397
|
-
width: 0,
|
|
398
|
-
height: 0,
|
|
399
|
-
isGif: false,
|
|
400
|
-
poster: posterUrl
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
// Special handling for imgur
|
|
404
|
-
else if (directUrl.includes('imgur.com') && !directUrl.includes('.gifv')) {
|
|
405
|
-
console.log('🌐 Found Imgur link');
|
|
406
|
-
// Convert imgur links to direct image links
|
|
407
|
-
const imgurId = directUrl.match(/imgur\.com\/([a-zA-Z0-9]+)/)?.[1];
|
|
408
|
-
if (imgurId && !directUrl.includes('/a/') && !directUrl.includes('/gallery/')) {
|
|
409
|
-
// Try both jpg and png
|
|
410
|
-
media.push({
|
|
411
|
-
type: 'image',
|
|
412
|
-
src: `https://i.imgur.com/${imgurId}.jpg`,
|
|
413
|
-
width: 0,
|
|
414
|
-
height: 0,
|
|
415
|
-
alt: redditData.title
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
// i.redd.it images
|
|
420
|
-
else if (directUrl.includes('i.redd.it')) {
|
|
421
|
-
console.log('🖼️ Found i.redd.it image');
|
|
422
|
-
media.push({
|
|
423
|
-
type: 'image',
|
|
424
|
-
src: redditData.url,
|
|
425
|
-
width: 0,
|
|
426
|
-
height: 0,
|
|
427
|
-
alt: redditData.title
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
console.log('📋 Final Reddit media array:', media);
|
|
433
|
-
return media;
|
|
434
|
-
};
|
|
435
|
-
|
|
436
|
-
const mediaContent = getMediaContent();
|
|
437
|
-
// Lib's `MediaCarousel` expects items shaped like the carousel-utils-stub
|
|
438
|
-
// `MediaItem` (which has a required `id`). Reddit constructs items without an
|
|
439
|
-
// `id`, so we synthesize one at the boundary. Keep the runtime cast — lib
|
|
440
|
-
// carousel keys by index and only reads `.type`/`.src`/`.poster`/`.alt`.
|
|
441
|
-
const carouselItems: CarouselMediaItem[] = mediaContent.map((m, i) => ({
|
|
442
|
-
id: `reddit-${i}`,
|
|
443
|
-
type: m.type,
|
|
444
|
-
src: m.src,
|
|
445
|
-
poster: m.poster,
|
|
446
|
-
alt: m.alt,
|
|
447
|
-
width: m.width,
|
|
448
|
-
height: m.height,
|
|
449
|
-
}));
|
|
450
|
-
|
|
451
|
-
console.log('🎬 MediaCarousel will render with:', mediaContent.length, 'items', mediaContent);
|
|
452
|
-
|
|
453
|
-
// Format time
|
|
454
|
-
const formatTimeAgo = (timestamp: number) => {
|
|
455
|
-
const now = Math.floor(Date.now() / 1000);
|
|
456
|
-
const diffSeconds = now - timestamp;
|
|
457
|
-
|
|
458
|
-
if (diffSeconds < 60) return 'just now';
|
|
459
|
-
if (diffSeconds < 3600) return `${Math.floor(diffSeconds / 60)}m ago`;
|
|
460
|
-
if (diffSeconds < 86400) return `${Math.floor(diffSeconds / 3600)}h ago`;
|
|
461
|
-
return `${Math.floor(diffSeconds / 86400)}d ago`;
|
|
462
|
-
};
|
|
463
|
-
|
|
464
|
-
// Format numbers using utility function
|
|
465
|
-
const formatNumber = formatLargeNumber;
|
|
466
|
-
|
|
467
|
-
const truncateText = (text: string, maxLength: number = 600) => {
|
|
468
|
-
if (text.length <= maxLength) return text;
|
|
469
|
-
return text.slice(0, maxLength) + '...';
|
|
470
|
-
};
|
|
471
|
-
|
|
472
|
-
return (
|
|
473
|
-
<RedditContainer>
|
|
474
|
-
<div className="border border-ods-border rounded-lg bg-ods-card overflow-hidden">
|
|
475
|
-
{/* Header with Profile Picture */}
|
|
476
|
-
<div className="p-4 border-b border-ods-border">
|
|
477
|
-
<div className="flex items-center justify-between">
|
|
478
|
-
<div className="flex items-center space-x-3">
|
|
479
|
-
{/* Lazy-loaded User Profile Picture */}
|
|
480
|
-
<div className="w-8 h-8 rounded-full overflow-hidden flex-shrink-0">
|
|
481
|
-
<RedditProfilePic username={redditData.author} />
|
|
482
|
-
</div>
|
|
483
|
-
<div>
|
|
484
|
-
<p className="text-ods-text-primary font-medium">r/{redditData.subreddit}</p>
|
|
485
|
-
<div className="flex items-center space-x-2 text-ods-text-secondary text-sm">
|
|
486
|
-
<UserIcon />
|
|
487
|
-
<span>u/{redditData.author}</span>
|
|
488
|
-
<ClockIcon />
|
|
489
|
-
<span>{formatTimeAgo(redditData.created_utc)}</span>
|
|
490
|
-
</div>
|
|
491
|
-
</div>
|
|
492
|
-
</div>
|
|
493
|
-
</div>
|
|
494
|
-
</div>
|
|
495
|
-
|
|
496
|
-
{/* Content - Matching Twitter Style */}
|
|
497
|
-
<div className="p-4">
|
|
498
|
-
<h3 className="text-ods-text-primary font-semibold text-lg mb-3 leading-tight">
|
|
499
|
-
{redditData.title}
|
|
500
|
-
</h3>
|
|
501
|
-
|
|
502
|
-
{redditData.selftext && (
|
|
503
|
-
<div
|
|
504
|
-
className="text-ods-text-secondary text-sm leading-relaxed mb-4 overflow-hidden"
|
|
505
|
-
style={{ maxHeight: `${maxWidth - 200}px` }}
|
|
506
|
-
>
|
|
507
|
-
<p className="whitespace-pre-wrap">
|
|
508
|
-
{truncateText(redditData.selftext)}
|
|
509
|
-
</p>
|
|
510
|
-
</div>
|
|
511
|
-
)}
|
|
512
|
-
|
|
513
|
-
{/* Enhanced Media Section with Carousel */}
|
|
514
|
-
{carouselItems.length > 0 && (
|
|
515
|
-
<MediaCarousel
|
|
516
|
-
media={carouselItems}
|
|
517
|
-
aspectRatio="16/9"
|
|
518
|
-
showThumbnails={carouselItems.length > 1}
|
|
519
|
-
/>
|
|
520
|
-
)}
|
|
521
|
-
|
|
522
|
-
{/* Stats - Matching Twitter Style */}
|
|
523
|
-
<div className="flex items-center space-x-6 text-ods-text-secondary text-sm">
|
|
524
|
-
<div className="flex items-center space-x-1">
|
|
525
|
-
<ArrowUpIcon />
|
|
526
|
-
<span>{formatNumber(redditData.ups)} upvotes</span>
|
|
527
|
-
</div>
|
|
528
|
-
<div className="flex items-center space-x-1">
|
|
529
|
-
<MessageCircleIcon />
|
|
530
|
-
<span>{formatNumber(redditData.num_comments)} comments</span>
|
|
531
|
-
</div>
|
|
532
|
-
</div>
|
|
533
|
-
</div>
|
|
534
|
-
|
|
535
|
-
{/* Footer - Matching Twitter Style */}
|
|
536
|
-
<div className="px-4 py-3 bg-ods-bg-secondary border-t border-ods-border">
|
|
537
|
-
<a
|
|
538
|
-
href={url}
|
|
539
|
-
target="_blank"
|
|
540
|
-
rel="noopener noreferrer"
|
|
541
|
-
className="inline-flex items-center space-x-2 text-ods-accent hover:opacity-80 transition-colors text-sm font-medium"
|
|
542
|
-
>
|
|
543
|
-
<ExternalLinkIcon />
|
|
544
|
-
<span>View on Reddit</span>
|
|
545
|
-
</a>
|
|
546
|
-
</div>
|
|
547
|
-
</div>
|
|
548
|
-
</RedditContainer>
|
|
549
|
-
);
|
|
550
|
-
}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { createContext, useContext, useMemo, type ReactNode } from 'react'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Runtime knobs threaded from `<RichMarkdownRenderer>` down to its
|
|
7
|
-
* satellite embed clients (reddit / twitter / og link preview / markdown
|
|
8
|
-
* image). Lives in its own micro-context — distinct from the
|
|
9
|
-
* `ChatRuntimeContext` used by the lib's chat module — because the
|
|
10
|
-
* markdown renderer is mounted from documentation pages (blog, legal,
|
|
11
|
-
* data room, knowledge base) that do NOT have a chat runtime in scope,
|
|
12
|
-
* and the satellites need just three knobs:
|
|
13
|
-
*
|
|
14
|
-
* - WHERE to fetch the reddit / twitter / OG-scrape proxy
|
|
15
|
-
* - HOW to transform a markdown image URL (the hub injects its
|
|
16
|
-
* Supabase image transformer; embedders pass null / identity)
|
|
17
|
-
*
|
|
18
|
-
* Defaults match the hub's existing endpoints so passing
|
|
19
|
-
* `<RichMarkdownRenderer>` no props at all still works end-to-end on
|
|
20
|
-
* the hub. Embedders override per-prop as needed.
|
|
21
|
-
*/
|
|
22
|
-
export interface RichMarkdownRuntime {
|
|
23
|
-
redditProxyUrl: string
|
|
24
|
-
twitterProxyUrl: string
|
|
25
|
-
ogScraperUrl: string
|
|
26
|
-
/** Hub-only Supabase image transformer. Returning null means "don't
|
|
27
|
-
* rewrite — use the src as-is". Defaults to identity (no rewrite). */
|
|
28
|
-
transformImageSrc: (
|
|
29
|
-
src: string,
|
|
30
|
-
opts?: { width?: number; quality?: number; resize?: 'cover' | 'contain' | 'fill' }
|
|
31
|
-
) => string | null
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const DEFAULT_RUNTIME: RichMarkdownRuntime = {
|
|
35
|
-
redditProxyUrl: '/api/blog/reddit-proxy',
|
|
36
|
-
twitterProxyUrl: '/api/blog/twitter-proxy',
|
|
37
|
-
ogScraperUrl: '/api/blog/og-scraper',
|
|
38
|
-
transformImageSrc: () => null,
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const RichMarkdownRuntimeContext = createContext<RichMarkdownRuntime>(DEFAULT_RUNTIME)
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Provider that fills in defaults for any prop the caller didn't pass.
|
|
45
|
-
* Memoizes the resolved runtime so satellites don't re-render when an
|
|
46
|
-
* unrelated parent state ticks.
|
|
47
|
-
*/
|
|
48
|
-
export function RichMarkdownRuntimeProvider({
|
|
49
|
-
redditProxyUrl,
|
|
50
|
-
twitterProxyUrl,
|
|
51
|
-
ogScraperUrl,
|
|
52
|
-
transformImageSrc,
|
|
53
|
-
children,
|
|
54
|
-
}: Partial<RichMarkdownRuntime> & { children: ReactNode }) {
|
|
55
|
-
const value = useMemo<RichMarkdownRuntime>(
|
|
56
|
-
() => ({
|
|
57
|
-
redditProxyUrl: redditProxyUrl ?? DEFAULT_RUNTIME.redditProxyUrl,
|
|
58
|
-
twitterProxyUrl: twitterProxyUrl ?? DEFAULT_RUNTIME.twitterProxyUrl,
|
|
59
|
-
ogScraperUrl: ogScraperUrl ?? DEFAULT_RUNTIME.ogScraperUrl,
|
|
60
|
-
transformImageSrc: transformImageSrc ?? DEFAULT_RUNTIME.transformImageSrc,
|
|
61
|
-
}),
|
|
62
|
-
[redditProxyUrl, twitterProxyUrl, ogScraperUrl, transformImageSrc],
|
|
63
|
-
)
|
|
64
|
-
return (
|
|
65
|
-
<RichMarkdownRuntimeContext.Provider value={value}>
|
|
66
|
-
{children}
|
|
67
|
-
</RichMarkdownRuntimeContext.Provider>
|
|
68
|
-
)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Read the ambient runtime. Returns the defaults when called outside any
|
|
73
|
-
* `RichMarkdownRuntimeProvider` — so embedders that drop a satellite into
|
|
74
|
-
* a non-renderer context (e.g. a release page calling
|
|
75
|
-
* `<RedditEmbedClient>` directly) get the hub-matching defaults for free.
|
|
76
|
-
*/
|
|
77
|
-
export function useRichMarkdownRuntime(): RichMarkdownRuntime {
|
|
78
|
-
return useContext(RichMarkdownRuntimeContext)
|
|
79
|
-
}
|