Add Youtube video rendering to DescriptionRenderer

This commit is contained in:
Justin Edmund 2025-09-24 00:43:06 -07:00
parent a1eef8c8a5
commit 06a91bd532

View file

@ -19,7 +19,7 @@
// Apply marks (formatting) // Apply marks (formatting)
if (node.marks) { if (node.marks) {
node.marks.forEach(mark => { node.marks.forEach((mark) => {
switch (mark.type) { switch (mark.type) {
case 'bold': case 'bold':
text = `<strong>${text}</strong>` text = `<strong>${text}</strong>`
@ -79,7 +79,7 @@
return `<blockquote>${quoteContent}</blockquote>` return `<blockquote>${quoteContent}</blockquote>`
case 'codeBlock': case 'codeBlock':
const codeContent = (node.content || []).map(n => n.text || '').join('') const codeContent = (node.content || []).map((n) => n.text || '').join('')
return `<pre><code>${codeContent}</code></pre>` return `<pre><code>${codeContent}</code></pre>`
case 'hardBreak': case 'hardBreak':
@ -89,9 +89,44 @@
return '<hr>' return '<hr>'
case 'youtube': case 'youtube':
// For now, show a link to the video in truncated view
const videoUrl = node.attrs?.src || '' const videoUrl = node.attrs?.src || ''
return `<p><a href="${videoUrl}" target="_blank" rel="noopener noreferrer">📹 View Video</a></p>` // Extract video ID from various YouTube URL formats
let videoId = ''
// Handle different YouTube URL formats
const patterns = [
/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/,
/youtube\.com\/watch\?.*v=([^&\n?#]+)/
]
for (const pattern of patterns) {
const match = videoUrl.match(pattern)
if (match) {
videoId = match[1]
break
}
}
// If we couldn't extract an ID, fall back to link
if (!videoId) {
return `<p><a href="${videoUrl}" target="_blank" rel="noopener noreferrer">📹 View Video</a></p>`
}
// For truncated view, show a link instead of embed
if (truncate) {
return `<p><a href="${videoUrl}" target="_blank" rel="noopener noreferrer">📹 View Video</a></p>`
}
// Embed YouTube video with responsive iframe
return `<div class="video-wrapper">
<iframe
src="https://www.youtube.com/embed/${videoId}"
title="YouTube video"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
></iframe>
</div>`
case 'mention': case 'mention':
// Handle game item mentions // Handle game item mentions
@ -133,11 +168,7 @@
const parsedHTML = $derived(parseContent(content)) const parsedHTML = $derived(parseContent(content))
</script> </script>
<div <div class="description-content" class:truncate style={truncate ? `--max-lines: ${maxLines}` : ''}>
class="description-content"
class:truncate
style={truncate ? `--max-lines: ${maxLines}` : ''}
>
{@html parsedHTML} {@html parsedHTML}
</div> </div>
@ -145,6 +176,7 @@
@use '$src/themes/typography' as *; @use '$src/themes/typography' as *;
@use '$src/themes/colors' as *; @use '$src/themes/colors' as *;
@use '$src/themes/spacing' as *; @use '$src/themes/spacing' as *;
@use '$src/themes/layout' as *;
.description-content { .description-content {
color: var(--text-primary); color: var(--text-primary);
@ -161,7 +193,9 @@
} }
} }
h1, h2, h3 { h1,
h2,
h3 {
font-weight: $bold; font-weight: $bold;
margin: $unit 0 $unit-half 0; margin: $unit 0 $unit-half 0;
} }
@ -178,11 +212,13 @@
font-size: $font-medium; font-size: $font-medium;
} }
strong, b { strong,
b {
font-weight: $bold; font-weight: $bold;
} }
em, i { em,
i {
font-style: italic; font-style: italic;
} }
@ -199,7 +235,7 @@
background: rgba(255, 237, 76, 0.3); background: rgba(255, 237, 76, 0.3);
color: var(--text-primary); color: var(--text-primary);
padding: 0 $unit-fourth; padding: 0 $unit-fourth;
border-radius: 2px; border-radius: $input-corner;
font-weight: $medium; font-weight: $medium;
} }
@ -213,7 +249,8 @@
} }
} }
ul, ol { ul,
ol {
margin: 0 0 $unit 0; margin: 0 0 $unit 0;
padding-left: $unit-3x; padding-left: $unit-3x;
} }
@ -225,7 +262,7 @@
code { code {
background: var(--button-bg); background: var(--button-bg);
padding: 2px $unit-half; padding: 2px $unit-half;
border-radius: 3px; border-radius: $input-corner;
font-family: monospace; font-family: monospace;
font-size: 0.9em; font-size: 0.9em;
} }
@ -233,7 +270,7 @@
pre { pre {
background: var(--button-bg); background: var(--button-bg);
padding: $unit; padding: $unit;
border-radius: $unit-half; border-radius: $card-corner;
overflow-x: auto; overflow-x: auto;
margin: $unit 0; margin: $unit 0;
@ -256,6 +293,27 @@
border-top: 1px solid var(--button-bg); border-top: 1px solid var(--button-bg);
margin: $unit-2x 0; margin: $unit-2x 0;
} }
// Responsive YouTube video embed
.video-wrapper {
position: relative;
padding-bottom: 56.25%; // 16:9 aspect ratio
height: 0;
overflow: hidden;
margin: $unit 0;
border-radius: $card-corner;
background: var(--button-bg);
iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 0;
border-radius: $card-corner;
}
}
} }
&.truncate { &.truncate {
@ -267,10 +325,13 @@
// Hide block elements that might break truncation // Hide block elements that might break truncation
:global { :global {
pre, blockquote, ul, ol { pre,
blockquote,
ul,
ol {
display: inline; display: inline;
} }
} }
} }
} }
</style> </style>