@flamingo-stack/openframe-frontend-core 0.0.295 → 0.0.296-snapshot.20260621021605
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 +9 -0
- package/dist/{chunk-7RIYT7ZH.js → chunk-2QG57XOJ.js} +1067 -205
- package/dist/chunk-2QG57XOJ.js.map +1 -0
- package/dist/{chunk-7KXD7CWD.js → chunk-3JIQVE7T.js} +9 -15
- package/dist/{chunk-7KXD7CWD.js.map → chunk-3JIQVE7T.js.map} +1 -1
- package/dist/{chunk-FT4FCV7L.cjs → chunk-4PSQS3SW.cjs} +7 -9
- package/dist/chunk-4PSQS3SW.cjs.map +1 -0
- package/dist/{chunk-OOKKGOPQ.js → chunk-4TLE6VLU.js} +30 -24
- package/dist/chunk-4TLE6VLU.js.map +1 -0
- package/dist/{chunk-6IBA2MQV.cjs → chunk-53FUMSZ5.cjs} +40 -46
- package/dist/chunk-53FUMSZ5.cjs.map +1 -0
- package/dist/{chunk-D3LEFMOA.cjs → chunk-54KNMC2R.cjs} +3 -3
- package/dist/{chunk-D3LEFMOA.cjs.map → chunk-54KNMC2R.cjs.map} +1 -1
- package/dist/{chunk-EYEW6PTA.cjs → chunk-6C526VNN.cjs} +358 -118
- package/dist/chunk-6C526VNN.cjs.map +1 -0
- package/dist/{chunk-5O6N3BKR.cjs → chunk-7OVGB2DQ.cjs} +19 -25
- package/dist/chunk-7OVGB2DQ.cjs.map +1 -0
- package/dist/{chunk-6GCI7JOE.js → chunk-AD6C23QY.js} +8 -7
- package/dist/{chunk-6GCI7JOE.js.map → chunk-AD6C23QY.js.map} +1 -1
- package/dist/chunk-F5OB2YAL.cjs +144 -0
- package/dist/chunk-F5OB2YAL.cjs.map +1 -0
- package/dist/chunk-FBWXMMRB.cjs +2 -0
- package/dist/chunk-FBWXMMRB.cjs.map +1 -0
- package/dist/{chunk-YIGPRLQY.cjs → chunk-FCDQNTDG.cjs} +21 -20
- package/dist/chunk-FCDQNTDG.cjs.map +1 -0
- package/dist/{chunk-XXI7BNB6.cjs → chunk-FQOTC3UU.cjs} +321 -18
- package/dist/chunk-FQOTC3UU.cjs.map +1 -0
- package/dist/{chunk-INDQMNP6.cjs → chunk-GUTS7HGA.cjs} +11658 -2146
- package/dist/chunk-GUTS7HGA.cjs.map +1 -0
- package/dist/chunk-GZ4C3XW6.js +2 -0
- package/dist/chunk-GZ4C3XW6.js.map +1 -0
- package/dist/{chunk-HOVJGXF7.js → chunk-IL47XWV5.js} +8 -14
- package/dist/{chunk-HOVJGXF7.js.map → chunk-IL47XWV5.js.map} +1 -1
- package/dist/{chunk-LCNMR277.js → chunk-IZ7JSBFP.js} +1 -1
- package/dist/chunk-IZ7JSBFP.js.map +1 -0
- package/dist/{chunk-5IJ46KAV.js → chunk-JALO4TAZ.js} +360 -57
- package/dist/chunk-JALO4TAZ.js.map +1 -0
- package/dist/{chunk-AQOWFSMB.cjs → chunk-L6PSSIUQ.cjs} +1 -1
- package/dist/chunk-L6PSSIUQ.cjs.map +1 -0
- package/dist/{chunk-J3RDKZ32.js → chunk-L7ULJKG7.js} +6 -10
- package/dist/{chunk-J3RDKZ32.js.map → chunk-L7ULJKG7.js.map} +1 -1
- package/dist/{chunk-6BZEAPNT.js → chunk-PC746XCO.js} +15120 -5608
- package/dist/chunk-PC746XCO.js.map +1 -0
- package/dist/{chunk-3ZXUQQL4.js → chunk-PI4WSYQV.js} +2 -2
- package/dist/{chunk-E4XABBSU.js → chunk-PWQUAVA3.js} +338 -98
- package/dist/chunk-PWQUAVA3.js.map +1 -0
- package/dist/chunk-SA2WPJVO.js +144 -0
- package/dist/chunk-SA2WPJVO.js.map +1 -0
- package/dist/{chunk-ETACGX2A.cjs → chunk-UNVE2SDJ.cjs} +37 -31
- package/dist/chunk-UNVE2SDJ.cjs.map +1 -0
- package/dist/{chunk-5E2HOSSH.cjs → chunk-WMSTJAZT.cjs} +913 -51
- package/dist/chunk-WMSTJAZT.cjs.map +1 -0
- package/dist/{chunk-EJXHZX2E.js → chunk-X4DOXQRT.js} +4 -6
- package/dist/{chunk-EJXHZX2E.js.map → chunk-X4DOXQRT.js.map} +1 -1
- package/dist/{chunk-A2YL7QRX.cjs → chunk-YBYI62OE.cjs} +33 -37
- package/dist/chunk-YBYI62OE.cjs.map +1 -0
- package/dist/components/case-studies/index.cjs +126 -0
- package/dist/components/case-studies/index.cjs.map +1 -0
- package/dist/components/case-studies/index.d.ts +2 -0
- package/dist/components/case-studies/index.d.ts.map +1 -0
- package/dist/components/case-studies/index.js +126 -0
- package/dist/components/case-studies/index.js.map +1 -0
- package/dist/components/case-studies/share-experience-section.d.ts +48 -0
- package/dist/components/case-studies/share-experience-section.d.ts.map +1 -0
- package/dist/components/chat/chat-container.d.ts.map +1 -1
- package/dist/components/chat/error-message-display.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +8 -18
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.js +75 -85
- package/dist/components/chat/types/component.types.d.ts +2 -0
- package/dist/components/chat/types/component.types.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +8 -15
- package/dist/components/contact/index.cjs.map +1 -1
- package/dist/components/contact/index.js +7 -14
- package/dist/components/docs/doc-viewer.d.ts +39 -2
- package/dist/components/docs/doc-viewer.d.ts.map +1 -1
- package/dist/components/docs/docs-hub-page.d.ts +46 -0
- package/dist/components/docs/docs-hub-page.d.ts.map +1 -0
- package/dist/components/docs/index.cjs +17 -9
- package/dist/components/docs/index.cjs.map +1 -1
- package/dist/components/docs/index.d.ts +4 -0
- package/dist/components/docs/index.d.ts.map +1 -1
- package/dist/components/docs/index.js +16 -8
- package/dist/components/docs/skeletons.d.ts +32 -0
- package/dist/components/docs/skeletons.d.ts.map +1 -0
- package/dist/components/docs/use-docs-resolve-link.d.ts +20 -0
- package/dist/components/docs/use-docs-resolve-link.d.ts.map +1 -0
- package/dist/components/docs/use-document-tree.d.ts.map +1 -1
- package/dist/components/embeds/embed-container.d.ts +37 -0
- package/dist/components/embeds/embed-container.d.ts.map +1 -0
- package/dist/components/embeds/embed-iframe.d.ts.map +1 -1
- package/dist/components/embeds/file-download-card.d.ts +18 -0
- package/dist/components/embeds/file-download-card.d.ts.map +1 -0
- package/dist/components/embeds/index.cjs +38 -15
- package/dist/components/embeds/index.cjs.map +1 -1
- package/dist/components/embeds/index.d.ts +8 -0
- package/dist/components/embeds/index.d.ts.map +1 -1
- package/dist/components/embeds/index.js +40 -17
- package/dist/components/embeds/linkedin-embed-client.d.ts +8 -0
- package/dist/components/embeds/linkedin-embed-client.d.ts.map +1 -0
- package/dist/components/embeds/markdown-image.d.ts +5 -0
- package/dist/components/embeds/markdown-image.d.ts.map +1 -0
- package/dist/components/embeds/reddit-embed-client.d.ts +7 -0
- package/dist/components/embeds/reddit-embed-client.d.ts.map +1 -0
- package/dist/components/embeds/rich-markdown-runtime.d.ts +46 -0
- package/dist/components/embeds/rich-markdown-runtime.d.ts.map +1 -0
- package/dist/components/embeds/twitter-embed-client.d.ts +8 -0
- package/dist/components/embeds/twitter-embed-client.d.ts.map +1 -0
- package/dist/components/faq/index.cjs +9 -16
- package/dist/components/faq/index.cjs.map +1 -1
- package/dist/components/faq/index.js +8 -15
- package/dist/components/features/index.cjs +8 -16
- package/dist/components/features/index.cjs.map +1 -1
- package/dist/components/features/index.js +24 -32
- package/dist/components/features/notifications/notification-drawer.d.ts.map +1 -1
- package/dist/components/features/notifications/notifications-context.d.ts +5 -1
- package/dist/components/features/notifications/notifications-context.d.ts.map +1 -1
- package/dist/components/index.cjs +257 -452
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +781 -976
- package/dist/components/index.js.map +1 -1
- package/dist/components/layout/page-header.d.ts +78 -0
- package/dist/components/layout/page-header.d.ts.map +1 -0
- package/dist/components/layout/page-layout.d.ts +10 -1
- package/dist/components/layout/page-layout.d.ts.map +1 -1
- package/dist/components/layout/page-with-header.d.ts +67 -0
- package/dist/components/layout/page-with-header.d.ts.map +1 -0
- package/dist/components/layout/title-block.d.ts +17 -1
- package/dist/components/layout/title-block.d.ts.map +1 -1
- package/dist/components/navigation/index.cjs +7 -15
- package/dist/components/navigation/index.cjs.map +1 -1
- package/dist/components/navigation/index.js +9 -17
- package/dist/components/onboarding-guides/index.cjs +35 -36
- package/dist/components/onboarding-guides/index.cjs.map +1 -1
- package/dist/components/onboarding-guides/index.js +13 -14
- 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 +9 -16
- package/dist/components/related-content/index.cjs.map +1 -1
- package/dist/components/related-content/index.js +8 -15
- package/dist/components/shared/dev-section/dev-section-page.d.ts +9 -0
- 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 +100 -112
- package/dist/components/tickets/index.cjs.map +1 -1
- package/dist/components/tickets/index.js +20 -32
- package/dist/components/tickets/index.js.map +1 -1
- package/dist/components/ui/button/split-button.d.ts.map +1 -1
- package/dist/components/ui/file-manager/index.cjs +50 -52
- package/dist/components/ui/file-manager/index.cjs.map +1 -1
- package/dist/components/ui/file-manager/index.js +4 -6
- package/dist/components/ui/file-manager/index.js.map +1 -1
- package/dist/components/ui/index.cjs +13 -19
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.d.ts +2 -0
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +133 -139
- package/dist/components/ui/release-changelog-section.d.ts +6 -2
- package/dist/components/ui/release-changelog-section.d.ts.map +1 -1
- package/dist/components/ui/rich-markdown-renderer.d.ts +34 -0
- package/dist/components/ui/rich-markdown-renderer.d.ts.map +1 -0
- package/dist/components/ui/simple-markdown-renderer.d.ts +2 -8
- package/dist/components/ui/simple-markdown-renderer.d.ts.map +1 -1
- package/dist/contexts/chat-runtime-context.d.ts +14 -0
- 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 +4 -9
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.js +6 -11
- package/dist/index.cjs +14 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +362 -368
- package/dist/types/doc-source.d.ts +31 -1
- package/dist/types/doc-source.d.ts.map +1 -1
- package/dist/utils/index.cjs +4 -0
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +4 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/page-header-constants.d.ts +15 -0
- package/dist/utils/page-header-constants.d.ts.map +1 -0
- package/dist/utils/social-embed-cache.d.ts +29 -0
- package/dist/utils/social-embed-cache.d.ts.map +1 -0
- package/package.json +7 -1
- package/src/components/case-studies/index.ts +4 -0
- package/src/components/case-studies/share-experience-section.tsx +185 -0
- package/src/components/chat/chat-container.tsx +5 -7
- package/src/components/chat/embeddable-chat.tsx +1 -1
- package/src/components/chat/error-message-display.tsx +49 -31
- package/src/components/chat/types/component.types.ts +2 -0
- package/src/components/docs/doc-viewer.tsx +111 -19
- package/src/components/docs/docs-hub-page.tsx +149 -0
- package/src/components/docs/index.ts +17 -0
- package/src/components/docs/skeletons.tsx +138 -0
- package/src/components/docs/use-docs-resolve-link.ts +52 -0
- package/src/components/docs/use-document-tree.ts +21 -0
- package/src/components/embeds/embed-container.tsx +80 -0
- package/src/components/embeds/embed-iframe.tsx +7 -9
- package/src/components/embeds/file-download-card.tsx +54 -0
- package/src/components/embeds/index.ts +30 -0
- package/src/components/embeds/linkedin-embed-client.tsx +100 -0
- package/src/components/embeds/markdown-image.tsx +88 -0
- package/src/components/embeds/og-link-preview.tsx +13 -13
- package/src/components/embeds/reddit-embed-client.tsx +550 -0
- package/src/components/embeds/rich-markdown-runtime.tsx +79 -0
- package/src/components/embeds/twitter-embed-client.tsx +308 -0
- package/src/components/features/notifications/notification-drawer.tsx +18 -7
- package/src/components/features/notifications/notifications-context.tsx +7 -0
- package/src/components/layout/page-header.tsx +182 -0
- package/src/components/layout/page-layout.tsx +14 -1
- package/src/components/layout/page-with-header.tsx +110 -0
- package/src/components/layout/title-block.tsx +40 -62
- package/src/components/onboarding-guides/onboarding-guide-detail-view.tsx +3 -3
- package/src/components/shared/dev-section/dev-section-page.tsx +9 -1
- package/src/components/shared/dev-section/dev-section-view.tsx +14 -9
- package/src/components/shared/dev-section/index.ts +1 -1
- package/src/components/shared/doc-search/use-doc-search.ts +7 -3
- package/src/components/shared/legal-document/legal-document-page.tsx +2 -2
- package/src/components/shared/product-release/release-detail-page.tsx +6 -4
- package/src/components/ui/button/split-button.tsx +5 -2
- package/src/components/ui/index.ts +2 -0
- package/src/components/ui/release-changelog-section.tsx +7 -2
- package/src/components/ui/rich-markdown-renderer.tsx +1203 -0
- package/src/components/ui/simple-markdown-renderer.tsx +7 -11
- package/src/contexts/chat-runtime-context.tsx +14 -0
- package/src/stories/NotificationDrawer.stories.tsx +2 -0
- package/src/types/doc-source.ts +33 -1
- package/src/utils/index.ts +1 -0
- package/src/utils/page-header-constants.ts +15 -0
- package/src/utils/social-embed-cache.ts +391 -0
- package/dist/chunk-26PKDALD.js +0 -2379
- package/dist/chunk-26PKDALD.js.map +0 -1
- package/dist/chunk-3MCHAFHB.js +0 -89
- package/dist/chunk-3MCHAFHB.js.map +0 -1
- package/dist/chunk-3XIB4VKS.cjs +0 -619
- package/dist/chunk-3XIB4VKS.cjs.map +0 -1
- package/dist/chunk-4W7NYJ3B.cjs +0 -3009
- package/dist/chunk-4W7NYJ3B.cjs.map +0 -1
- package/dist/chunk-5E2HOSSH.cjs.map +0 -1
- package/dist/chunk-5IJ46KAV.js.map +0 -1
- package/dist/chunk-5O6N3BKR.cjs.map +0 -1
- package/dist/chunk-6BZEAPNT.js.map +0 -1
- package/dist/chunk-6IBA2MQV.cjs.map +0 -1
- package/dist/chunk-6JINAOI7.cjs +0 -311
- package/dist/chunk-6JINAOI7.cjs.map +0 -1
- package/dist/chunk-7RIYT7ZH.js.map +0 -1
- package/dist/chunk-A2YL7QRX.cjs.map +0 -1
- package/dist/chunk-AQOWFSMB.cjs.map +0 -1
- package/dist/chunk-E4XABBSU.js.map +0 -1
- package/dist/chunk-ETACGX2A.cjs.map +0 -1
- package/dist/chunk-EYEW6PTA.cjs.map +0 -1
- package/dist/chunk-FQJK446R.js +0 -1606
- package/dist/chunk-FQJK446R.js.map +0 -1
- package/dist/chunk-FT4FCV7L.cjs.map +0 -1
- package/dist/chunk-INDQMNP6.cjs.map +0 -1
- package/dist/chunk-J54Z3OCR.cjs +0 -1606
- package/dist/chunk-J54Z3OCR.cjs.map +0 -1
- package/dist/chunk-KXCRGTRN.cjs +0 -2379
- package/dist/chunk-KXCRGTRN.cjs.map +0 -1
- package/dist/chunk-LCNMR277.js.map +0 -1
- package/dist/chunk-LFGGF7OT.cjs +0 -449
- package/dist/chunk-LFGGF7OT.cjs.map +0 -1
- package/dist/chunk-M2OCXTNT.js +0 -311
- package/dist/chunk-M2OCXTNT.js.map +0 -1
- package/dist/chunk-NSPOYUBH.js +0 -3009
- package/dist/chunk-NSPOYUBH.js.map +0 -1
- package/dist/chunk-OOKKGOPQ.js.map +0 -1
- package/dist/chunk-OQ6X7ZOC.js +0 -449
- package/dist/chunk-OQ6X7ZOC.js.map +0 -1
- package/dist/chunk-POKKCWKF.js +0 -354
- package/dist/chunk-POKKCWKF.js.map +0 -1
- package/dist/chunk-TFSYSWPS.cjs +0 -89
- package/dist/chunk-TFSYSWPS.cjs.map +0 -1
- package/dist/chunk-XXI7BNB6.cjs.map +0 -1
- package/dist/chunk-YD43AKI5.js +0 -619
- package/dist/chunk-YD43AKI5.js.map +0 -1
- package/dist/chunk-YETA25JW.cjs +0 -354
- package/dist/chunk-YETA25JW.cjs.map +0 -1
- package/dist/chunk-YIGPRLQY.cjs.map +0 -1
- /package/dist/{chunk-3ZXUQQL4.js.map → chunk-PI4WSYQV.js.map} +0 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo, useState } from 'react';
|
|
4
|
+
import { LinkedInContainer } from './embed-container';
|
|
5
|
+
import { LinkedinIcon } from '../icons-v2-generated/brand-logos/linkedin-icon';
|
|
6
|
+
import { ExternalLink } from 'lucide-react';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Derive LinkedIn's official embed URL from any post URL or URN.
|
|
10
|
+
* LinkedIn renders public posts at /embed/feed/update/<urn>. Returns '' when no
|
|
11
|
+
* URN can be derived, so the component falls back to a link instead of a broken
|
|
12
|
+
* (X-Frame-blocked) iframe.
|
|
13
|
+
*/
|
|
14
|
+
function toLinkedInEmbedUrl(url: string): string {
|
|
15
|
+
if (!url) return '';
|
|
16
|
+
if (url.includes('linkedin.com/embed/')) return url.split('?')[0];
|
|
17
|
+
let m = url.match(/urn:li:(activity|share|ugcPost):(\d+)/i);
|
|
18
|
+
if (m) return `https://www.linkedin.com/embed/feed/update/urn:li:${m[1]}:${m[2]}`;
|
|
19
|
+
m = url.match(/activity[-:](\d{15,25})/i);
|
|
20
|
+
if (m) return `https://www.linkedin.com/embed/feed/update/urn:li:activity:${m[1]}`;
|
|
21
|
+
m = url.match(/-(\d{15,25})(?:-[A-Za-z0-9_-]+)?\/?(?:\?.*)?$/);
|
|
22
|
+
if (m) return `https://www.linkedin.com/embed/feed/update/urn:li:activity:${m[1]}`;
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface LinkedInEmbedProps {
|
|
27
|
+
url: string;
|
|
28
|
+
/** Fixed iframe height — LinkedIn embeds don't auto-resize. */
|
|
29
|
+
height?: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function LinkedInEmbedClient({ url, height = 600 }: LinkedInEmbedProps) {
|
|
33
|
+
const embedUrl = useMemo(() => toLinkedInEmbedUrl(url), [url]);
|
|
34
|
+
const [loaded, setLoaded] = useState(false);
|
|
35
|
+
|
|
36
|
+
// No derivable URN → graceful fallback card with a link (mirrors reddit's error state)
|
|
37
|
+
if (!embedUrl) {
|
|
38
|
+
return (
|
|
39
|
+
<LinkedInContainer>
|
|
40
|
+
<div className="p-6">
|
|
41
|
+
<div className="flex items-center space-x-3 text-ods-text-secondary mb-4">
|
|
42
|
+
<LinkedinIcon className="w-5 h-5 shrink-0" />
|
|
43
|
+
<span>LinkedIn post</span>
|
|
44
|
+
</div>
|
|
45
|
+
<a
|
|
46
|
+
href={url}
|
|
47
|
+
target="_blank"
|
|
48
|
+
rel="noopener noreferrer"
|
|
49
|
+
className="inline-flex items-center space-x-2 px-4 py-2 bg-ods-card border border-ods-border text-ods-text-primary rounded-md text-sm font-medium hover:bg-ods-bg-secondary transition-colors"
|
|
50
|
+
>
|
|
51
|
+
<LinkedinIcon className="w-4 h-4" />
|
|
52
|
+
<span>View on LinkedIn</span>
|
|
53
|
+
</a>
|
|
54
|
+
</div>
|
|
55
|
+
</LinkedInContainer>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<LinkedInContainer>
|
|
61
|
+
<div className="relative w-full" style={{ height }}>
|
|
62
|
+
{!loaded && (
|
|
63
|
+
<div className="absolute inset-0 p-6 animate-pulse">
|
|
64
|
+
<div className="flex items-center space-x-3 mb-4">
|
|
65
|
+
<div className="w-12 h-12 bg-ods-border rounded-full" />
|
|
66
|
+
<div>
|
|
67
|
+
<div className="h-4 bg-ods-border rounded w-32 mb-2" />
|
|
68
|
+
<div className="h-3 bg-ods-border rounded w-24" />
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
<div className="space-y-2">
|
|
72
|
+
<div className="h-4 bg-ods-border rounded w-full" />
|
|
73
|
+
<div className="h-4 bg-ods-border rounded w-3/4" />
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
)}
|
|
77
|
+
<iframe
|
|
78
|
+
src={embedUrl}
|
|
79
|
+
title="Embedded LinkedIn post"
|
|
80
|
+
className="w-full h-full"
|
|
81
|
+
style={{ border: 0 }}
|
|
82
|
+
loading="lazy"
|
|
83
|
+
allowFullScreen
|
|
84
|
+
onLoad={() => setLoaded(true)}
|
|
85
|
+
/>
|
|
86
|
+
</div>
|
|
87
|
+
<div className="px-4 py-3 bg-ods-bg-secondary border-t border-ods-border">
|
|
88
|
+
<a
|
|
89
|
+
href={url}
|
|
90
|
+
target="_blank"
|
|
91
|
+
rel="noopener noreferrer"
|
|
92
|
+
className="inline-flex items-center space-x-2 text-ods-accent hover:text-ods-accent/80 transition-colors text-sm font-medium"
|
|
93
|
+
>
|
|
94
|
+
<ExternalLink className="w-4 h-4" />
|
|
95
|
+
<span>View on LinkedIn</span>
|
|
96
|
+
</a>
|
|
97
|
+
</div>
|
|
98
|
+
</LinkedInContainer>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import Image from '../../embed-shims/next-image';
|
|
5
|
+
import { useRichMarkdownRuntime } from './rich-markdown-runtime';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* In-article markdown image.
|
|
9
|
+
*
|
|
10
|
+
* Markdown images have unknown intrinsic dimensions — which is what broke the old
|
|
11
|
+
* approach: a fixed width/height made the Supabase loader `resize=cover` CROP tall
|
|
12
|
+
* screenshots, and `w-full` then blew them up to the column width. This renders
|
|
13
|
+
* through the lib's `next/image` shim (full Next.js Image Optimization on the hub,
|
|
14
|
+
* plain `<img>` everywhere else) and fixes both problems at the source:
|
|
15
|
+
*
|
|
16
|
+
* - `object-contain` flips the hub's Supabase loader (injected via
|
|
17
|
+
* `transformImageSrc` from the {@link RichMarkdownRuntimeProvider}) to
|
|
18
|
+
* `resize=contain` — aspect preserved, never cropped.
|
|
19
|
+
* - We learn the real aspect ratio from a tiny probe (the same
|
|
20
|
+
* `transformImageSrc` at 48px when available) and pass matching width/height
|
|
21
|
+
* so `next/image`'s aspect-ratio box is correct.
|
|
22
|
+
* - CSS (`max-w-full` + `max-h` + auto) caps the on-page size so a tall
|
|
23
|
+
* screenshot can't dominate the article, while the browser keeps the true
|
|
24
|
+
* aspect ratio.
|
|
25
|
+
*
|
|
26
|
+
* Embedders that don't pass `transformImageSrc` get an identity fallback — the
|
|
27
|
+
* raw `src` is used both for the probe and the display copy.
|
|
28
|
+
*/
|
|
29
|
+
const MAX_H_REM = 32; // ~512px on-page cap
|
|
30
|
+
const DISPLAY_W = 768; // logical width hint; the optimizer handles srcset + retina
|
|
31
|
+
|
|
32
|
+
export function MarkdownImage({ src, alt }: { src: string; alt?: string }) {
|
|
33
|
+
const { transformImageSrc } = useRichMarkdownRuntime();
|
|
34
|
+
const [ratio, setRatio] = useState<number | null>(null); // width / height
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
let cancelled = false;
|
|
38
|
+
// Reset on src change so a reused instance doesn't briefly size the new image with
|
|
39
|
+
// the previous image's ratio.
|
|
40
|
+
setRatio(null);
|
|
41
|
+
// Probe a tiny aspect-preserving variant so we learn the real ratio without
|
|
42
|
+
// downloading the full image; the display copy is then fetched once, at the right size.
|
|
43
|
+
// When no transformer is wired (embedders), fall back to the raw src.
|
|
44
|
+
const probeSrc = transformImageSrc(src, { width: 48, resize: 'contain', quality: 20 }) ?? src;
|
|
45
|
+
const probe = new window.Image();
|
|
46
|
+
probe.onload = () => {
|
|
47
|
+
if (!cancelled && probe.naturalWidth && probe.naturalHeight) {
|
|
48
|
+
setRatio(probe.naturalWidth / probe.naturalHeight);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
probe.onerror = () => {
|
|
52
|
+
if (!cancelled) setRatio(1.5); // neutral fallback so we still render something
|
|
53
|
+
};
|
|
54
|
+
probe.src = probeSrc;
|
|
55
|
+
return () => {
|
|
56
|
+
cancelled = true;
|
|
57
|
+
};
|
|
58
|
+
}, [src, transformImageSrc]);
|
|
59
|
+
|
|
60
|
+
// Reserve a neutral box while probing so the layout doesn't jump when the image appears.
|
|
61
|
+
if (!ratio) {
|
|
62
|
+
return (
|
|
63
|
+
<span
|
|
64
|
+
className="mx-auto my-2 block w-full max-w-full animate-pulse rounded-lg bg-ods-card"
|
|
65
|
+
style={{ aspectRatio: '3 / 2', maxHeight: `${MAX_H_REM}rem` }}
|
|
66
|
+
aria-hidden
|
|
67
|
+
/>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<Image
|
|
73
|
+
src={src}
|
|
74
|
+
alt={alt ?? 'No image available'}
|
|
75
|
+
width={DISPLAY_W}
|
|
76
|
+
height={Math.round(DISPLAY_W / ratio)}
|
|
77
|
+
sizes="(max-width: 768px) 100vw, 768px"
|
|
78
|
+
loading="lazy"
|
|
79
|
+
// `object-contain` → SupabaseOptimizedImage uses `resize=contain` (no crop).
|
|
80
|
+
// `w-full` (not `w-auto`) gives the img a definite width so it lays out + lazy-loads
|
|
81
|
+
// even before decode. Height follows via the aspect-ratio box; `maxHeight` caps tall
|
|
82
|
+
// images and `maxWidth = maxHeight × ratio` shrinks the width in lockstep so the box
|
|
83
|
+
// stays snug to the image (no letterbox bars).
|
|
84
|
+
className="mx-auto my-2 block h-auto w-full rounded-lg object-contain"
|
|
85
|
+
style={{ maxWidth: `calc(${MAX_H_REM}rem * ${ratio})`, maxHeight: `${MAX_H_REM}rem` }}
|
|
86
|
+
/>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
@@ -262,34 +262,34 @@ export const OGLinkPreview: React.FC<OGLinkPreviewProps> = ({
|
|
|
262
262
|
const renderSkeleton = () => isCompact ? (
|
|
263
263
|
<div className="my-4">
|
|
264
264
|
<div className="flex flex-row border border-ods-border rounded-lg overflow-hidden bg-ods-card h-[120px]">
|
|
265
|
-
<div className="w-[200px] h-full flex-shrink-0 bg-ods-
|
|
265
|
+
<div className="w-[200px] h-full flex-shrink-0 bg-ods-border animate-pulse" />
|
|
266
266
|
<div className="flex-1 p-3 flex flex-col justify-center">
|
|
267
|
-
<div className="bg-ods-
|
|
268
|
-
<div className="bg-ods-
|
|
269
|
-
<div className="bg-ods-
|
|
270
|
-
<div className="bg-ods-
|
|
267
|
+
<div className="bg-ods-border rounded animate-pulse h-4 w-3/4 mb-2" />
|
|
268
|
+
<div className="bg-ods-border rounded animate-pulse h-3 w-full mb-1" />
|
|
269
|
+
<div className="bg-ods-border rounded animate-pulse h-3 w-2/3 mb-2" />
|
|
270
|
+
<div className="bg-ods-border rounded animate-pulse h-3 w-1/3" />
|
|
271
271
|
</div>
|
|
272
272
|
</div>
|
|
273
273
|
</div>
|
|
274
274
|
) : (
|
|
275
275
|
<div className="my-6">
|
|
276
276
|
<div className="block border border-ods-border rounded-lg overflow-hidden bg-ods-card">
|
|
277
|
-
<div className="aspect-video w-full bg-ods-
|
|
277
|
+
<div className="aspect-video w-full bg-ods-border overflow-hidden relative animate-pulse" />
|
|
278
278
|
<div className="p-4">
|
|
279
279
|
<div className="flex items-start gap-3">
|
|
280
|
-
<div className="w-6 h-6 bg-ods-
|
|
280
|
+
<div className="w-6 h-6 bg-ods-border rounded flex-shrink-0 mt-0.5 animate-pulse" />
|
|
281
281
|
<div className="flex-1 min-w-0">
|
|
282
282
|
<div className="h-[2.5rem] leading-[1.25rem] mb-2 overflow-hidden">
|
|
283
|
-
<div className="bg-ods-
|
|
284
|
-
<div className="bg-ods-
|
|
283
|
+
<div className="bg-ods-border rounded animate-pulse" style={{ height: '1.25rem', marginBottom: '0.25rem' }} />
|
|
284
|
+
<div className="bg-ods-border rounded animate-pulse w-3/4" style={{ height: '1.25rem' }} />
|
|
285
285
|
</div>
|
|
286
286
|
<div className="h-[2.5rem] leading-[1.25rem] mb-2 overflow-hidden">
|
|
287
|
-
<div className="bg-ods-
|
|
288
|
-
<div className="bg-ods-
|
|
287
|
+
<div className="bg-ods-border rounded animate-pulse" style={{ height: '1.25rem', marginBottom: '0.25rem' }} />
|
|
288
|
+
<div className="bg-ods-border rounded animate-pulse w-5/6" style={{ height: '1.25rem' }} />
|
|
289
289
|
</div>
|
|
290
290
|
<div className="flex items-center gap-2">
|
|
291
|
-
<div className="bg-ods-
|
|
292
|
-
<div className="bg-ods-
|
|
291
|
+
<div className="bg-ods-border rounded animate-pulse" style={{ height: '0.75rem', width: '6rem' }} />
|
|
292
|
+
<div className="bg-ods-border rounded animate-pulse" style={{ height: '0.75rem', width: '5rem' }} />
|
|
293
293
|
</div>
|
|
294
294
|
</div>
|
|
295
295
|
</div>
|