11// Licensed to the .NET Foundation under one or more agreements.
22// The .NET Foundation licenses this file to you under the MIT license.
33
4+ using System . Collections . ObjectModel ;
5+ using System . Diagnostics ;
6+ using System . Text . RegularExpressions ;
47using System . Web ;
58using Markdig . Renderers ;
69using Markdig . Renderers . Html ;
710
811namespace Docfx . MarkdigEngine . Extensions ;
912
10- public class QuoteSectionNoteRender : HtmlObjectRenderer < QuoteSectionNoteBlock >
13+ public partial class QuoteSectionNoteRender : HtmlObjectRenderer < QuoteSectionNoteBlock >
1114{
1215 private readonly MarkdownContext _context ;
1316 private readonly Dictionary < string , string > _notes ;
@@ -107,6 +110,7 @@ public static string FixUpLink(string link)
107110 if ( Uri . TryCreate ( link , UriKind . Absolute , out Uri videoLink ) )
108111 {
109112 var host = videoLink . Host ;
113+ var path = videoLink . LocalPath ;
110114 var query = videoLink . Query ;
111115 if ( query . Length > 1 )
112116 {
@@ -125,16 +129,107 @@ public static string FixUpLink(string link)
125129 query += "&nocookie=true" ;
126130 }
127131 }
128- else if ( host . Equals ( "youtube.com" , StringComparison . OrdinalIgnoreCase ) || host . Equals ( "www.youtube.com" , StringComparison . OrdinalIgnoreCase ) )
132+ else if ( hostsYouTube . Contains ( host , StringComparer . OrdinalIgnoreCase ) )
129133 {
130134 // case 2, YouTube video
131- host = "www.youtube-nocookie.com" ;
135+ var idYouTube = GetYouTubeId ( host , path , ref query ) ;
136+ if ( idYouTube != null )
137+ {
138+ host = "www.youtube-nocookie.com" ;
139+ path = "/embed/" + idYouTube ;
140+ }
141+ else
142+ {
143+ //YouTube Playlist
144+ var listYouTube = GetYouTubeList ( query ) ;
145+ if ( listYouTube != null )
146+ {
147+ host = "www.youtube-nocookie.com" ;
148+ path = "/embed/videoseries" ;
149+ query = "list=" + listYouTube ;
150+ //No need to modify query parameters, "list=..." is the same
151+ }
152+ }
153+
154+ //Only related videos from the same channel
155+ //https://developers.google.com/youtube/player_parameters
156+ //Add rel=0 unless specified in the original link
157+ if ( query . Split ( '&' ) . Any ( q => q . StartsWith ( "rel=" ) ) == false )
158+ {
159+ if ( query . Length == 0 )
160+ query = "rel=0" ;
161+ else
162+ query += "&rel=0" ;
163+ }
164+
165+ //Keep this to preserve previous behavior
166+ if ( host . Equals ( "youtube.com" , StringComparison . OrdinalIgnoreCase ) || host . Equals ( "www.youtube.com" , StringComparison . OrdinalIgnoreCase ) )
167+ {
168+ host = "www.youtube-nocookie.com" ;
169+ }
132170 }
133171
134- var builder = new UriBuilder ( videoLink ) { Host = host , Query = query } ;
172+ var builder = new UriBuilder ( videoLink ) { Host = host , Path = path , Query = query } ;
135173 link = builder . Uri . ToString ( ) ;
136174 }
137175
138176 return link ;
139177 }
178+
179+ static readonly ReadOnlyCollection < string > hostsYouTube = new string [ ] {
180+ "youtube.com" ,
181+ "www.youtube.com" ,
182+ "youtu.be" ,
183+ "www.youtube-nocookie.com" ,
184+ } . AsReadOnly ( ) ;
185+
186+ private static string GetYouTubeId ( string host , string path , ref string query )
187+ {
188+ if ( host == "youtu.be" )
189+ {
190+ return path . Substring ( 1 ) ;
191+ }
192+
193+ var match = ReYouTubeQueryVideo ( ) . Match ( query ) ;
194+ if ( match . Success )
195+ {
196+ //Remove from query
197+ query = query . Replace ( match . Groups [ 0 ] . Value , "" ) . Trim ( '&' ) . Replace ( "&&" , "&" ) ;
198+ return match . Groups [ 2 ] . Value ;
199+ }
200+
201+ match = ReYouTubePathId ( ) . Match ( path ) ;
202+ if ( match . Success )
203+ {
204+ var id = match . Groups [ 1 ] . Value ;
205+
206+ if ( id == "videoseries" )
207+ return null ;
208+
209+ return id ;
210+ }
211+
212+ return null ;
213+ }
214+
215+ [ GeneratedRegex ( @"(^|&)v=([^&]+)" ) ]
216+ private static partial Regex ReYouTubeQueryVideo ( ) ;
217+
218+ [ GeneratedRegex ( @"(^|&)list=([^&]+)" ) ]
219+ private static partial Regex ReYouTubeQueryList ( ) ;
220+
221+ [ GeneratedRegex ( @"/embed/([^/]+)$" ) ]
222+ private static partial Regex ReYouTubePathId ( ) ;
223+
224+ private static string GetYouTubeList ( string query )
225+ {
226+ var match = ReYouTubeQueryList ( ) . Match ( query ) ;
227+ if ( match . Success )
228+ {
229+ return match . Groups [ 2 ] . Value ;
230+ }
231+
232+ return null ;
233+ }
234+
140235}
0 commit comments