Some checks failed
🧪 CI (QA) / 🧪 Quality Assurance (push) Failing after 1m3s
- Restructure to pnpm monorepo (site moved to apps/web) - Integrate @mintel/tsconfig, @mintel/eslint-config, @mintel/husky-config - Implement Docker service architecture (Varnish, Directus, Gatekeeper) - Setup environment-aware Gitea Actions deployment
99 lines
3.0 KiB
TypeScript
99 lines
3.0 KiB
TypeScript
import React from 'react';
|
|
|
|
interface GenericEmbedProps {
|
|
url: string;
|
|
className?: string;
|
|
maxWidth?: string;
|
|
type?: 'video' | 'article' | 'rich';
|
|
}
|
|
|
|
export const GenericEmbed: React.FC<GenericEmbedProps> = ({
|
|
url,
|
|
className = "",
|
|
maxWidth = "100%",
|
|
type = 'rich'
|
|
}) => {
|
|
let embedUrl: string | null = null;
|
|
let provider = 'unknown';
|
|
|
|
try {
|
|
const urlObj = new URL(url);
|
|
const hostname = urlObj.hostname.replace('www.', '');
|
|
|
|
if (hostname.includes('youtube.com') || hostname.includes('youtu.be')) {
|
|
const videoId = urlObj.searchParams.get('v') || urlObj.pathname.split('/').pop();
|
|
if (videoId) {
|
|
embedUrl = `https://www.youtube.com/embed/${videoId}`;
|
|
provider = 'youtube.com';
|
|
}
|
|
}
|
|
else if (hostname.includes('vimeo.com')) {
|
|
const videoId = urlObj.pathname.split('/').filter(Boolean)[0];
|
|
if (videoId) {
|
|
embedUrl = `https://player.vimeo.com/video/${videoId}`;
|
|
provider = 'vimeo.com';
|
|
}
|
|
}
|
|
else if (hostname.includes('codepen.io')) {
|
|
const penPath = urlObj.pathname.replace('/pen/', '/');
|
|
embedUrl = `https://codepen.io${penPath}?default-tab=html,result`;
|
|
provider = 'codepen.io';
|
|
}
|
|
else if (hostname.includes('gist.github.com')) {
|
|
const gistPath = urlObj.pathname;
|
|
embedUrl = `https://gist.github.com${gistPath}.js`;
|
|
provider = 'gist.github.com';
|
|
}
|
|
} catch (e) {
|
|
console.warn('GenericEmbed: Failed to parse URL', e);
|
|
}
|
|
|
|
const hasEmbed = embedUrl !== null;
|
|
|
|
return (
|
|
<div
|
|
className={`generic-embed not-prose ${className}`}
|
|
data-provider={provider}
|
|
data-type={type}
|
|
style={{ '--max-width': maxWidth } as React.CSSProperties}
|
|
>
|
|
{hasEmbed ? (
|
|
<div className="embed-wrapper">
|
|
{type === 'video' ? (
|
|
<iframe
|
|
src={embedUrl!}
|
|
width="100%"
|
|
height="100%"
|
|
style={{ border: 'none', width: '100%', height: '100%' }}
|
|
allowFullScreen
|
|
loading="lazy"
|
|
title={`${provider} embed`}
|
|
/>
|
|
) : (
|
|
<iframe
|
|
src={embedUrl!}
|
|
width="100%"
|
|
height="400"
|
|
style={{ border: 'none' }}
|
|
loading="lazy"
|
|
title={`${provider} embed`}
|
|
/>
|
|
)}
|
|
</div>
|
|
) : (
|
|
<div className="embed-fallback">
|
|
<div className="fallback-content">
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
<span>Unable to embed this URL</span>
|
|
<a href={url} target="_blank" rel="noopener noreferrer" className="fallback-link">
|
|
Open link →
|
|
</a>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|