@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.
Files changed (276) hide show
  1. package/README.md +0 -9
  2. package/dist/chunk-26PKDALD.js +2379 -0
  3. package/dist/chunk-26PKDALD.js.map +1 -0
  4. package/dist/chunk-3MCHAFHB.js +89 -0
  5. package/dist/chunk-3MCHAFHB.js.map +1 -0
  6. package/dist/{chunk-PI4WSYQV.js → chunk-3ZXUQQL4.js} +2 -2
  7. package/dist/{chunk-WMSTJAZT.cjs → chunk-5E2HOSSH.cjs} +51 -913
  8. package/dist/chunk-5E2HOSSH.cjs.map +1 -0
  9. package/dist/{chunk-IL47XWV5.js → chunk-5P3B2LZW.js} +14 -8
  10. package/dist/{chunk-IL47XWV5.js.map → chunk-5P3B2LZW.js.map} +1 -1
  11. package/dist/chunk-66AANIOC.cjs +619 -0
  12. package/dist/chunk-66AANIOC.cjs.map +1 -0
  13. package/dist/{chunk-AD6C23QY.js → chunk-6GCI7JOE.js} +7 -8
  14. package/dist/{chunk-AD6C23QY.js.map → chunk-6GCI7JOE.js.map} +1 -1
  15. package/dist/chunk-6JINAOI7.cjs +311 -0
  16. package/dist/chunk-6JINAOI7.cjs.map +1 -0
  17. package/dist/{chunk-2QG57XOJ.js → chunk-7RIYT7ZH.js} +205 -1067
  18. package/dist/chunk-7RIYT7ZH.js.map +1 -0
  19. package/dist/{chunk-L6PSSIUQ.cjs → chunk-AQOWFSMB.cjs} +1 -1
  20. package/dist/chunk-AQOWFSMB.cjs.map +1 -0
  21. package/dist/chunk-BOCFIKYS.cjs +3009 -0
  22. package/dist/chunk-BOCFIKYS.cjs.map +1 -0
  23. package/dist/{chunk-54KNMC2R.cjs → chunk-D3LEFMOA.cjs} +3 -3
  24. package/dist/{chunk-54KNMC2R.cjs.map → chunk-D3LEFMOA.cjs.map} +1 -1
  25. package/dist/chunk-D652TJBQ.js +3009 -0
  26. package/dist/chunk-D652TJBQ.js.map +1 -0
  27. package/dist/{chunk-PWQUAVA3.js → chunk-E4XABBSU.js} +98 -338
  28. package/dist/chunk-E4XABBSU.js.map +1 -0
  29. package/dist/{chunk-JALO4TAZ.js → chunk-EL6QLAWX.js} +55 -357
  30. package/dist/chunk-EL6QLAWX.js.map +1 -0
  31. package/dist/{chunk-6C526VNN.cjs → chunk-EYEW6PTA.cjs} +118 -358
  32. package/dist/chunk-EYEW6PTA.cjs.map +1 -0
  33. package/dist/chunk-FQJK446R.js +1606 -0
  34. package/dist/chunk-FQJK446R.js.map +1 -0
  35. package/dist/{chunk-4PSQS3SW.cjs → chunk-GLLDTKZK.cjs} +9 -7
  36. package/dist/chunk-GLLDTKZK.cjs.map +1 -0
  37. package/dist/{chunk-FQOTC3UU.cjs → chunk-IE6OU3WQ.cjs} +16 -318
  38. package/dist/chunk-IE6OU3WQ.cjs.map +1 -0
  39. package/dist/chunk-J54Z3OCR.cjs +1606 -0
  40. package/dist/chunk-J54Z3OCR.cjs.map +1 -0
  41. package/dist/{chunk-PC746XCO.js → chunk-K2PFPBMF.js} +5563 -15048
  42. package/dist/chunk-K2PFPBMF.js.map +1 -0
  43. package/dist/chunk-KXCRGTRN.cjs +2379 -0
  44. package/dist/chunk-KXCRGTRN.cjs.map +1 -0
  45. package/dist/{chunk-IZ7JSBFP.js → chunk-LCNMR277.js} +1 -1
  46. package/dist/chunk-LCNMR277.js.map +1 -0
  47. package/dist/chunk-LFGGF7OT.cjs +449 -0
  48. package/dist/chunk-LFGGF7OT.cjs.map +1 -0
  49. package/dist/chunk-M2OCXTNT.js +311 -0
  50. package/dist/chunk-M2OCXTNT.js.map +1 -0
  51. package/dist/{chunk-L7ULJKG7.js → chunk-MBFWU2EM.js} +10 -6
  52. package/dist/{chunk-L7ULJKG7.js.map → chunk-MBFWU2EM.js.map} +1 -1
  53. package/dist/chunk-ME4EVDFP.js +619 -0
  54. package/dist/chunk-ME4EVDFP.js.map +1 -0
  55. package/dist/chunk-OQ6X7ZOC.js +449 -0
  56. package/dist/chunk-OQ6X7ZOC.js.map +1 -0
  57. package/dist/{chunk-4TLE6VLU.js → chunk-OY7OF7E7.js} +24 -30
  58. package/dist/chunk-OY7OF7E7.js.map +1 -0
  59. package/dist/chunk-POKKCWKF.js +354 -0
  60. package/dist/chunk-POKKCWKF.js.map +1 -0
  61. package/dist/{chunk-GUTS7HGA.cjs → chunk-QHIXS3W2.cjs} +2514 -11999
  62. package/dist/chunk-QHIXS3W2.cjs.map +1 -0
  63. package/dist/chunk-TFSYSWPS.cjs +89 -0
  64. package/dist/chunk-TFSYSWPS.cjs.map +1 -0
  65. package/dist/{chunk-53FUMSZ5.cjs → chunk-W6M2FLLT.cjs} +46 -40
  66. package/dist/chunk-W6M2FLLT.cjs.map +1 -0
  67. package/dist/{chunk-3JIQVE7T.js → chunk-WHMATDVP.js} +15 -9
  68. package/dist/{chunk-3JIQVE7T.js.map → chunk-WHMATDVP.js.map} +1 -1
  69. package/dist/{chunk-YBYI62OE.cjs → chunk-X647HY3F.cjs} +37 -33
  70. package/dist/chunk-X647HY3F.cjs.map +1 -0
  71. package/dist/{chunk-UNVE2SDJ.cjs → chunk-X6BV7MB7.cjs} +31 -37
  72. package/dist/chunk-X6BV7MB7.cjs.map +1 -0
  73. package/dist/{chunk-7OVGB2DQ.cjs → chunk-XREEV72C.cjs} +25 -19
  74. package/dist/chunk-XREEV72C.cjs.map +1 -0
  75. package/dist/chunk-YETA25JW.cjs +354 -0
  76. package/dist/chunk-YETA25JW.cjs.map +1 -0
  77. package/dist/{chunk-FCDQNTDG.cjs → chunk-YIGPRLQY.cjs} +20 -21
  78. package/dist/chunk-YIGPRLQY.cjs.map +1 -0
  79. package/dist/{chunk-X4DOXQRT.js → chunk-ZP4AVIZP.js} +6 -4
  80. package/dist/{chunk-X4DOXQRT.js.map → chunk-ZP4AVIZP.js.map} +1 -1
  81. package/dist/components/chat/index.cjs +18 -8
  82. package/dist/components/chat/index.cjs.map +1 -1
  83. package/dist/components/chat/index.js +85 -75
  84. package/dist/components/contact/index.cjs +15 -8
  85. package/dist/components/contact/index.cjs.map +1 -1
  86. package/dist/components/contact/index.js +14 -7
  87. package/dist/components/docs/doc-viewer.d.ts +2 -39
  88. package/dist/components/docs/doc-viewer.d.ts.map +1 -1
  89. package/dist/components/docs/index.cjs +9 -17
  90. package/dist/components/docs/index.cjs.map +1 -1
  91. package/dist/components/docs/index.d.ts +0 -4
  92. package/dist/components/docs/index.d.ts.map +1 -1
  93. package/dist/components/docs/index.js +8 -16
  94. package/dist/components/docs/use-document-tree.d.ts.map +1 -1
  95. package/dist/components/embeds/embed-iframe.d.ts.map +1 -1
  96. package/dist/components/embeds/index.cjs +15 -38
  97. package/dist/components/embeds/index.cjs.map +1 -1
  98. package/dist/components/embeds/index.d.ts +0 -8
  99. package/dist/components/embeds/index.d.ts.map +1 -1
  100. package/dist/components/embeds/index.js +17 -40
  101. package/dist/components/faq/index.cjs +16 -9
  102. package/dist/components/faq/index.cjs.map +1 -1
  103. package/dist/components/faq/index.js +15 -8
  104. package/dist/components/features/index.cjs +16 -8
  105. package/dist/components/features/index.cjs.map +1 -1
  106. package/dist/components/features/index.js +32 -24
  107. package/dist/components/index.cjs +452 -257
  108. package/dist/components/index.cjs.map +1 -1
  109. package/dist/components/index.js +976 -781
  110. package/dist/components/index.js.map +1 -1
  111. package/dist/components/layout/page-layout.d.ts +1 -10
  112. package/dist/components/layout/page-layout.d.ts.map +1 -1
  113. package/dist/components/layout/title-block.d.ts +1 -17
  114. package/dist/components/layout/title-block.d.ts.map +1 -1
  115. package/dist/components/navigation/index.cjs +15 -7
  116. package/dist/components/navigation/index.cjs.map +1 -1
  117. package/dist/components/navigation/index.js +17 -9
  118. package/dist/components/onboarding-guides/index.cjs +36 -35
  119. package/dist/components/onboarding-guides/index.cjs.map +1 -1
  120. package/dist/components/onboarding-guides/index.js +14 -13
  121. package/dist/components/onboarding-guides/index.js.map +1 -1
  122. package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts +1 -1
  123. package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts.map +1 -1
  124. package/dist/components/related-content/index.cjs +16 -9
  125. package/dist/components/related-content/index.cjs.map +1 -1
  126. package/dist/components/related-content/index.js +15 -8
  127. package/dist/components/shared/dev-section/dev-section-page.d.ts +0 -9
  128. package/dist/components/shared/dev-section/dev-section-page.d.ts.map +1 -1
  129. package/dist/components/shared/dev-section/dev-section-view.d.ts.map +1 -1
  130. package/dist/components/shared/dev-section/index.d.ts +1 -1
  131. package/dist/components/shared/dev-section/index.d.ts.map +1 -1
  132. package/dist/components/shared/doc-search/use-doc-search.d.ts.map +1 -1
  133. package/dist/components/shared/legal-document/legal-document-page.d.ts.map +1 -1
  134. package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
  135. package/dist/components/tickets/index.cjs +112 -100
  136. package/dist/components/tickets/index.cjs.map +1 -1
  137. package/dist/components/tickets/index.js +32 -20
  138. package/dist/components/tickets/index.js.map +1 -1
  139. package/dist/components/ui/file-manager/index.cjs +52 -50
  140. package/dist/components/ui/file-manager/index.cjs.map +1 -1
  141. package/dist/components/ui/file-manager/index.js +6 -4
  142. package/dist/components/ui/file-manager/index.js.map +1 -1
  143. package/dist/components/ui/index.cjs +19 -13
  144. package/dist/components/ui/index.cjs.map +1 -1
  145. package/dist/components/ui/index.d.ts +0 -2
  146. package/dist/components/ui/index.d.ts.map +1 -1
  147. package/dist/components/ui/index.js +139 -133
  148. package/dist/components/ui/release-changelog-section.d.ts +2 -6
  149. package/dist/components/ui/release-changelog-section.d.ts.map +1 -1
  150. package/dist/components/ui/simple-markdown-renderer.d.ts +8 -2
  151. package/dist/components/ui/simple-markdown-renderer.d.ts.map +1 -1
  152. package/dist/contexts/chat-runtime-context.d.ts +0 -14
  153. package/dist/contexts/chat-runtime-context.d.ts.map +1 -1
  154. package/dist/contexts/index.cjs +3 -3
  155. package/dist/contexts/index.js +5 -5
  156. package/dist/embed-shims/index.cjs +3 -3
  157. package/dist/embed-shims/index.cjs.map +1 -1
  158. package/dist/embed-shims/index.js +4 -4
  159. package/dist/hooks/index.cjs +9 -4
  160. package/dist/hooks/index.cjs.map +1 -1
  161. package/dist/hooks/index.js +11 -6
  162. package/dist/index.cjs +20 -14
  163. package/dist/index.cjs.map +1 -1
  164. package/dist/index.js +364 -358
  165. package/dist/types/doc-source.d.ts +1 -31
  166. package/dist/types/doc-source.d.ts.map +1 -1
  167. package/dist/utils/index.cjs +0 -4
  168. package/dist/utils/index.cjs.map +1 -1
  169. package/dist/utils/index.d.ts +0 -1
  170. package/dist/utils/index.d.ts.map +1 -1
  171. package/dist/utils/index.js +1 -4
  172. package/dist/utils/index.js.map +1 -1
  173. package/package.json +1 -7
  174. package/src/components/chat/embeddable-chat.tsx +1 -1
  175. package/src/components/docs/doc-viewer.tsx +19 -111
  176. package/src/components/docs/index.ts +0 -17
  177. package/src/components/docs/use-document-tree.ts +0 -21
  178. package/src/components/embeds/embed-iframe.tsx +9 -7
  179. package/src/components/embeds/index.ts +0 -30
  180. package/src/components/embeds/og-link-preview.tsx +13 -13
  181. package/src/components/layout/page-layout.tsx +1 -14
  182. package/src/components/layout/title-block.tsx +62 -40
  183. package/src/components/onboarding-guides/onboarding-guide-detail-view.tsx +3 -3
  184. package/src/components/shared/dev-section/dev-section-page.tsx +1 -9
  185. package/src/components/shared/dev-section/dev-section-view.tsx +9 -14
  186. package/src/components/shared/dev-section/index.ts +1 -1
  187. package/src/components/shared/doc-search/use-doc-search.ts +3 -7
  188. package/src/components/shared/legal-document/legal-document-page.tsx +2 -2
  189. package/src/components/shared/product-release/release-detail-page.tsx +4 -6
  190. package/src/components/ui/index.ts +0 -2
  191. package/src/components/ui/release-changelog-section.tsx +2 -7
  192. package/src/components/ui/simple-markdown-renderer.tsx +11 -7
  193. package/src/contexts/chat-runtime-context.tsx +0 -14
  194. package/src/types/doc-source.ts +1 -33
  195. package/src/utils/index.ts +0 -1
  196. package/dist/chunk-2QG57XOJ.js.map +0 -1
  197. package/dist/chunk-4PSQS3SW.cjs.map +0 -1
  198. package/dist/chunk-4TLE6VLU.js.map +0 -1
  199. package/dist/chunk-53FUMSZ5.cjs.map +0 -1
  200. package/dist/chunk-6C526VNN.cjs.map +0 -1
  201. package/dist/chunk-7OVGB2DQ.cjs.map +0 -1
  202. package/dist/chunk-F5OB2YAL.cjs +0 -144
  203. package/dist/chunk-F5OB2YAL.cjs.map +0 -1
  204. package/dist/chunk-FBWXMMRB.cjs +0 -2
  205. package/dist/chunk-FBWXMMRB.cjs.map +0 -1
  206. package/dist/chunk-FCDQNTDG.cjs.map +0 -1
  207. package/dist/chunk-FQOTC3UU.cjs.map +0 -1
  208. package/dist/chunk-GUTS7HGA.cjs.map +0 -1
  209. package/dist/chunk-GZ4C3XW6.js +0 -2
  210. package/dist/chunk-GZ4C3XW6.js.map +0 -1
  211. package/dist/chunk-IZ7JSBFP.js.map +0 -1
  212. package/dist/chunk-JALO4TAZ.js.map +0 -1
  213. package/dist/chunk-L6PSSIUQ.cjs.map +0 -1
  214. package/dist/chunk-PC746XCO.js.map +0 -1
  215. package/dist/chunk-PWQUAVA3.js.map +0 -1
  216. package/dist/chunk-SA2WPJVO.js +0 -144
  217. package/dist/chunk-SA2WPJVO.js.map +0 -1
  218. package/dist/chunk-UNVE2SDJ.cjs.map +0 -1
  219. package/dist/chunk-WMSTJAZT.cjs.map +0 -1
  220. package/dist/chunk-YBYI62OE.cjs.map +0 -1
  221. package/dist/components/case-studies/index.cjs +0 -126
  222. package/dist/components/case-studies/index.cjs.map +0 -1
  223. package/dist/components/case-studies/index.d.ts +0 -2
  224. package/dist/components/case-studies/index.d.ts.map +0 -1
  225. package/dist/components/case-studies/index.js +0 -126
  226. package/dist/components/case-studies/index.js.map +0 -1
  227. package/dist/components/case-studies/share-experience-section.d.ts +0 -48
  228. package/dist/components/case-studies/share-experience-section.d.ts.map +0 -1
  229. package/dist/components/docs/docs-hub-page.d.ts +0 -46
  230. package/dist/components/docs/docs-hub-page.d.ts.map +0 -1
  231. package/dist/components/docs/skeletons.d.ts +0 -32
  232. package/dist/components/docs/skeletons.d.ts.map +0 -1
  233. package/dist/components/docs/use-docs-resolve-link.d.ts +0 -20
  234. package/dist/components/docs/use-docs-resolve-link.d.ts.map +0 -1
  235. package/dist/components/embeds/embed-container.d.ts +0 -37
  236. package/dist/components/embeds/embed-container.d.ts.map +0 -1
  237. package/dist/components/embeds/file-download-card.d.ts +0 -18
  238. package/dist/components/embeds/file-download-card.d.ts.map +0 -1
  239. package/dist/components/embeds/linkedin-embed-client.d.ts +0 -8
  240. package/dist/components/embeds/linkedin-embed-client.d.ts.map +0 -1
  241. package/dist/components/embeds/markdown-image.d.ts +0 -5
  242. package/dist/components/embeds/markdown-image.d.ts.map +0 -1
  243. package/dist/components/embeds/reddit-embed-client.d.ts +0 -7
  244. package/dist/components/embeds/reddit-embed-client.d.ts.map +0 -1
  245. package/dist/components/embeds/rich-markdown-runtime.d.ts +0 -46
  246. package/dist/components/embeds/rich-markdown-runtime.d.ts.map +0 -1
  247. package/dist/components/embeds/twitter-embed-client.d.ts +0 -8
  248. package/dist/components/embeds/twitter-embed-client.d.ts.map +0 -1
  249. package/dist/components/layout/page-header.d.ts +0 -78
  250. package/dist/components/layout/page-header.d.ts.map +0 -1
  251. package/dist/components/layout/page-with-header.d.ts +0 -67
  252. package/dist/components/layout/page-with-header.d.ts.map +0 -1
  253. package/dist/components/ui/rich-markdown-renderer.d.ts +0 -34
  254. package/dist/components/ui/rich-markdown-renderer.d.ts.map +0 -1
  255. package/dist/utils/page-header-constants.d.ts +0 -15
  256. package/dist/utils/page-header-constants.d.ts.map +0 -1
  257. package/dist/utils/social-embed-cache.d.ts +0 -29
  258. package/dist/utils/social-embed-cache.d.ts.map +0 -1
  259. package/src/components/case-studies/index.ts +0 -4
  260. package/src/components/case-studies/share-experience-section.tsx +0 -185
  261. package/src/components/docs/docs-hub-page.tsx +0 -149
  262. package/src/components/docs/skeletons.tsx +0 -138
  263. package/src/components/docs/use-docs-resolve-link.ts +0 -52
  264. package/src/components/embeds/embed-container.tsx +0 -80
  265. package/src/components/embeds/file-download-card.tsx +0 -54
  266. package/src/components/embeds/linkedin-embed-client.tsx +0 -100
  267. package/src/components/embeds/markdown-image.tsx +0 -88
  268. package/src/components/embeds/reddit-embed-client.tsx +0 -550
  269. package/src/components/embeds/rich-markdown-runtime.tsx +0 -79
  270. package/src/components/embeds/twitter-embed-client.tsx +0 -308
  271. package/src/components/layout/page-header.tsx +0 -182
  272. package/src/components/layout/page-with-header.tsx +0 -110
  273. package/src/components/ui/rich-markdown-renderer.tsx +0 -1203
  274. package/src/utils/page-header-constants.ts +0 -15
  275. package/src/utils/social-embed-cache.ts +0 -391
  276. /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(/&amp;/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(/&amp;/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(/&amp;/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(/&amp;/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
- }