diff --git a/toju-app/src/app/domains/chat/feature/chat-messages/components/message-item/chat-message-markdown.component.html b/toju-app/src/app/domains/chat/feature/chat-messages/components/message-item/chat-message-markdown.component.html
index c4342f0..b97474b 100644
--- a/toju-app/src/app/domains/chat/feature/chat-messages/components/message-item/chat-message-markdown.component.html
+++ b/toju-app/src/app/domains/chat/feature/chat-messages/components/message-item/chat-message-markdown.component.html
@@ -32,4 +32,19 @@
}
+
+
+ @if (isYoutubeUrl(node.url)) {
+
+ }
+
diff --git a/toju-app/src/app/domains/chat/feature/chat-messages/components/message-item/chat-message-markdown.component.ts b/toju-app/src/app/domains/chat/feature/chat-messages/components/message-item/chat-message-markdown.component.ts
index 53cd9f7..e929c11 100644
--- a/toju-app/src/app/domains/chat/feature/chat-messages/components/message-item/chat-message-markdown.component.ts
+++ b/toju-app/src/app/domains/chat/feature/chat-messages/components/message-item/chat-message-markdown.component.ts
@@ -6,6 +6,7 @@ import remarkGfm from 'remark-gfm';
import remarkParse from 'remark-parse';
import { unified } from 'unified';
import { ChatImageProxyFallbackDirective } from '../../../chat-image-proxy-fallback.directive';
+import { ChatYoutubeEmbedComponent, isYoutubeUrl } from './chat-youtube-embed.component';
const PRISM_LANGUAGE_ALIASES: Record = {
cs: 'csharp',
@@ -38,7 +39,8 @@ const REMARK_PROCESSOR = unified()
CommonModule,
RemarkModule,
MermaidComponent,
- ChatImageProxyFallbackDirective
+ ChatImageProxyFallbackDirective,
+ ChatYoutubeEmbedComponent
],
templateUrl: './chat-message-markdown.component.html'
})
@@ -57,6 +59,10 @@ export class ChatMessageMarkdownComponent {
return KLIPY_MEDIA_URL_PATTERN.test(url);
}
+ isYoutubeUrl(url?: string): boolean {
+ return isYoutubeUrl(url);
+ }
+
isMermaidCodeBlock(lang?: string): boolean {
return this.normalizeCodeLanguage(lang) === 'mermaid';
}
diff --git a/toju-app/src/app/domains/chat/feature/chat-messages/components/message-item/chat-youtube-embed.component.ts b/toju-app/src/app/domains/chat/feature/chat-messages/components/message-item/chat-youtube-embed.component.ts
new file mode 100644
index 0000000..1380352
--- /dev/null
+++ b/toju-app/src/app/domains/chat/feature/chat-messages/components/message-item/chat-youtube-embed.component.ts
@@ -0,0 +1,48 @@
+import { Component, computed, input } from '@angular/core';
+import { DomSanitizer } from '@angular/platform-browser';
+
+const YOUTUBE_URL_PATTERN = /(?:youtube\.com\/(?:watch\?.*v=|embed\/|shorts\/)|youtu\.be\/)([\w-]{11})/;
+
+@Component({
+ selector: 'app-chat-youtube-embed',
+ standalone: true,
+ template: `
+ @if (videoId()) {
+
+
+
+ }
+ `
+})
+export class ChatYoutubeEmbedComponent {
+ readonly url = input.required();
+
+ readonly videoId = computed(() => {
+ const match = this.url().match(YOUTUBE_URL_PATTERN);
+
+ return match?.[1] ?? null;
+ });
+
+ readonly embedUrl = computed(() => {
+ const id = this.videoId();
+
+ if (!id)
+ return '';
+
+ return this.sanitizer.bypassSecurityTrustResourceUrl(
+ `https://www.youtube-nocookie.com/embed/${encodeURIComponent(id)}`
+ );
+ });
+
+ constructor(private readonly sanitizer: DomSanitizer) {}
+}
+
+export function isYoutubeUrl(url?: string): boolean {
+ return !!url && YOUTUBE_URL_PATTERN.test(url);
+}
diff --git a/toju-app/src/index.html b/toju-app/src/index.html
index b252b3f..5c11f47 100644
--- a/toju-app/src/index.html
+++ b/toju-app/src/index.html
@@ -10,7 +10,7 @@
/>