[{"data":1,"prerenderedAt":3277},["ShallowReactive",2],{"article-md-editor-v3-embed-youtube-custom":3},{"id":4,"title":5,"body":6,"date":3264,"description":24,"extension":3265,"meta":3266,"navigation":51,"path":3272,"rawbody":3273,"seo":3274,"stem":3275,"__hash__":3276},"content\u002Fyehangzhou\u002Fposts\u002Fmd-editor-v3-embed-youtube-custom.md","md-editor-v3（版本：5.8.5）插件自定义工具栏增加嵌入YouTube视频功能",{"type":7,"value":8,"toc":3262},"minimark",[9,13,25,28,2158,2161,3223,3226,3234,3247,3258],[10,11,12],"p",{},"先看效果",[10,14,15],{},[16,17,21],"a",{"href":18,"rel":19},"https:\u002F\u002Fcdn.jsdelivr.net\u002Fgh\u002Ftomdehub\u002Fresources\u002Fbe\u002Ff16d677e3033c8b5d8288f0dbd586d61\u002Fblog_6038391571834635959\u002F2026-07\u002F2026070117_U3-Zv1fvg.png",[20],"nofollow",[22,23],"img",{"alt":24,"src":18},"",[10,26,27],{},"第一步：先引入 utils\u002Fyoutube.ts 文件",[29,30,34],"pre",{"className":31,"code":32,"language":33,"meta":24,"style":24},"language-ts shiki shiki-themes github-light github-dark","\u002F\u002F utils\u002Fyoutube.ts\n\n\u002F**\n * YouTube 链接解析结果\n *\u002F\nexport interface YouTubeParseResult {\n  videoId: string | null\n  type: 'watch' | 'youtu.be' | 'embed' | 'short' | 'invalid'\n  timestamp?: number \u002F\u002F 时间戳（秒）\n  playlistId?: string | null\n}\n\n\u002F**\n * 从 YouTube 链接中提取视频 ID\n * 支持格式：\n * - https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=VIDEO_ID\n * - https:\u002F\u002Fyoutu.be\u002FVIDEO_ID\n * - https:\u002F\u002Fwww.youtube.com\u002Fembed\u002FVIDEO_ID\n * - https:\u002F\u002Fwww.youtube.com\u002Fshorts\u002FVIDEO_ID\n * - https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=VIDEO_ID&t=30s\n *\n * @param url YouTube 链接\n * @returns 视频 ID，如果无法解析则返回 null\n *\u002F\nexport function getYouTubeVideoId(url: string): string | null {\n  if (!url || typeof url !== 'string') return null\n\n  try {\n    const parsedUrl = new URL(url)\n    const hostname = parsedUrl.hostname\n\n    \u002F\u002F 1. 标准 watch 链接: ?v=VIDEO_ID\n    if (hostname.includes('youtube.com') || hostname.includes('youtu.be')) {\n      \u002F\u002F 尝试从查询参数获取 v\n      const videoId = parsedUrl.searchParams.get('v')\n      if (videoId) return videoId\n\n      \u002F\u002F 2. youtu.be\u002FVIDEO_ID 或 youtube.com\u002Fembed\u002FVIDEO_ID\n      const pathname = parsedUrl.pathname\n      const pathSegments = pathname.split('\u002F').filter((seg) => seg.length > 0)\n\n      \u002F\u002F \u002Fembed\u002FVIDEO_ID\n      if (pathname.includes('\u002Fembed\u002F') && pathSegments.length >= 2) {\n        return pathSegments[pathSegments.length - 1]\n      }\n\n      \u002F\u002F \u002Fshorts\u002FVIDEO_ID\n      if (pathname.includes('\u002Fshorts\u002F') && pathSegments.length >= 2) {\n        return pathSegments[pathSegments.length - 1]\n      }\n\n      \u002F\u002F youtu.be\u002FVIDEO_ID\n      if (hostname.includes('youtu.be') && pathSegments.length >= 1) {\n        return pathSegments[0]\n      }\n\n      \u002F\u002F \u002Fv\u002FVIDEO_ID (旧格式)\n      if (pathname.includes('\u002Fv\u002F') && pathSegments.length >= 2) {\n        return pathSegments[pathSegments.length - 1]\n      }\n    }\n\n    return null\n  } catch {\n    \u002F\u002F URL 格式无效\n    return null\n  }\n}\n\n\u002F**\n * 从 YouTube 链接中提取完整信息（视频 ID、类型、时间戳等）\n *\u002F\nexport function parseYouTubeUrl(url: string): YouTubeParseResult {\n  const defaultResult: YouTubeParseResult = {\n    videoId: null,\n    type: 'invalid',\n  }\n\n  if (!url || typeof url !== 'string') return defaultResult\n\n  try {\n    const parsedUrl = new URL(url)\n    const hostname = parsedUrl.hostname\n    let videoId: string | null = null\n    let type: YouTubeParseResult['type'] = 'invalid'\n    let timestamp: number | undefined\n    const playlistId = parsedUrl.searchParams.get('list')\n\n    \u002F\u002F 解析时间戳\n    const tParam = parsedUrl.searchParams.get('t')\n    if (tParam) {\n      const match = tParam.match(\u002F^(\\d+)(?:s|m|h)?$\u002F)\n      if (match) {\n        timestamp = parseInt(match[1], 10)\n      }\n    }\n\n    \u002F\u002F 检测链接类型并提取 ID\n    if (hostname.includes('youtube.com') || hostname.includes('youtu.be')) {\n      \u002F\u002F 标准 watch 链接\n      const vParam = parsedUrl.searchParams.get('v')\n      if (vParam) {\n        videoId = vParam\n        type = 'watch'\n        return { videoId, type, timestamp, playlistId }\n      }\n\n      const pathname = parsedUrl.pathname\n      const pathSegments = pathname.split('\u002F').filter((seg) => seg.length > 0)\n\n      \u002F\u002F youtu.be\u002FVIDEO_ID\n      if (hostname.includes('youtu.be') && pathSegments.length >= 1) {\n        videoId = pathSegments[0]\n        type = 'youtu.be'\n        return { videoId, type, timestamp, playlistId }\n      }\n\n      \u002F\u002F \u002Fembed\u002FVIDEO_ID\n      if (pathname.includes('\u002Fembed\u002F') && pathSegments.length >= 2) {\n        videoId = pathSegments[pathSegments.length - 1]\n        type = 'embed'\n        return { videoId, type, timestamp, playlistId }\n      }\n\n      \u002F\u002F \u002Fshorts\u002FVIDEO_ID\n      if (pathname.includes('\u002Fshorts\u002F') && pathSegments.length >= 2) {\n        videoId = pathSegments[pathSegments.length - 1]\n        type = 'short'\n        return { videoId, type, timestamp, playlistId }\n      }\n    }\n\n    return { videoId: null, type: 'invalid' }\n  } catch {\n    return defaultResult\n  }\n}\n\n\u002F**\n * 验证 YouTube 链接是否有效\n *\u002F\nexport function isValidYouTubeUrl(url: string): boolean {\n  return getYouTubeVideoId(url) !== null\n}\n\n\u002F**\n * 生成 YouTube 嵌入链接\n * @param videoId 视频 ID\n * @param options 配置选项\n *\u002F\nexport function getYouTubeEmbedUrl(\n  videoId: string,\n  options?: {\n    autoplay?: boolean\n    muted?: boolean\n    start?: number \u002F\u002F 开始时间（秒）\n    end?: number \u002F\u002F 结束时间（秒）\n    controls?: boolean\n    rel?: boolean \u002F\u002F 是否显示相关视频\n  }\n): string {\n  const base = `https:\u002F\u002Fwww.youtube.com\u002Fembed\u002F${videoId}`\n  const params = new URLSearchParams()\n\n  if (options?.autoplay) params.set('autoplay', '1')\n  if (options?.muted) params.set('mute', '1')\n  if (options?.start) params.set('start', String(options.start))\n  if (options?.end) params.set('end', String(options.end))\n  if (options?.controls === false) params.set('controls', '0')\n  if (options?.rel === false) params.set('rel', '0')\n\n  const queryString = params.toString()\n  return queryString ? `${base}?${queryString}` : base\n}\n","ts",[35,36,37,46,53,59,65,71,89,109,141,156,170,176,181,186,192,198,204,210,216,222,228,234,249,260,265,300,338,343,351,372,385,390,396,430,436,461,475,480,486,499,551,556,562,596,616,622,627,633,661,676,681,686,692,719,732,737,742,748,776,791,796,802,807,815,826,832,839,845,850,855,860,866,871,897,914,926,937,942,947,975,980,987,1002,1013,1033,1059,1076,1097,1102,1108,1129,1137,1191,1199,1224,1229,1234,1239,1245,1272,1278,1298,1306,1317,1328,1336,1341,1346,1357,1396,1401,1406,1433,1446,1456,1463,1468,1473,1478,1505,1522,1532,1539,1544,1549,1554,1581,1598,1608,1615,1620,1625,1630,1648,1657,1664,1669,1674,1679,1684,1690,1695,1722,1737,1742,1747,1752,1758,1770,1783,1788,1801,1812,1822,1833,1843,1856,1869,1879,1892,1897,1908,1927,1945,1950,1974,1995,2018,2040,2071,2098,2103,2121,2153],"code",{"__ignoreMap":24},[38,39,42],"span",{"class":40,"line":41},"line",1,[38,43,45],{"class":44},"sJ8bj","\u002F\u002F utils\u002Fyoutube.ts\n",[38,47,49],{"class":40,"line":48},2,[38,50,52],{"emptyLinePlaceholder":51},true,"\n",[38,54,56],{"class":40,"line":55},3,[38,57,58],{"class":44},"\u002F**\n",[38,60,62],{"class":40,"line":61},4,[38,63,64],{"class":44}," * YouTube 链接解析结果\n",[38,66,68],{"class":40,"line":67},5,[38,69,70],{"class":44}," *\u002F\n",[38,72,74,78,81,85],{"class":40,"line":73},6,[38,75,77],{"class":76},"szBVR","export",[38,79,80],{"class":76}," interface",[38,82,84],{"class":83},"sScJk"," YouTubeParseResult",[38,86,88],{"class":87},"sVt8B"," {\n",[38,90,92,96,99,103,106],{"class":40,"line":91},7,[38,93,95],{"class":94},"s4XuR","  videoId",[38,97,98],{"class":76},":",[38,100,102],{"class":101},"sj4cs"," string",[38,104,105],{"class":76}," |",[38,107,108],{"class":101}," null\n",[38,110,112,115,117,121,123,126,128,131,133,136,138],{"class":40,"line":111},8,[38,113,114],{"class":94},"  type",[38,116,98],{"class":76},[38,118,120],{"class":119},"sZZnC"," 'watch'",[38,122,105],{"class":76},[38,124,125],{"class":119}," 'youtu.be'",[38,127,105],{"class":76},[38,129,130],{"class":119}," 'embed'",[38,132,105],{"class":76},[38,134,135],{"class":119}," 'short'",[38,137,105],{"class":76},[38,139,140],{"class":119}," 'invalid'\n",[38,142,144,147,150,153],{"class":40,"line":143},9,[38,145,146],{"class":94},"  timestamp",[38,148,149],{"class":76},"?:",[38,151,152],{"class":101}," number",[38,154,155],{"class":44}," \u002F\u002F 时间戳（秒）\n",[38,157,159,162,164,166,168],{"class":40,"line":158},10,[38,160,161],{"class":94},"  playlistId",[38,163,149],{"class":76},[38,165,102],{"class":101},[38,167,105],{"class":76},[38,169,108],{"class":101},[38,171,173],{"class":40,"line":172},11,[38,174,175],{"class":87},"}\n",[38,177,179],{"class":40,"line":178},12,[38,180,52],{"emptyLinePlaceholder":51},[38,182,184],{"class":40,"line":183},13,[38,185,58],{"class":44},[38,187,189],{"class":40,"line":188},14,[38,190,191],{"class":44}," * 从 YouTube 链接中提取视频 ID\n",[38,193,195],{"class":40,"line":194},15,[38,196,197],{"class":44}," * 支持格式：\n",[38,199,201],{"class":40,"line":200},16,[38,202,203],{"class":44}," * - https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=VIDEO_ID\n",[38,205,207],{"class":40,"line":206},17,[38,208,209],{"class":44}," * - https:\u002F\u002Fyoutu.be\u002FVIDEO_ID\n",[38,211,213],{"class":40,"line":212},18,[38,214,215],{"class":44}," * - https:\u002F\u002Fwww.youtube.com\u002Fembed\u002FVIDEO_ID\n",[38,217,219],{"class":40,"line":218},19,[38,220,221],{"class":44}," * - https:\u002F\u002Fwww.youtube.com\u002Fshorts\u002FVIDEO_ID\n",[38,223,225],{"class":40,"line":224},20,[38,226,227],{"class":44}," * - https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=VIDEO_ID&t=30s\n",[38,229,231],{"class":40,"line":230},21,[38,232,233],{"class":44}," *\n",[38,235,237,240,243,246],{"class":40,"line":236},22,[38,238,239],{"class":44}," * ",[38,241,242],{"class":76},"@param",[38,244,245],{"class":87}," url",[38,247,248],{"class":44}," YouTube 链接\n",[38,250,252,254,257],{"class":40,"line":251},23,[38,253,239],{"class":44},[38,255,256],{"class":76},"@returns",[38,258,259],{"class":44}," 视频 ID，如果无法解析则返回 null\n",[38,261,263],{"class":40,"line":262},24,[38,264,70],{"class":44},[38,266,268,270,273,276,279,282,284,286,289,291,293,295,298],{"class":40,"line":267},25,[38,269,77],{"class":76},[38,271,272],{"class":76}," function",[38,274,275],{"class":83}," getYouTubeVideoId",[38,277,278],{"class":87},"(",[38,280,281],{"class":94},"url",[38,283,98],{"class":76},[38,285,102],{"class":101},[38,287,288],{"class":87},")",[38,290,98],{"class":76},[38,292,102],{"class":101},[38,294,105],{"class":76},[38,296,297],{"class":101}," null",[38,299,88],{"class":87},[38,301,303,306,309,312,315,318,321,324,327,330,333,336],{"class":40,"line":302},26,[38,304,305],{"class":76},"  if",[38,307,308],{"class":87}," (",[38,310,311],{"class":76},"!",[38,313,314],{"class":87},"url ",[38,316,317],{"class":76},"||",[38,319,320],{"class":76}," typeof",[38,322,323],{"class":87}," url ",[38,325,326],{"class":76},"!==",[38,328,329],{"class":119}," 'string'",[38,331,332],{"class":87},") ",[38,334,335],{"class":76},"return",[38,337,108],{"class":101},[38,339,341],{"class":40,"line":340},27,[38,342,52],{"emptyLinePlaceholder":51},[38,344,346,349],{"class":40,"line":345},28,[38,347,348],{"class":76},"  try",[38,350,88],{"class":87},[38,352,354,357,360,363,366,369],{"class":40,"line":353},29,[38,355,356],{"class":76},"    const",[38,358,359],{"class":101}," parsedUrl",[38,361,362],{"class":76}," =",[38,364,365],{"class":76}," new",[38,367,368],{"class":83}," URL",[38,370,371],{"class":87},"(url)\n",[38,373,375,377,380,382],{"class":40,"line":374},30,[38,376,356],{"class":76},[38,378,379],{"class":101}," hostname",[38,381,362],{"class":76},[38,383,384],{"class":87}," parsedUrl.hostname\n",[38,386,388],{"class":40,"line":387},31,[38,389,52],{"emptyLinePlaceholder":51},[38,391,393],{"class":40,"line":392},32,[38,394,395],{"class":44},"    \u002F\u002F 1. 标准 watch 链接: ?v=VIDEO_ID\n",[38,397,399,402,405,408,410,413,415,417,420,422,424,427],{"class":40,"line":398},33,[38,400,401],{"class":76},"    if",[38,403,404],{"class":87}," (hostname.",[38,406,407],{"class":83},"includes",[38,409,278],{"class":87},[38,411,412],{"class":119},"'youtube.com'",[38,414,332],{"class":87},[38,416,317],{"class":76},[38,418,419],{"class":87}," hostname.",[38,421,407],{"class":83},[38,423,278],{"class":87},[38,425,426],{"class":119},"'youtu.be'",[38,428,429],{"class":87},")) {\n",[38,431,433],{"class":40,"line":432},34,[38,434,435],{"class":44},"      \u002F\u002F 尝试从查询参数获取 v\n",[38,437,439,442,445,447,450,453,455,458],{"class":40,"line":438},35,[38,440,441],{"class":76},"      const",[38,443,444],{"class":101}," videoId",[38,446,362],{"class":76},[38,448,449],{"class":87}," parsedUrl.searchParams.",[38,451,452],{"class":83},"get",[38,454,278],{"class":87},[38,456,457],{"class":119},"'v'",[38,459,460],{"class":87},")\n",[38,462,464,467,470,472],{"class":40,"line":463},36,[38,465,466],{"class":76},"      if",[38,468,469],{"class":87}," (videoId) ",[38,471,335],{"class":76},[38,473,474],{"class":87}," videoId\n",[38,476,478],{"class":40,"line":477},37,[38,479,52],{"emptyLinePlaceholder":51},[38,481,483],{"class":40,"line":482},38,[38,484,485],{"class":44},"      \u002F\u002F 2. youtu.be\u002FVIDEO_ID 或 youtube.com\u002Fembed\u002FVIDEO_ID\n",[38,487,489,491,494,496],{"class":40,"line":488},39,[38,490,441],{"class":76},[38,492,493],{"class":101}," pathname",[38,495,362],{"class":76},[38,497,498],{"class":87}," parsedUrl.pathname\n",[38,500,502,504,507,509,512,515,517,520,523,526,529,532,534,537,540,543,546,549],{"class":40,"line":501},40,[38,503,441],{"class":76},[38,505,506],{"class":101}," pathSegments",[38,508,362],{"class":76},[38,510,511],{"class":87}," pathname.",[38,513,514],{"class":83},"split",[38,516,278],{"class":87},[38,518,519],{"class":119},"'\u002F'",[38,521,522],{"class":87},").",[38,524,525],{"class":83},"filter",[38,527,528],{"class":87},"((",[38,530,531],{"class":94},"seg",[38,533,332],{"class":87},[38,535,536],{"class":76},"=>",[38,538,539],{"class":87}," seg.",[38,541,542],{"class":101},"length",[38,544,545],{"class":76}," >",[38,547,548],{"class":101}," 0",[38,550,460],{"class":87},[38,552,554],{"class":40,"line":553},41,[38,555,52],{"emptyLinePlaceholder":51},[38,557,559],{"class":40,"line":558},42,[38,560,561],{"class":44},"      \u002F\u002F \u002Fembed\u002FVIDEO_ID\n",[38,563,565,567,570,572,574,577,579,582,585,587,590,593],{"class":40,"line":564},43,[38,566,466],{"class":76},[38,568,569],{"class":87}," (pathname.",[38,571,407],{"class":83},[38,573,278],{"class":87},[38,575,576],{"class":119},"'\u002Fembed\u002F'",[38,578,332],{"class":87},[38,580,581],{"class":76},"&&",[38,583,584],{"class":87}," pathSegments.",[38,586,542],{"class":101},[38,588,589],{"class":76}," >=",[38,591,592],{"class":101}," 2",[38,594,595],{"class":87},") {\n",[38,597,599,602,605,607,610,613],{"class":40,"line":598},44,[38,600,601],{"class":76},"        return",[38,603,604],{"class":87}," pathSegments[pathSegments.",[38,606,542],{"class":101},[38,608,609],{"class":76}," -",[38,611,612],{"class":101}," 1",[38,614,615],{"class":87},"]\n",[38,617,619],{"class":40,"line":618},45,[38,620,621],{"class":87},"      }\n",[38,623,625],{"class":40,"line":624},46,[38,626,52],{"emptyLinePlaceholder":51},[38,628,630],{"class":40,"line":629},47,[38,631,632],{"class":44},"      \u002F\u002F \u002Fshorts\u002FVIDEO_ID\n",[38,634,636,638,640,642,644,647,649,651,653,655,657,659],{"class":40,"line":635},48,[38,637,466],{"class":76},[38,639,569],{"class":87},[38,641,407],{"class":83},[38,643,278],{"class":87},[38,645,646],{"class":119},"'\u002Fshorts\u002F'",[38,648,332],{"class":87},[38,650,581],{"class":76},[38,652,584],{"class":87},[38,654,542],{"class":101},[38,656,589],{"class":76},[38,658,592],{"class":101},[38,660,595],{"class":87},[38,662,664,666,668,670,672,674],{"class":40,"line":663},49,[38,665,601],{"class":76},[38,667,604],{"class":87},[38,669,542],{"class":101},[38,671,609],{"class":76},[38,673,612],{"class":101},[38,675,615],{"class":87},[38,677,679],{"class":40,"line":678},50,[38,680,621],{"class":87},[38,682,684],{"class":40,"line":683},51,[38,685,52],{"emptyLinePlaceholder":51},[38,687,689],{"class":40,"line":688},52,[38,690,691],{"class":44},"      \u002F\u002F youtu.be\u002FVIDEO_ID\n",[38,693,695,697,699,701,703,705,707,709,711,713,715,717],{"class":40,"line":694},53,[38,696,466],{"class":76},[38,698,404],{"class":87},[38,700,407],{"class":83},[38,702,278],{"class":87},[38,704,426],{"class":119},[38,706,332],{"class":87},[38,708,581],{"class":76},[38,710,584],{"class":87},[38,712,542],{"class":101},[38,714,589],{"class":76},[38,716,612],{"class":101},[38,718,595],{"class":87},[38,720,722,724,727,730],{"class":40,"line":721},54,[38,723,601],{"class":76},[38,725,726],{"class":87}," pathSegments[",[38,728,729],{"class":101},"0",[38,731,615],{"class":87},[38,733,735],{"class":40,"line":734},55,[38,736,621],{"class":87},[38,738,740],{"class":40,"line":739},56,[38,741,52],{"emptyLinePlaceholder":51},[38,743,745],{"class":40,"line":744},57,[38,746,747],{"class":44},"      \u002F\u002F \u002Fv\u002FVIDEO_ID (旧格式)\n",[38,749,751,753,755,757,759,762,764,766,768,770,772,774],{"class":40,"line":750},58,[38,752,466],{"class":76},[38,754,569],{"class":87},[38,756,407],{"class":83},[38,758,278],{"class":87},[38,760,761],{"class":119},"'\u002Fv\u002F'",[38,763,332],{"class":87},[38,765,581],{"class":76},[38,767,584],{"class":87},[38,769,542],{"class":101},[38,771,589],{"class":76},[38,773,592],{"class":101},[38,775,595],{"class":87},[38,777,779,781,783,785,787,789],{"class":40,"line":778},59,[38,780,601],{"class":76},[38,782,604],{"class":87},[38,784,542],{"class":101},[38,786,609],{"class":76},[38,788,612],{"class":101},[38,790,615],{"class":87},[38,792,794],{"class":40,"line":793},60,[38,795,621],{"class":87},[38,797,799],{"class":40,"line":798},61,[38,800,801],{"class":87},"    }\n",[38,803,805],{"class":40,"line":804},62,[38,806,52],{"emptyLinePlaceholder":51},[38,808,810,813],{"class":40,"line":809},63,[38,811,812],{"class":76},"    return",[38,814,108],{"class":101},[38,816,818,821,824],{"class":40,"line":817},64,[38,819,820],{"class":87},"  } ",[38,822,823],{"class":76},"catch",[38,825,88],{"class":87},[38,827,829],{"class":40,"line":828},65,[38,830,831],{"class":44},"    \u002F\u002F URL 格式无效\n",[38,833,835,837],{"class":40,"line":834},66,[38,836,812],{"class":76},[38,838,108],{"class":101},[38,840,842],{"class":40,"line":841},67,[38,843,844],{"class":87},"  }\n",[38,846,848],{"class":40,"line":847},68,[38,849,175],{"class":87},[38,851,853],{"class":40,"line":852},69,[38,854,52],{"emptyLinePlaceholder":51},[38,856,858],{"class":40,"line":857},70,[38,859,58],{"class":44},[38,861,863],{"class":40,"line":862},71,[38,864,865],{"class":44}," * 从 YouTube 链接中提取完整信息（视频 ID、类型、时间戳等）\n",[38,867,869],{"class":40,"line":868},72,[38,870,70],{"class":44},[38,872,874,876,878,881,883,885,887,889,891,893,895],{"class":40,"line":873},73,[38,875,77],{"class":76},[38,877,272],{"class":76},[38,879,880],{"class":83}," parseYouTubeUrl",[38,882,278],{"class":87},[38,884,281],{"class":94},[38,886,98],{"class":76},[38,888,102],{"class":101},[38,890,288],{"class":87},[38,892,98],{"class":76},[38,894,84],{"class":83},[38,896,88],{"class":87},[38,898,900,903,906,908,910,912],{"class":40,"line":899},74,[38,901,902],{"class":76},"  const",[38,904,905],{"class":101}," defaultResult",[38,907,98],{"class":76},[38,909,84],{"class":83},[38,911,362],{"class":76},[38,913,88],{"class":87},[38,915,917,920,923],{"class":40,"line":916},75,[38,918,919],{"class":87},"    videoId: ",[38,921,922],{"class":101},"null",[38,924,925],{"class":87},",\n",[38,927,929,932,935],{"class":40,"line":928},76,[38,930,931],{"class":87},"    type: ",[38,933,934],{"class":119},"'invalid'",[38,936,925],{"class":87},[38,938,940],{"class":40,"line":939},77,[38,941,844],{"class":87},[38,943,945],{"class":40,"line":944},78,[38,946,52],{"emptyLinePlaceholder":51},[38,948,950,952,954,956,958,960,962,964,966,968,970,972],{"class":40,"line":949},79,[38,951,305],{"class":76},[38,953,308],{"class":87},[38,955,311],{"class":76},[38,957,314],{"class":87},[38,959,317],{"class":76},[38,961,320],{"class":76},[38,963,323],{"class":87},[38,965,326],{"class":76},[38,967,329],{"class":119},[38,969,332],{"class":87},[38,971,335],{"class":76},[38,973,974],{"class":87}," defaultResult\n",[38,976,978],{"class":40,"line":977},80,[38,979,52],{"emptyLinePlaceholder":51},[38,981,983,985],{"class":40,"line":982},81,[38,984,348],{"class":76},[38,986,88],{"class":87},[38,988,990,992,994,996,998,1000],{"class":40,"line":989},82,[38,991,356],{"class":76},[38,993,359],{"class":101},[38,995,362],{"class":76},[38,997,365],{"class":76},[38,999,368],{"class":83},[38,1001,371],{"class":87},[38,1003,1005,1007,1009,1011],{"class":40,"line":1004},83,[38,1006,356],{"class":76},[38,1008,379],{"class":101},[38,1010,362],{"class":76},[38,1012,384],{"class":87},[38,1014,1016,1019,1021,1023,1025,1027,1029,1031],{"class":40,"line":1015},84,[38,1017,1018],{"class":76},"    let",[38,1020,444],{"class":87},[38,1022,98],{"class":76},[38,1024,102],{"class":101},[38,1026,105],{"class":76},[38,1028,297],{"class":101},[38,1030,362],{"class":76},[38,1032,108],{"class":101},[38,1034,1036,1038,1041,1043,1045,1048,1051,1054,1057],{"class":40,"line":1035},85,[38,1037,1018],{"class":76},[38,1039,1040],{"class":87}," type",[38,1042,98],{"class":76},[38,1044,84],{"class":83},[38,1046,1047],{"class":87},"[",[38,1049,1050],{"class":119},"'type'",[38,1052,1053],{"class":87},"] ",[38,1055,1056],{"class":76},"=",[38,1058,140],{"class":119},[38,1060,1062,1064,1067,1069,1071,1073],{"class":40,"line":1061},86,[38,1063,1018],{"class":76},[38,1065,1066],{"class":87}," timestamp",[38,1068,98],{"class":76},[38,1070,152],{"class":101},[38,1072,105],{"class":76},[38,1074,1075],{"class":101}," undefined\n",[38,1077,1079,1081,1084,1086,1088,1090,1092,1095],{"class":40,"line":1078},87,[38,1080,356],{"class":76},[38,1082,1083],{"class":101}," playlistId",[38,1085,362],{"class":76},[38,1087,449],{"class":87},[38,1089,452],{"class":83},[38,1091,278],{"class":87},[38,1093,1094],{"class":119},"'list'",[38,1096,460],{"class":87},[38,1098,1100],{"class":40,"line":1099},88,[38,1101,52],{"emptyLinePlaceholder":51},[38,1103,1105],{"class":40,"line":1104},89,[38,1106,1107],{"class":44},"    \u002F\u002F 解析时间戳\n",[38,1109,1111,1113,1116,1118,1120,1122,1124,1127],{"class":40,"line":1110},90,[38,1112,356],{"class":76},[38,1114,1115],{"class":101}," tParam",[38,1117,362],{"class":76},[38,1119,449],{"class":87},[38,1121,452],{"class":83},[38,1123,278],{"class":87},[38,1125,1126],{"class":119},"'t'",[38,1128,460],{"class":87},[38,1130,1132,1134],{"class":40,"line":1131},91,[38,1133,401],{"class":76},[38,1135,1136],{"class":87}," (tParam) {\n",[38,1138,1140,1142,1145,1147,1150,1153,1155,1158,1161,1164,1167,1170,1173,1176,1179,1181,1184,1187,1189],{"class":40,"line":1139},92,[38,1141,441],{"class":76},[38,1143,1144],{"class":101}," match",[38,1146,362],{"class":76},[38,1148,1149],{"class":87}," tParam.",[38,1151,1152],{"class":83},"match",[38,1154,278],{"class":87},[38,1156,1157],{"class":119},"\u002F",[38,1159,1160],{"class":76},"^",[38,1162,278],{"class":1163},"sA_wV",[38,1165,1166],{"class":101},"\\d",[38,1168,1169],{"class":76},"+",[38,1171,1172],{"class":1163},")(?:s",[38,1174,1175],{"class":76},"|",[38,1177,1178],{"class":1163},"m",[38,1180,1175],{"class":76},[38,1182,1183],{"class":1163},"h)",[38,1185,1186],{"class":76},"?$",[38,1188,1157],{"class":119},[38,1190,460],{"class":87},[38,1192,1194,1196],{"class":40,"line":1193},93,[38,1195,466],{"class":76},[38,1197,1198],{"class":87}," (match) {\n",[38,1200,1202,1205,1207,1210,1213,1216,1219,1222],{"class":40,"line":1201},94,[38,1203,1204],{"class":87},"        timestamp ",[38,1206,1056],{"class":76},[38,1208,1209],{"class":83}," parseInt",[38,1211,1212],{"class":87},"(match[",[38,1214,1215],{"class":101},"1",[38,1217,1218],{"class":87},"], ",[38,1220,1221],{"class":101},"10",[38,1223,460],{"class":87},[38,1225,1227],{"class":40,"line":1226},95,[38,1228,621],{"class":87},[38,1230,1232],{"class":40,"line":1231},96,[38,1233,801],{"class":87},[38,1235,1237],{"class":40,"line":1236},97,[38,1238,52],{"emptyLinePlaceholder":51},[38,1240,1242],{"class":40,"line":1241},98,[38,1243,1244],{"class":44},"    \u002F\u002F 检测链接类型并提取 ID\n",[38,1246,1248,1250,1252,1254,1256,1258,1260,1262,1264,1266,1268,1270],{"class":40,"line":1247},99,[38,1249,401],{"class":76},[38,1251,404],{"class":87},[38,1253,407],{"class":83},[38,1255,278],{"class":87},[38,1257,412],{"class":119},[38,1259,332],{"class":87},[38,1261,317],{"class":76},[38,1263,419],{"class":87},[38,1265,407],{"class":83},[38,1267,278],{"class":87},[38,1269,426],{"class":119},[38,1271,429],{"class":87},[38,1273,1275],{"class":40,"line":1274},100,[38,1276,1277],{"class":44},"      \u002F\u002F 标准 watch 链接\n",[38,1279,1281,1283,1286,1288,1290,1292,1294,1296],{"class":40,"line":1280},101,[38,1282,441],{"class":76},[38,1284,1285],{"class":101}," vParam",[38,1287,362],{"class":76},[38,1289,449],{"class":87},[38,1291,452],{"class":83},[38,1293,278],{"class":87},[38,1295,457],{"class":119},[38,1297,460],{"class":87},[38,1299,1301,1303],{"class":40,"line":1300},102,[38,1302,466],{"class":76},[38,1304,1305],{"class":87}," (vParam) {\n",[38,1307,1309,1312,1314],{"class":40,"line":1308},103,[38,1310,1311],{"class":87},"        videoId ",[38,1313,1056],{"class":76},[38,1315,1316],{"class":87}," vParam\n",[38,1318,1320,1323,1325],{"class":40,"line":1319},104,[38,1321,1322],{"class":87},"        type ",[38,1324,1056],{"class":76},[38,1326,1327],{"class":119}," 'watch'\n",[38,1329,1331,1333],{"class":40,"line":1330},105,[38,1332,601],{"class":76},[38,1334,1335],{"class":87}," { videoId, type, timestamp, playlistId }\n",[38,1337,1339],{"class":40,"line":1338},106,[38,1340,621],{"class":87},[38,1342,1344],{"class":40,"line":1343},107,[38,1345,52],{"emptyLinePlaceholder":51},[38,1347,1349,1351,1353,1355],{"class":40,"line":1348},108,[38,1350,441],{"class":76},[38,1352,493],{"class":101},[38,1354,362],{"class":76},[38,1356,498],{"class":87},[38,1358,1360,1362,1364,1366,1368,1370,1372,1374,1376,1378,1380,1382,1384,1386,1388,1390,1392,1394],{"class":40,"line":1359},109,[38,1361,441],{"class":76},[38,1363,506],{"class":101},[38,1365,362],{"class":76},[38,1367,511],{"class":87},[38,1369,514],{"class":83},[38,1371,278],{"class":87},[38,1373,519],{"class":119},[38,1375,522],{"class":87},[38,1377,525],{"class":83},[38,1379,528],{"class":87},[38,1381,531],{"class":94},[38,1383,332],{"class":87},[38,1385,536],{"class":76},[38,1387,539],{"class":87},[38,1389,542],{"class":101},[38,1391,545],{"class":76},[38,1393,548],{"class":101},[38,1395,460],{"class":87},[38,1397,1399],{"class":40,"line":1398},110,[38,1400,52],{"emptyLinePlaceholder":51},[38,1402,1404],{"class":40,"line":1403},111,[38,1405,691],{"class":44},[38,1407,1409,1411,1413,1415,1417,1419,1421,1423,1425,1427,1429,1431],{"class":40,"line":1408},112,[38,1410,466],{"class":76},[38,1412,404],{"class":87},[38,1414,407],{"class":83},[38,1416,278],{"class":87},[38,1418,426],{"class":119},[38,1420,332],{"class":87},[38,1422,581],{"class":76},[38,1424,584],{"class":87},[38,1426,542],{"class":101},[38,1428,589],{"class":76},[38,1430,612],{"class":101},[38,1432,595],{"class":87},[38,1434,1436,1438,1440,1442,1444],{"class":40,"line":1435},113,[38,1437,1311],{"class":87},[38,1439,1056],{"class":76},[38,1441,726],{"class":87},[38,1443,729],{"class":101},[38,1445,615],{"class":87},[38,1447,1449,1451,1453],{"class":40,"line":1448},114,[38,1450,1322],{"class":87},[38,1452,1056],{"class":76},[38,1454,1455],{"class":119}," 'youtu.be'\n",[38,1457,1459,1461],{"class":40,"line":1458},115,[38,1460,601],{"class":76},[38,1462,1335],{"class":87},[38,1464,1466],{"class":40,"line":1465},116,[38,1467,621],{"class":87},[38,1469,1471],{"class":40,"line":1470},117,[38,1472,52],{"emptyLinePlaceholder":51},[38,1474,1476],{"class":40,"line":1475},118,[38,1477,561],{"class":44},[38,1479,1481,1483,1485,1487,1489,1491,1493,1495,1497,1499,1501,1503],{"class":40,"line":1480},119,[38,1482,466],{"class":76},[38,1484,569],{"class":87},[38,1486,407],{"class":83},[38,1488,278],{"class":87},[38,1490,576],{"class":119},[38,1492,332],{"class":87},[38,1494,581],{"class":76},[38,1496,584],{"class":87},[38,1498,542],{"class":101},[38,1500,589],{"class":76},[38,1502,592],{"class":101},[38,1504,595],{"class":87},[38,1506,1508,1510,1512,1514,1516,1518,1520],{"class":40,"line":1507},120,[38,1509,1311],{"class":87},[38,1511,1056],{"class":76},[38,1513,604],{"class":87},[38,1515,542],{"class":101},[38,1517,609],{"class":76},[38,1519,612],{"class":101},[38,1521,615],{"class":87},[38,1523,1525,1527,1529],{"class":40,"line":1524},121,[38,1526,1322],{"class":87},[38,1528,1056],{"class":76},[38,1530,1531],{"class":119}," 'embed'\n",[38,1533,1535,1537],{"class":40,"line":1534},122,[38,1536,601],{"class":76},[38,1538,1335],{"class":87},[38,1540,1542],{"class":40,"line":1541},123,[38,1543,621],{"class":87},[38,1545,1547],{"class":40,"line":1546},124,[38,1548,52],{"emptyLinePlaceholder":51},[38,1550,1552],{"class":40,"line":1551},125,[38,1553,632],{"class":44},[38,1555,1557,1559,1561,1563,1565,1567,1569,1571,1573,1575,1577,1579],{"class":40,"line":1556},126,[38,1558,466],{"class":76},[38,1560,569],{"class":87},[38,1562,407],{"class":83},[38,1564,278],{"class":87},[38,1566,646],{"class":119},[38,1568,332],{"class":87},[38,1570,581],{"class":76},[38,1572,584],{"class":87},[38,1574,542],{"class":101},[38,1576,589],{"class":76},[38,1578,592],{"class":101},[38,1580,595],{"class":87},[38,1582,1584,1586,1588,1590,1592,1594,1596],{"class":40,"line":1583},127,[38,1585,1311],{"class":87},[38,1587,1056],{"class":76},[38,1589,604],{"class":87},[38,1591,542],{"class":101},[38,1593,609],{"class":76},[38,1595,612],{"class":101},[38,1597,615],{"class":87},[38,1599,1601,1603,1605],{"class":40,"line":1600},128,[38,1602,1322],{"class":87},[38,1604,1056],{"class":76},[38,1606,1607],{"class":119}," 'short'\n",[38,1609,1611,1613],{"class":40,"line":1610},129,[38,1612,601],{"class":76},[38,1614,1335],{"class":87},[38,1616,1618],{"class":40,"line":1617},130,[38,1619,621],{"class":87},[38,1621,1623],{"class":40,"line":1622},131,[38,1624,801],{"class":87},[38,1626,1628],{"class":40,"line":1627},132,[38,1629,52],{"emptyLinePlaceholder":51},[38,1631,1633,1635,1638,1640,1643,1645],{"class":40,"line":1632},133,[38,1634,812],{"class":76},[38,1636,1637],{"class":87}," { videoId: ",[38,1639,922],{"class":101},[38,1641,1642],{"class":87},", type: ",[38,1644,934],{"class":119},[38,1646,1647],{"class":87}," }\n",[38,1649,1651,1653,1655],{"class":40,"line":1650},134,[38,1652,820],{"class":87},[38,1654,823],{"class":76},[38,1656,88],{"class":87},[38,1658,1660,1662],{"class":40,"line":1659},135,[38,1661,812],{"class":76},[38,1663,974],{"class":87},[38,1665,1667],{"class":40,"line":1666},136,[38,1668,844],{"class":87},[38,1670,1672],{"class":40,"line":1671},137,[38,1673,175],{"class":87},[38,1675,1677],{"class":40,"line":1676},138,[38,1678,52],{"emptyLinePlaceholder":51},[38,1680,1682],{"class":40,"line":1681},139,[38,1683,58],{"class":44},[38,1685,1687],{"class":40,"line":1686},140,[38,1688,1689],{"class":44}," * 验证 YouTube 链接是否有效\n",[38,1691,1693],{"class":40,"line":1692},141,[38,1694,70],{"class":44},[38,1696,1698,1700,1702,1705,1707,1709,1711,1713,1715,1717,1720],{"class":40,"line":1697},142,[38,1699,77],{"class":76},[38,1701,272],{"class":76},[38,1703,1704],{"class":83}," isValidYouTubeUrl",[38,1706,278],{"class":87},[38,1708,281],{"class":94},[38,1710,98],{"class":76},[38,1712,102],{"class":101},[38,1714,288],{"class":87},[38,1716,98],{"class":76},[38,1718,1719],{"class":101}," boolean",[38,1721,88],{"class":87},[38,1723,1725,1728,1730,1733,1735],{"class":40,"line":1724},143,[38,1726,1727],{"class":76},"  return",[38,1729,275],{"class":83},[38,1731,1732],{"class":87},"(url) ",[38,1734,326],{"class":76},[38,1736,108],{"class":101},[38,1738,1740],{"class":40,"line":1739},144,[38,1741,175],{"class":87},[38,1743,1745],{"class":40,"line":1744},145,[38,1746,52],{"emptyLinePlaceholder":51},[38,1748,1750],{"class":40,"line":1749},146,[38,1751,58],{"class":44},[38,1753,1755],{"class":40,"line":1754},147,[38,1756,1757],{"class":44}," * 生成 YouTube 嵌入链接\n",[38,1759,1761,1763,1765,1767],{"class":40,"line":1760},148,[38,1762,239],{"class":44},[38,1764,242],{"class":76},[38,1766,444],{"class":87},[38,1768,1769],{"class":44}," 视频 ID\n",[38,1771,1773,1775,1777,1780],{"class":40,"line":1772},149,[38,1774,239],{"class":44},[38,1776,242],{"class":76},[38,1778,1779],{"class":87}," options",[38,1781,1782],{"class":44}," 配置选项\n",[38,1784,1786],{"class":40,"line":1785},150,[38,1787,70],{"class":44},[38,1789,1791,1793,1795,1798],{"class":40,"line":1790},151,[38,1792,77],{"class":76},[38,1794,272],{"class":76},[38,1796,1797],{"class":83}," getYouTubeEmbedUrl",[38,1799,1800],{"class":87},"(\n",[38,1802,1804,1806,1808,1810],{"class":40,"line":1803},152,[38,1805,95],{"class":94},[38,1807,98],{"class":76},[38,1809,102],{"class":101},[38,1811,925],{"class":87},[38,1813,1815,1818,1820],{"class":40,"line":1814},153,[38,1816,1817],{"class":94},"  options",[38,1819,149],{"class":76},[38,1821,88],{"class":87},[38,1823,1825,1828,1830],{"class":40,"line":1824},154,[38,1826,1827],{"class":94},"    autoplay",[38,1829,149],{"class":76},[38,1831,1832],{"class":101}," boolean\n",[38,1834,1836,1839,1841],{"class":40,"line":1835},155,[38,1837,1838],{"class":94},"    muted",[38,1840,149],{"class":76},[38,1842,1832],{"class":101},[38,1844,1846,1849,1851,1853],{"class":40,"line":1845},156,[38,1847,1848],{"class":94},"    start",[38,1850,149],{"class":76},[38,1852,152],{"class":101},[38,1854,1855],{"class":44}," \u002F\u002F 开始时间（秒）\n",[38,1857,1859,1862,1864,1866],{"class":40,"line":1858},157,[38,1860,1861],{"class":94},"    end",[38,1863,149],{"class":76},[38,1865,152],{"class":101},[38,1867,1868],{"class":44}," \u002F\u002F 结束时间（秒）\n",[38,1870,1872,1875,1877],{"class":40,"line":1871},158,[38,1873,1874],{"class":94},"    controls",[38,1876,149],{"class":76},[38,1878,1832],{"class":101},[38,1880,1882,1885,1887,1889],{"class":40,"line":1881},159,[38,1883,1884],{"class":94},"    rel",[38,1886,149],{"class":76},[38,1888,1719],{"class":101},[38,1890,1891],{"class":44}," \u002F\u002F 是否显示相关视频\n",[38,1893,1895],{"class":40,"line":1894},160,[38,1896,844],{"class":87},[38,1898,1900,1902,1904,1906],{"class":40,"line":1899},161,[38,1901,288],{"class":87},[38,1903,98],{"class":76},[38,1905,102],{"class":101},[38,1907,88],{"class":87},[38,1909,1911,1913,1916,1918,1921,1924],{"class":40,"line":1910},162,[38,1912,902],{"class":76},[38,1914,1915],{"class":101}," base",[38,1917,362],{"class":76},[38,1919,1920],{"class":119}," `https:\u002F\u002Fwww.youtube.com\u002Fembed\u002F${",[38,1922,1923],{"class":87},"videoId",[38,1925,1926],{"class":119},"}`\n",[38,1928,1930,1932,1935,1937,1939,1942],{"class":40,"line":1929},163,[38,1931,902],{"class":76},[38,1933,1934],{"class":101}," params",[38,1936,362],{"class":76},[38,1938,365],{"class":76},[38,1940,1941],{"class":83}," URLSearchParams",[38,1943,1944],{"class":87},"()\n",[38,1946,1948],{"class":40,"line":1947},164,[38,1949,52],{"emptyLinePlaceholder":51},[38,1951,1953,1955,1958,1961,1963,1966,1969,1972],{"class":40,"line":1952},165,[38,1954,305],{"class":76},[38,1956,1957],{"class":87}," (options?.autoplay) params.",[38,1959,1960],{"class":83},"set",[38,1962,278],{"class":87},[38,1964,1965],{"class":119},"'autoplay'",[38,1967,1968],{"class":87},", ",[38,1970,1971],{"class":119},"'1'",[38,1973,460],{"class":87},[38,1975,1977,1979,1982,1984,1986,1989,1991,1993],{"class":40,"line":1976},166,[38,1978,305],{"class":76},[38,1980,1981],{"class":87}," (options?.muted) params.",[38,1983,1960],{"class":83},[38,1985,278],{"class":87},[38,1987,1988],{"class":119},"'mute'",[38,1990,1968],{"class":87},[38,1992,1971],{"class":119},[38,1994,460],{"class":87},[38,1996,1998,2000,2003,2005,2007,2010,2012,2015],{"class":40,"line":1997},167,[38,1999,305],{"class":76},[38,2001,2002],{"class":87}," (options?.start) params.",[38,2004,1960],{"class":83},[38,2006,278],{"class":87},[38,2008,2009],{"class":119},"'start'",[38,2011,1968],{"class":87},[38,2013,2014],{"class":83},"String",[38,2016,2017],{"class":87},"(options.start))\n",[38,2019,2021,2023,2026,2028,2030,2033,2035,2037],{"class":40,"line":2020},168,[38,2022,305],{"class":76},[38,2024,2025],{"class":87}," (options?.end) params.",[38,2027,1960],{"class":83},[38,2029,278],{"class":87},[38,2031,2032],{"class":119},"'end'",[38,2034,1968],{"class":87},[38,2036,2014],{"class":83},[38,2038,2039],{"class":87},"(options.end))\n",[38,2041,2043,2045,2048,2051,2054,2057,2059,2061,2064,2066,2069],{"class":40,"line":2042},169,[38,2044,305],{"class":76},[38,2046,2047],{"class":87}," (options?.controls ",[38,2049,2050],{"class":76},"===",[38,2052,2053],{"class":101}," false",[38,2055,2056],{"class":87},") params.",[38,2058,1960],{"class":83},[38,2060,278],{"class":87},[38,2062,2063],{"class":119},"'controls'",[38,2065,1968],{"class":87},[38,2067,2068],{"class":119},"'0'",[38,2070,460],{"class":87},[38,2072,2074,2076,2079,2081,2083,2085,2087,2089,2092,2094,2096],{"class":40,"line":2073},170,[38,2075,305],{"class":76},[38,2077,2078],{"class":87}," (options?.rel ",[38,2080,2050],{"class":76},[38,2082,2053],{"class":101},[38,2084,2056],{"class":87},[38,2086,1960],{"class":83},[38,2088,278],{"class":87},[38,2090,2091],{"class":119},"'rel'",[38,2093,1968],{"class":87},[38,2095,2068],{"class":119},[38,2097,460],{"class":87},[38,2099,2101],{"class":40,"line":2100},171,[38,2102,52],{"emptyLinePlaceholder":51},[38,2104,2106,2108,2111,2113,2116,2119],{"class":40,"line":2105},172,[38,2107,902],{"class":76},[38,2109,2110],{"class":101}," queryString",[38,2112,362],{"class":76},[38,2114,2115],{"class":87}," params.",[38,2117,2118],{"class":83},"toString",[38,2120,1944],{"class":87},[38,2122,2124,2126,2129,2132,2135,2138,2141,2144,2147,2150],{"class":40,"line":2123},173,[38,2125,1727],{"class":76},[38,2127,2128],{"class":87}," queryString ",[38,2130,2131],{"class":76},"?",[38,2133,2134],{"class":119}," `${",[38,2136,2137],{"class":87},"base",[38,2139,2140],{"class":119},"}?${",[38,2142,2143],{"class":87},"queryString",[38,2145,2146],{"class":119},"}`",[38,2148,2149],{"class":76}," :",[38,2151,2152],{"class":87}," base\n",[38,2154,2156],{"class":40,"line":2155},174,[38,2157,175],{"class":87},[10,2159,2160],{},"第二步：编写vue代码",[29,2162,2166],{"className":2163,"code":2164,"language":2165,"meta":24,"style":24},"language-vue shiki shiki-themes github-light github-dark","\u003Ctemplate>\n  \u003CMdEditor\n    theme=\"dark\"\n    ref=\"mdEditorRef\"\n    v-model=\"content\"\n    preview-theme=\"github\"\n    :toolbars=\"toolbars\"\n  >\n    \u003Ctemplate #defToolbars>\n      \u003Cdiv class=\"md-editor-toolbar-item\" title=\"嵌入YouTube视频\" @click=\"insertYouTubeIframe\">\n        \u003Csvg\n          xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\"\n          width=\"20\"\n          height=\"20\"\n          viewBox=\"0 0 24 24\"\n          fill=\"currentColor\"\n        >\n          \u003Cpath\n            d=\"M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814z\"\n          \u002F>\n          \u003Cpath d=\"M9.545 15.568L15.818 12 9.545 8.432v7.136z\" fill=\"#ffffff\" \u002F>\n        \u003C\u002Fsvg>\n      \u003C\u002Fdiv>\n    \u003C\u002Ftemplate>\n  \u003C\u002FMdEditor>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup lang=\"ts\">\nimport { ref } from 'vue'\nimport { MdEditor } from 'md-editor-v3'\nimport 'md-editor-v3\u002Flib\u002Fstyle.css'\nimport { ElMessageBox } from 'element-plus'\nimport { getYouTubeEmbedUrl, parseYouTubeUrl } from '@\u002Futils\u002Fyoutube.ts'\n\nconst content = ref('')\nconst mdEditorRef = ref\u003Cany>()\n\n\u002F\u002F md编辑器工具栏\nconst toolbars: any[] = [\n  'bold',\n  'underline',\n  'italic',\n  '-',\n  'title',\n  'strikeThrough',\n  'sub',\n  'sup',\n  'quote',\n  'unorderedList',\n  'orderedList',\n  '-',\n  'codeRow',\n  'code',\n  'link',\n  'image',\n  'table',\n  0, \u002F\u002F 这里的名字要和下面插槽内对应（或者直接按插槽顺序渲染）\n  '-',\n  'revoke',\n  'next',\n  'save',\n  '=',\n  'pageFullscreen',\n  'fullscreen',\n  'preview',\n  'htmlPreview',\n  'catalog',\n]\n\nconst insertYouTubeIframe = async () => {\n  const selectedText = mdEditorRef.value?.getSelectedText().trim()\n\n  const { value } = await ElMessageBox.prompt('请填入视频地址：', '嵌入YouTube', {\n    confirmButtonText: '插入',\n    cancelButtonText: '取消',\n    inputValue: selectedText,\n    inputPattern: \u002F^https?:\\\u002F\\\u002F.+\u002F,\n    inputErrorMessage: '请输入有效的地址',\n  })\n\n  const inputValueUrl = value.trim()\n  if (!inputValueUrl) return ElMessageBox.alert('视频地址不能为空')\n\n  const youtube = parseYouTubeUrl(inputValueUrl)\n  if (youtube.type === 'invalid') return ElMessageBox.alert('invalid')\n\n  const videoId = youtube.videoId ? youtube.videoId : ''\n  const embedUrl = getYouTubeEmbedUrl(videoId, { start: youtube.timestamp })\n\n  \u002F\u002F 安全地通过 CodeMirror 实例注入文本，保留历史记录（撤销\u002F重做）\n  mdEditorRef.value?.insert(() => {\n    const iframeHtml = `\u003Cdiv style=\"width: 100%;aspect-ratio: 16 \u002F 9;\">\n  \u003Ciframe\n  src=\"${embedUrl}\"\n  allowfullscreen=\"allowfullscreen\"\n  style=\"border: none;border-radius:8px;width: 100%;height: 100%\"\n  >\u003C\u002Fiframe>\n\u003C\u002Fdiv>`\n\n    return {\n      targetValue: iframeHtml,\n      select: false,\n      deviationStart: 0,\n      deviationEnd: 0,\n    }\n  })\n}\n\u003C\u002Fscript>\n","vue",[35,2167,2168,2180,2188,2198,2208,2218,2228,2238,2243,2258,2292,2300,2310,2320,2329,2339,2349,2354,2362,2372,2377,2403,2413,2422,2431,2441,2450,2454,2474,2488,2500,2507,2519,2531,2535,2555,2574,2578,2583,2603,2610,2617,2624,2631,2638,2645,2652,2659,2666,2673,2680,2686,2693,2700,2707,2714,2721,2731,2737,2744,2751,2758,2765,2772,2779,2786,2793,2800,2804,2808,2827,2850,2854,2891,2901,2911,2916,2946,2956,2961,2965,2981,3006,3010,3024,3050,3054,3074,3088,3092,3097,3112,3124,3129,3140,3145,3150,3155,3160,3164,3170,3175,3185,3194,3203,3207,3211,3215],{"__ignoreMap":24},[38,2169,2170,2173,2177],{"class":40,"line":41},[38,2171,2172],{"class":87},"\u003C",[38,2174,2176],{"class":2175},"s9eBZ","template",[38,2178,2179],{"class":87},">\n",[38,2181,2182,2185],{"class":40,"line":48},[38,2183,2184],{"class":87},"  \u003C",[38,2186,2187],{"class":2175},"MdEditor\n",[38,2189,2190,2193,2195],{"class":40,"line":55},[38,2191,2192],{"class":83},"    theme",[38,2194,1056],{"class":87},[38,2196,2197],{"class":119},"\"dark\"\n",[38,2199,2200,2203,2205],{"class":40,"line":61},[38,2201,2202],{"class":83},"    ref",[38,2204,1056],{"class":87},[38,2206,2207],{"class":119},"\"mdEditorRef\"\n",[38,2209,2210,2213,2215],{"class":40,"line":67},[38,2211,2212],{"class":83},"    v-model",[38,2214,1056],{"class":87},[38,2216,2217],{"class":119},"\"content\"\n",[38,2219,2220,2223,2225],{"class":40,"line":73},[38,2221,2222],{"class":83},"    preview-theme",[38,2224,1056],{"class":87},[38,2226,2227],{"class":119},"\"github\"\n",[38,2229,2230,2233,2235],{"class":40,"line":91},[38,2231,2232],{"class":83},"    :toolbars",[38,2234,1056],{"class":87},[38,2236,2237],{"class":119},"\"toolbars\"\n",[38,2239,2240],{"class":40,"line":111},[38,2241,2242],{"class":87},"  >\n",[38,2244,2245,2248,2250,2253,2256],{"class":40,"line":143},[38,2246,2247],{"class":87},"    \u003C",[38,2249,2176],{"class":2175},[38,2251,2252],{"class":87}," #",[38,2254,2255],{"class":83},"defToolbars",[38,2257,2179],{"class":87},[38,2259,2260,2263,2266,2269,2271,2274,2277,2279,2282,2285,2287,2290],{"class":40,"line":158},[38,2261,2262],{"class":87},"      \u003C",[38,2264,2265],{"class":2175},"div",[38,2267,2268],{"class":83}," class",[38,2270,1056],{"class":87},[38,2272,2273],{"class":119},"\"md-editor-toolbar-item\"",[38,2275,2276],{"class":83}," title",[38,2278,1056],{"class":87},[38,2280,2281],{"class":119},"\"嵌入YouTube视频\"",[38,2283,2284],{"class":83}," @click",[38,2286,1056],{"class":87},[38,2288,2289],{"class":119},"\"insertYouTubeIframe\"",[38,2291,2179],{"class":87},[38,2293,2294,2297],{"class":40,"line":172},[38,2295,2296],{"class":87},"        \u003C",[38,2298,2299],{"class":2175},"svg\n",[38,2301,2302,2305,2307],{"class":40,"line":178},[38,2303,2304],{"class":83},"          xmlns",[38,2306,1056],{"class":87},[38,2308,2309],{"class":119},"\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\"\n",[38,2311,2312,2315,2317],{"class":40,"line":183},[38,2313,2314],{"class":83},"          width",[38,2316,1056],{"class":87},[38,2318,2319],{"class":119},"\"20\"\n",[38,2321,2322,2325,2327],{"class":40,"line":188},[38,2323,2324],{"class":83},"          height",[38,2326,1056],{"class":87},[38,2328,2319],{"class":119},[38,2330,2331,2334,2336],{"class":40,"line":194},[38,2332,2333],{"class":83},"          viewBox",[38,2335,1056],{"class":87},[38,2337,2338],{"class":119},"\"0 0 24 24\"\n",[38,2340,2341,2344,2346],{"class":40,"line":200},[38,2342,2343],{"class":83},"          fill",[38,2345,1056],{"class":87},[38,2347,2348],{"class":119},"\"currentColor\"\n",[38,2350,2351],{"class":40,"line":206},[38,2352,2353],{"class":87},"        >\n",[38,2355,2356,2359],{"class":40,"line":212},[38,2357,2358],{"class":87},"          \u003C",[38,2360,2361],{"class":2175},"path\n",[38,2363,2364,2367,2369],{"class":40,"line":218},[38,2365,2366],{"class":83},"            d",[38,2368,1056],{"class":87},[38,2370,2371],{"class":119},"\"M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814z\"\n",[38,2373,2374],{"class":40,"line":224},[38,2375,2376],{"class":87},"          \u002F>\n",[38,2378,2379,2381,2384,2387,2389,2392,2395,2397,2400],{"class":40,"line":230},[38,2380,2358],{"class":87},[38,2382,2383],{"class":2175},"path",[38,2385,2386],{"class":83}," d",[38,2388,1056],{"class":87},[38,2390,2391],{"class":119},"\"M9.545 15.568L15.818 12 9.545 8.432v7.136z\"",[38,2393,2394],{"class":83}," fill",[38,2396,1056],{"class":87},[38,2398,2399],{"class":119},"\"#ffffff\"",[38,2401,2402],{"class":87}," \u002F>\n",[38,2404,2405,2408,2411],{"class":40,"line":236},[38,2406,2407],{"class":87},"        \u003C\u002F",[38,2409,2410],{"class":2175},"svg",[38,2412,2179],{"class":87},[38,2414,2415,2418,2420],{"class":40,"line":251},[38,2416,2417],{"class":87},"      \u003C\u002F",[38,2419,2265],{"class":2175},[38,2421,2179],{"class":87},[38,2423,2424,2427,2429],{"class":40,"line":262},[38,2425,2426],{"class":87},"    \u003C\u002F",[38,2428,2176],{"class":2175},[38,2430,2179],{"class":87},[38,2432,2433,2436,2439],{"class":40,"line":267},[38,2434,2435],{"class":87},"  \u003C\u002F",[38,2437,2438],{"class":2175},"MdEditor",[38,2440,2179],{"class":87},[38,2442,2443,2446,2448],{"class":40,"line":302},[38,2444,2445],{"class":87},"\u003C\u002F",[38,2447,2176],{"class":2175},[38,2449,2179],{"class":87},[38,2451,2452],{"class":40,"line":340},[38,2453,52],{"emptyLinePlaceholder":51},[38,2455,2456,2458,2461,2464,2467,2469,2472],{"class":40,"line":345},[38,2457,2172],{"class":87},[38,2459,2460],{"class":2175},"script",[38,2462,2463],{"class":83}," setup",[38,2465,2466],{"class":83}," lang",[38,2468,1056],{"class":87},[38,2470,2471],{"class":119},"\"ts\"",[38,2473,2179],{"class":87},[38,2475,2476,2479,2482,2485],{"class":40,"line":353},[38,2477,2478],{"class":76},"import",[38,2480,2481],{"class":87}," { ref } ",[38,2483,2484],{"class":76},"from",[38,2486,2487],{"class":119}," 'vue'\n",[38,2489,2490,2492,2495,2497],{"class":40,"line":374},[38,2491,2478],{"class":76},[38,2493,2494],{"class":87}," { MdEditor } ",[38,2496,2484],{"class":76},[38,2498,2499],{"class":119}," 'md-editor-v3'\n",[38,2501,2502,2504],{"class":40,"line":387},[38,2503,2478],{"class":76},[38,2505,2506],{"class":119}," 'md-editor-v3\u002Flib\u002Fstyle.css'\n",[38,2508,2509,2511,2514,2516],{"class":40,"line":392},[38,2510,2478],{"class":76},[38,2512,2513],{"class":87}," { ElMessageBox } ",[38,2515,2484],{"class":76},[38,2517,2518],{"class":119}," 'element-plus'\n",[38,2520,2521,2523,2526,2528],{"class":40,"line":398},[38,2522,2478],{"class":76},[38,2524,2525],{"class":87}," { getYouTubeEmbedUrl, parseYouTubeUrl } ",[38,2527,2484],{"class":76},[38,2529,2530],{"class":119}," '@\u002Futils\u002Fyoutube.ts'\n",[38,2532,2533],{"class":40,"line":432},[38,2534,52],{"emptyLinePlaceholder":51},[38,2536,2537,2540,2543,2545,2548,2550,2553],{"class":40,"line":438},[38,2538,2539],{"class":76},"const",[38,2541,2542],{"class":101}," content",[38,2544,362],{"class":76},[38,2546,2547],{"class":83}," ref",[38,2549,278],{"class":87},[38,2551,2552],{"class":119},"''",[38,2554,460],{"class":87},[38,2556,2557,2559,2562,2564,2566,2568,2571],{"class":40,"line":463},[38,2558,2539],{"class":76},[38,2560,2561],{"class":101}," mdEditorRef",[38,2563,362],{"class":76},[38,2565,2547],{"class":83},[38,2567,2172],{"class":87},[38,2569,2570],{"class":101},"any",[38,2572,2573],{"class":87},">()\n",[38,2575,2576],{"class":40,"line":477},[38,2577,52],{"emptyLinePlaceholder":51},[38,2579,2580],{"class":40,"line":482},[38,2581,2582],{"class":44},"\u002F\u002F md编辑器工具栏\n",[38,2584,2585,2587,2590,2592,2595,2598,2600],{"class":40,"line":488},[38,2586,2539],{"class":76},[38,2588,2589],{"class":101}," toolbars",[38,2591,98],{"class":76},[38,2593,2594],{"class":101}," any",[38,2596,2597],{"class":87},"[] ",[38,2599,1056],{"class":76},[38,2601,2602],{"class":87}," [\n",[38,2604,2605,2608],{"class":40,"line":501},[38,2606,2607],{"class":119},"  'bold'",[38,2609,925],{"class":87},[38,2611,2612,2615],{"class":40,"line":553},[38,2613,2614],{"class":119},"  'underline'",[38,2616,925],{"class":87},[38,2618,2619,2622],{"class":40,"line":558},[38,2620,2621],{"class":119},"  'italic'",[38,2623,925],{"class":87},[38,2625,2626,2629],{"class":40,"line":564},[38,2627,2628],{"class":119},"  '-'",[38,2630,925],{"class":87},[38,2632,2633,2636],{"class":40,"line":598},[38,2634,2635],{"class":119},"  'title'",[38,2637,925],{"class":87},[38,2639,2640,2643],{"class":40,"line":618},[38,2641,2642],{"class":119},"  'strikeThrough'",[38,2644,925],{"class":87},[38,2646,2647,2650],{"class":40,"line":624},[38,2648,2649],{"class":119},"  'sub'",[38,2651,925],{"class":87},[38,2653,2654,2657],{"class":40,"line":629},[38,2655,2656],{"class":119},"  'sup'",[38,2658,925],{"class":87},[38,2660,2661,2664],{"class":40,"line":635},[38,2662,2663],{"class":119},"  'quote'",[38,2665,925],{"class":87},[38,2667,2668,2671],{"class":40,"line":663},[38,2669,2670],{"class":119},"  'unorderedList'",[38,2672,925],{"class":87},[38,2674,2675,2678],{"class":40,"line":678},[38,2676,2677],{"class":119},"  'orderedList'",[38,2679,925],{"class":87},[38,2681,2682,2684],{"class":40,"line":683},[38,2683,2628],{"class":119},[38,2685,925],{"class":87},[38,2687,2688,2691],{"class":40,"line":688},[38,2689,2690],{"class":119},"  'codeRow'",[38,2692,925],{"class":87},[38,2694,2695,2698],{"class":40,"line":694},[38,2696,2697],{"class":119},"  'code'",[38,2699,925],{"class":87},[38,2701,2702,2705],{"class":40,"line":721},[38,2703,2704],{"class":119},"  'link'",[38,2706,925],{"class":87},[38,2708,2709,2712],{"class":40,"line":734},[38,2710,2711],{"class":119},"  'image'",[38,2713,925],{"class":87},[38,2715,2716,2719],{"class":40,"line":739},[38,2717,2718],{"class":119},"  'table'",[38,2720,925],{"class":87},[38,2722,2723,2726,2728],{"class":40,"line":744},[38,2724,2725],{"class":101},"  0",[38,2727,1968],{"class":87},[38,2729,2730],{"class":44},"\u002F\u002F 这里的名字要和下面插槽内对应（或者直接按插槽顺序渲染）\n",[38,2732,2733,2735],{"class":40,"line":750},[38,2734,2628],{"class":119},[38,2736,925],{"class":87},[38,2738,2739,2742],{"class":40,"line":778},[38,2740,2741],{"class":119},"  'revoke'",[38,2743,925],{"class":87},[38,2745,2746,2749],{"class":40,"line":793},[38,2747,2748],{"class":119},"  'next'",[38,2750,925],{"class":87},[38,2752,2753,2756],{"class":40,"line":798},[38,2754,2755],{"class":119},"  'save'",[38,2757,925],{"class":87},[38,2759,2760,2763],{"class":40,"line":804},[38,2761,2762],{"class":119},"  '='",[38,2764,925],{"class":87},[38,2766,2767,2770],{"class":40,"line":809},[38,2768,2769],{"class":119},"  'pageFullscreen'",[38,2771,925],{"class":87},[38,2773,2774,2777],{"class":40,"line":817},[38,2775,2776],{"class":119},"  'fullscreen'",[38,2778,925],{"class":87},[38,2780,2781,2784],{"class":40,"line":828},[38,2782,2783],{"class":119},"  'preview'",[38,2785,925],{"class":87},[38,2787,2788,2791],{"class":40,"line":834},[38,2789,2790],{"class":119},"  'htmlPreview'",[38,2792,925],{"class":87},[38,2794,2795,2798],{"class":40,"line":841},[38,2796,2797],{"class":119},"  'catalog'",[38,2799,925],{"class":87},[38,2801,2802],{"class":40,"line":847},[38,2803,615],{"class":87},[38,2805,2806],{"class":40,"line":852},[38,2807,52],{"emptyLinePlaceholder":51},[38,2809,2810,2812,2815,2817,2820,2823,2825],{"class":40,"line":857},[38,2811,2539],{"class":76},[38,2813,2814],{"class":83}," insertYouTubeIframe",[38,2816,362],{"class":76},[38,2818,2819],{"class":76}," async",[38,2821,2822],{"class":87}," () ",[38,2824,536],{"class":76},[38,2826,88],{"class":87},[38,2828,2829,2831,2834,2836,2839,2842,2845,2848],{"class":40,"line":862},[38,2830,902],{"class":76},[38,2832,2833],{"class":101}," selectedText",[38,2835,362],{"class":76},[38,2837,2838],{"class":87}," mdEditorRef.value?.",[38,2840,2841],{"class":83},"getSelectedText",[38,2843,2844],{"class":87},"().",[38,2846,2847],{"class":83},"trim",[38,2849,1944],{"class":87},[38,2851,2852],{"class":40,"line":868},[38,2853,52],{"emptyLinePlaceholder":51},[38,2855,2856,2858,2861,2864,2867,2869,2872,2875,2878,2880,2883,2885,2888],{"class":40,"line":873},[38,2857,902],{"class":76},[38,2859,2860],{"class":87}," { ",[38,2862,2863],{"class":101},"value",[38,2865,2866],{"class":87}," } ",[38,2868,1056],{"class":76},[38,2870,2871],{"class":76}," await",[38,2873,2874],{"class":87}," ElMessageBox.",[38,2876,2877],{"class":83},"prompt",[38,2879,278],{"class":87},[38,2881,2882],{"class":119},"'请填入视频地址：'",[38,2884,1968],{"class":87},[38,2886,2887],{"class":119},"'嵌入YouTube'",[38,2889,2890],{"class":87},", {\n",[38,2892,2893,2896,2899],{"class":40,"line":899},[38,2894,2895],{"class":87},"    confirmButtonText: ",[38,2897,2898],{"class":119},"'插入'",[38,2900,925],{"class":87},[38,2902,2903,2906,2909],{"class":40,"line":916},[38,2904,2905],{"class":87},"    cancelButtonText: ",[38,2907,2908],{"class":119},"'取消'",[38,2910,925],{"class":87},[38,2912,2913],{"class":40,"line":928},[38,2914,2915],{"class":87},"    inputValue: selectedText,\n",[38,2917,2918,2921,2924,2926,2929,2931,2933,2937,2940,2942,2944],{"class":40,"line":939},[38,2919,2920],{"class":87},"    inputPattern:",[38,2922,2923],{"class":119}," \u002F",[38,2925,1160],{"class":76},[38,2927,2928],{"class":1163},"https",[38,2930,2131],{"class":76},[38,2932,98],{"class":1163},[38,2934,2936],{"class":2935},"snhLl","\\\u002F\\\u002F",[38,2938,2939],{"class":101},".",[38,2941,1169],{"class":76},[38,2943,1157],{"class":119},[38,2945,925],{"class":87},[38,2947,2948,2951,2954],{"class":40,"line":944},[38,2949,2950],{"class":87},"    inputErrorMessage: ",[38,2952,2953],{"class":119},"'请输入有效的地址'",[38,2955,925],{"class":87},[38,2957,2958],{"class":40,"line":949},[38,2959,2960],{"class":87},"  })\n",[38,2962,2963],{"class":40,"line":977},[38,2964,52],{"emptyLinePlaceholder":51},[38,2966,2967,2969,2972,2974,2977,2979],{"class":40,"line":982},[38,2968,902],{"class":76},[38,2970,2971],{"class":101}," inputValueUrl",[38,2973,362],{"class":76},[38,2975,2976],{"class":87}," value.",[38,2978,2847],{"class":83},[38,2980,1944],{"class":87},[38,2982,2983,2985,2987,2989,2992,2994,2996,2999,3001,3004],{"class":40,"line":989},[38,2984,305],{"class":76},[38,2986,308],{"class":87},[38,2988,311],{"class":76},[38,2990,2991],{"class":87},"inputValueUrl) ",[38,2993,335],{"class":76},[38,2995,2874],{"class":87},[38,2997,2998],{"class":83},"alert",[38,3000,278],{"class":87},[38,3002,3003],{"class":119},"'视频地址不能为空'",[38,3005,460],{"class":87},[38,3007,3008],{"class":40,"line":1004},[38,3009,52],{"emptyLinePlaceholder":51},[38,3011,3012,3014,3017,3019,3021],{"class":40,"line":1015},[38,3013,902],{"class":76},[38,3015,3016],{"class":101}," youtube",[38,3018,362],{"class":76},[38,3020,880],{"class":83},[38,3022,3023],{"class":87},"(inputValueUrl)\n",[38,3025,3026,3028,3031,3033,3036,3038,3040,3042,3044,3046,3048],{"class":40,"line":1035},[38,3027,305],{"class":76},[38,3029,3030],{"class":87}," (youtube.type ",[38,3032,2050],{"class":76},[38,3034,3035],{"class":119}," 'invalid'",[38,3037,332],{"class":87},[38,3039,335],{"class":76},[38,3041,2874],{"class":87},[38,3043,2998],{"class":83},[38,3045,278],{"class":87},[38,3047,934],{"class":119},[38,3049,460],{"class":87},[38,3051,3052],{"class":40,"line":1061},[38,3053,52],{"emptyLinePlaceholder":51},[38,3055,3056,3058,3060,3062,3065,3067,3069,3071],{"class":40,"line":1078},[38,3057,902],{"class":76},[38,3059,444],{"class":101},[38,3061,362],{"class":76},[38,3063,3064],{"class":87}," youtube.videoId ",[38,3066,2131],{"class":76},[38,3068,3064],{"class":87},[38,3070,98],{"class":76},[38,3072,3073],{"class":119}," ''\n",[38,3075,3076,3078,3081,3083,3085],{"class":40,"line":1099},[38,3077,902],{"class":76},[38,3079,3080],{"class":101}," embedUrl",[38,3082,362],{"class":76},[38,3084,1797],{"class":83},[38,3086,3087],{"class":87},"(videoId, { start: youtube.timestamp })\n",[38,3089,3090],{"class":40,"line":1104},[38,3091,52],{"emptyLinePlaceholder":51},[38,3093,3094],{"class":40,"line":1110},[38,3095,3096],{"class":44},"  \u002F\u002F 安全地通过 CodeMirror 实例注入文本，保留历史记录（撤销\u002F重做）\n",[38,3098,3099,3102,3105,3108,3110],{"class":40,"line":1131},[38,3100,3101],{"class":87},"  mdEditorRef.value?.",[38,3103,3104],{"class":83},"insert",[38,3106,3107],{"class":87},"(() ",[38,3109,536],{"class":76},[38,3111,88],{"class":87},[38,3113,3114,3116,3119,3121],{"class":40,"line":1139},[38,3115,356],{"class":76},[38,3117,3118],{"class":101}," iframeHtml",[38,3120,362],{"class":76},[38,3122,3123],{"class":119}," `\u003Cdiv style=\"width: 100%;aspect-ratio: 16 \u002F 9;\">\n",[38,3125,3126],{"class":40,"line":1193},[38,3127,3128],{"class":119},"  \u003Ciframe\n",[38,3130,3131,3134,3137],{"class":40,"line":1201},[38,3132,3133],{"class":119},"  src=\"${",[38,3135,3136],{"class":87},"embedUrl",[38,3138,3139],{"class":119},"}\"\n",[38,3141,3142],{"class":40,"line":1226},[38,3143,3144],{"class":119},"  allowfullscreen=\"allowfullscreen\"\n",[38,3146,3147],{"class":40,"line":1231},[38,3148,3149],{"class":119},"  style=\"border: none;border-radius:8px;width: 100%;height: 100%\"\n",[38,3151,3152],{"class":40,"line":1236},[38,3153,3154],{"class":119},"  >\u003C\u002Fiframe>\n",[38,3156,3157],{"class":40,"line":1241},[38,3158,3159],{"class":119},"\u003C\u002Fdiv>`\n",[38,3161,3162],{"class":40,"line":1247},[38,3163,52],{"emptyLinePlaceholder":51},[38,3165,3166,3168],{"class":40,"line":1274},[38,3167,812],{"class":76},[38,3169,88],{"class":87},[38,3171,3172],{"class":40,"line":1280},[38,3173,3174],{"class":87},"      targetValue: iframeHtml,\n",[38,3176,3177,3180,3183],{"class":40,"line":1300},[38,3178,3179],{"class":87},"      select: ",[38,3181,3182],{"class":101},"false",[38,3184,925],{"class":87},[38,3186,3187,3190,3192],{"class":40,"line":1308},[38,3188,3189],{"class":87},"      deviationStart: ",[38,3191,729],{"class":101},[38,3193,925],{"class":87},[38,3195,3196,3199,3201],{"class":40,"line":1319},[38,3197,3198],{"class":87},"      deviationEnd: ",[38,3200,729],{"class":101},[38,3202,925],{"class":87},[38,3204,3205],{"class":40,"line":1330},[38,3206,801],{"class":87},[38,3208,3209],{"class":40,"line":1338},[38,3210,2960],{"class":87},[38,3212,3213],{"class":40,"line":1343},[38,3214,175],{"class":87},[38,3216,3217,3219,3221],{"class":40,"line":1348},[38,3218,2445],{"class":87},[38,3220,2460],{"class":2175},[38,3222,2179],{"class":87},[10,3224,3225],{},"下面是个例子",[3227,3228,3233],"h4",{"className":3229,"style":3231,"id":3232},[3230],"iframe-title","color: #18ffff;","_2011年nba总决赛-达拉斯对阵迈阿密-第六场比赛最佳镜头","2011年NBA总决赛-达拉斯对阵迈阿密-第六场比赛最佳镜头",[2265,3235,3239],{"className":3236,"style":3238},[3237],"iframe-wrapper","width: 100%;aspect-ratio: 16 \u002F 9;",[3240,3241],"iframe",{"allowFullScreen":51,"className":3242,"src":3244,"youtube-src-id":3245,"style":3246},[3243],"BLOG_video_class","https:\u002F\u002Fwww.youtube.com\u002Fembed\u002F_2u7wLopWyw","_2u7wLopWyw","border: none;border-radius:8px;width: 100%;height: 100%",[2265,3248,3252],{"className":3249,"style":3251},[3250],"iframe-link","padding-bottom: 16px;",[16,3253,3257],{"dataIndex":3254,"href":3255,"target":3256},"2","https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=_2u7wLopWyw","_blank","If you can't watch it, please click here to go to YouTube.",[3259,3260,3261],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sA_wV, html code.shiki .sA_wV{--shiki-default:#032F62;--shiki-dark:#DBEDFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .snhLl, html code.shiki .snhLl{--shiki-default:#22863A;--shiki-default-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold}",{"title":24,"searchDepth":48,"depth":48,"links":3263},[],"2026-07-01 17:31:07","md",{"tags":3267,"image":18},[3268,3269,3270,3271,33,2165],"前端","自定义工具栏","element-plus","md-editor-v3","\u002Fyehangzhou\u002Fposts\u002Fmd-editor-v3-embed-youtube-custom","---\ntitle: 'md-editor-v3（版本：5.8.5）插件自定义工具栏增加嵌入YouTube视频功能'\ndescription: ''\ndate: 2026-07-01 17:31:07\ntags: ['前端', '自定义工具栏', 'element-plus', 'md-editor-v3', 'ts', 'vue']\nimage: 'https:\u002F\u002Fcdn.jsdelivr.net\u002Fgh\u002Ftomdehub\u002Fresources\u002Fbe\u002Ff16d677e3033c8b5d8288f0dbd586d61\u002Fblog_6038391571834635959\u002F2026-07\u002F2026070117_U3-Zv1fvg.png'\n---\n\n先看效果\n\n[![](https:\u002F\u002Fcdn.jsdelivr.net\u002Fgh\u002Ftomdehub\u002Fresources\u002Fbe\u002Ff16d677e3033c8b5d8288f0dbd586d61\u002Fblog_6038391571834635959\u002F2026-07\u002F2026070117_U3-Zv1fvg.png)](https:\u002F\u002Fcdn.jsdelivr.net\u002Fgh\u002Ftomdehub\u002Fresources\u002Fbe\u002Ff16d677e3033c8b5d8288f0dbd586d61\u002Fblog_6038391571834635959\u002F2026-07\u002F2026070117_U3-Zv1fvg.png)\n\n第一步：先引入 utils\u002Fyoutube.ts 文件\n\n```ts\n\u002F\u002F utils\u002Fyoutube.ts\n\n\u002F**\n * YouTube 链接解析结果\n *\u002F\nexport interface YouTubeParseResult {\n  videoId: string | null\n  type: 'watch' | 'youtu.be' | 'embed' | 'short' | 'invalid'\n  timestamp?: number \u002F\u002F 时间戳（秒）\n  playlistId?: string | null\n}\n\n\u002F**\n * 从 YouTube 链接中提取视频 ID\n * 支持格式：\n * - https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=VIDEO_ID\n * - https:\u002F\u002Fyoutu.be\u002FVIDEO_ID\n * - https:\u002F\u002Fwww.youtube.com\u002Fembed\u002FVIDEO_ID\n * - https:\u002F\u002Fwww.youtube.com\u002Fshorts\u002FVIDEO_ID\n * - https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=VIDEO_ID&t=30s\n *\n * @param url YouTube 链接\n * @returns 视频 ID，如果无法解析则返回 null\n *\u002F\nexport function getYouTubeVideoId(url: string): string | null {\n  if (!url || typeof url !== 'string') return null\n\n  try {\n    const parsedUrl = new URL(url)\n    const hostname = parsedUrl.hostname\n\n    \u002F\u002F 1. 标准 watch 链接: ?v=VIDEO_ID\n    if (hostname.includes('youtube.com') || hostname.includes('youtu.be')) {\n      \u002F\u002F 尝试从查询参数获取 v\n      const videoId = parsedUrl.searchParams.get('v')\n      if (videoId) return videoId\n\n      \u002F\u002F 2. youtu.be\u002FVIDEO_ID 或 youtube.com\u002Fembed\u002FVIDEO_ID\n      const pathname = parsedUrl.pathname\n      const pathSegments = pathname.split('\u002F').filter((seg) => seg.length > 0)\n\n      \u002F\u002F \u002Fembed\u002FVIDEO_ID\n      if (pathname.includes('\u002Fembed\u002F') && pathSegments.length >= 2) {\n        return pathSegments[pathSegments.length - 1]\n      }\n\n      \u002F\u002F \u002Fshorts\u002FVIDEO_ID\n      if (pathname.includes('\u002Fshorts\u002F') && pathSegments.length >= 2) {\n        return pathSegments[pathSegments.length - 1]\n      }\n\n      \u002F\u002F youtu.be\u002FVIDEO_ID\n      if (hostname.includes('youtu.be') && pathSegments.length >= 1) {\n        return pathSegments[0]\n      }\n\n      \u002F\u002F \u002Fv\u002FVIDEO_ID (旧格式)\n      if (pathname.includes('\u002Fv\u002F') && pathSegments.length >= 2) {\n        return pathSegments[pathSegments.length - 1]\n      }\n    }\n\n    return null\n  } catch {\n    \u002F\u002F URL 格式无效\n    return null\n  }\n}\n\n\u002F**\n * 从 YouTube 链接中提取完整信息（视频 ID、类型、时间戳等）\n *\u002F\nexport function parseYouTubeUrl(url: string): YouTubeParseResult {\n  const defaultResult: YouTubeParseResult = {\n    videoId: null,\n    type: 'invalid',\n  }\n\n  if (!url || typeof url !== 'string') return defaultResult\n\n  try {\n    const parsedUrl = new URL(url)\n    const hostname = parsedUrl.hostname\n    let videoId: string | null = null\n    let type: YouTubeParseResult['type'] = 'invalid'\n    let timestamp: number | undefined\n    const playlistId = parsedUrl.searchParams.get('list')\n\n    \u002F\u002F 解析时间戳\n    const tParam = parsedUrl.searchParams.get('t')\n    if (tParam) {\n      const match = tParam.match(\u002F^(\\d+)(?:s|m|h)?$\u002F)\n      if (match) {\n        timestamp = parseInt(match[1], 10)\n      }\n    }\n\n    \u002F\u002F 检测链接类型并提取 ID\n    if (hostname.includes('youtube.com') || hostname.includes('youtu.be')) {\n      \u002F\u002F 标准 watch 链接\n      const vParam = parsedUrl.searchParams.get('v')\n      if (vParam) {\n        videoId = vParam\n        type = 'watch'\n        return { videoId, type, timestamp, playlistId }\n      }\n\n      const pathname = parsedUrl.pathname\n      const pathSegments = pathname.split('\u002F').filter((seg) => seg.length > 0)\n\n      \u002F\u002F youtu.be\u002FVIDEO_ID\n      if (hostname.includes('youtu.be') && pathSegments.length >= 1) {\n        videoId = pathSegments[0]\n        type = 'youtu.be'\n        return { videoId, type, timestamp, playlistId }\n      }\n\n      \u002F\u002F \u002Fembed\u002FVIDEO_ID\n      if (pathname.includes('\u002Fembed\u002F') && pathSegments.length >= 2) {\n        videoId = pathSegments[pathSegments.length - 1]\n        type = 'embed'\n        return { videoId, type, timestamp, playlistId }\n      }\n\n      \u002F\u002F \u002Fshorts\u002FVIDEO_ID\n      if (pathname.includes('\u002Fshorts\u002F') && pathSegments.length >= 2) {\n        videoId = pathSegments[pathSegments.length - 1]\n        type = 'short'\n        return { videoId, type, timestamp, playlistId }\n      }\n    }\n\n    return { videoId: null, type: 'invalid' }\n  } catch {\n    return defaultResult\n  }\n}\n\n\u002F**\n * 验证 YouTube 链接是否有效\n *\u002F\nexport function isValidYouTubeUrl(url: string): boolean {\n  return getYouTubeVideoId(url) !== null\n}\n\n\u002F**\n * 生成 YouTube 嵌入链接\n * @param videoId 视频 ID\n * @param options 配置选项\n *\u002F\nexport function getYouTubeEmbedUrl(\n  videoId: string,\n  options?: {\n    autoplay?: boolean\n    muted?: boolean\n    start?: number \u002F\u002F 开始时间（秒）\n    end?: number \u002F\u002F 结束时间（秒）\n    controls?: boolean\n    rel?: boolean \u002F\u002F 是否显示相关视频\n  }\n): string {\n  const base = `https:\u002F\u002Fwww.youtube.com\u002Fembed\u002F${videoId}`\n  const params = new URLSearchParams()\n\n  if (options?.autoplay) params.set('autoplay', '1')\n  if (options?.muted) params.set('mute', '1')\n  if (options?.start) params.set('start', String(options.start))\n  if (options?.end) params.set('end', String(options.end))\n  if (options?.controls === false) params.set('controls', '0')\n  if (options?.rel === false) params.set('rel', '0')\n\n  const queryString = params.toString()\n  return queryString ? `${base}?${queryString}` : base\n}\n```\n\n第二步：编写vue代码\n\n```vue\n\u003Ctemplate>\n  \u003CMdEditor\n    theme=\"dark\"\n    ref=\"mdEditorRef\"\n    v-model=\"content\"\n    preview-theme=\"github\"\n    :toolbars=\"toolbars\"\n  >\n    \u003Ctemplate #defToolbars>\n      \u003Cdiv class=\"md-editor-toolbar-item\" title=\"嵌入YouTube视频\" @click=\"insertYouTubeIframe\">\n        \u003Csvg\n          xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\"\n          width=\"20\"\n          height=\"20\"\n          viewBox=\"0 0 24 24\"\n          fill=\"currentColor\"\n        >\n          \u003Cpath\n            d=\"M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814z\"\n          \u002F>\n          \u003Cpath d=\"M9.545 15.568L15.818 12 9.545 8.432v7.136z\" fill=\"#ffffff\" \u002F>\n        \u003C\u002Fsvg>\n      \u003C\u002Fdiv>\n    \u003C\u002Ftemplate>\n  \u003C\u002FMdEditor>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup lang=\"ts\">\nimport { ref } from 'vue'\nimport { MdEditor } from 'md-editor-v3'\nimport 'md-editor-v3\u002Flib\u002Fstyle.css'\nimport { ElMessageBox } from 'element-plus'\nimport { getYouTubeEmbedUrl, parseYouTubeUrl } from '@\u002Futils\u002Fyoutube.ts'\n\nconst content = ref('')\nconst mdEditorRef = ref\u003Cany>()\n\n\u002F\u002F md编辑器工具栏\nconst toolbars: any[] = [\n  'bold',\n  'underline',\n  'italic',\n  '-',\n  'title',\n  'strikeThrough',\n  'sub',\n  'sup',\n  'quote',\n  'unorderedList',\n  'orderedList',\n  '-',\n  'codeRow',\n  'code',\n  'link',\n  'image',\n  'table',\n  0, \u002F\u002F 这里的名字要和下面插槽内对应（或者直接按插槽顺序渲染）\n  '-',\n  'revoke',\n  'next',\n  'save',\n  '=',\n  'pageFullscreen',\n  'fullscreen',\n  'preview',\n  'htmlPreview',\n  'catalog',\n]\n\nconst insertYouTubeIframe = async () => {\n  const selectedText = mdEditorRef.value?.getSelectedText().trim()\n\n  const { value } = await ElMessageBox.prompt('请填入视频地址：', '嵌入YouTube', {\n    confirmButtonText: '插入',\n    cancelButtonText: '取消',\n    inputValue: selectedText,\n    inputPattern: \u002F^https?:\\\u002F\\\u002F.+\u002F,\n    inputErrorMessage: '请输入有效的地址',\n  })\n\n  const inputValueUrl = value.trim()\n  if (!inputValueUrl) return ElMessageBox.alert('视频地址不能为空')\n\n  const youtube = parseYouTubeUrl(inputValueUrl)\n  if (youtube.type === 'invalid') return ElMessageBox.alert('invalid')\n\n  const videoId = youtube.videoId ? youtube.videoId : ''\n  const embedUrl = getYouTubeEmbedUrl(videoId, { start: youtube.timestamp })\n\n  \u002F\u002F 安全地通过 CodeMirror 实例注入文本，保留历史记录（撤销\u002F重做）\n  mdEditorRef.value?.insert(() => {\n    const iframeHtml = `\u003Cdiv style=\"width: 100%;aspect-ratio: 16 \u002F 9;\">\n  \u003Ciframe\n  src=\"${embedUrl}\"\n  allowfullscreen=\"allowfullscreen\"\n  style=\"border: none;border-radius:8px;width: 100%;height: 100%\"\n  >\u003C\u002Fiframe>\n\u003C\u002Fdiv>`\n\n    return {\n      targetValue: iframeHtml,\n      select: false,\n      deviationStart: 0,\n      deviationEnd: 0,\n    }\n  })\n}\n\u003C\u002Fscript>\n```\n\n下面是个例子\n\n\u003Ch4 class=\"iframe-title\" style=\"color: #18ffff;\">2011年NBA总决赛-达拉斯对阵迈阿密-第六场比赛最佳镜头\u003C\u002Fh4>\u003Cdiv class=\"iframe-wrapper\" style=\"width: 100%;aspect-ratio: 16 \u002F 9;\">\n\u003Ciframe allowfullscreen=\"allowfullscreen\" class=\"BLOG_video_class\" src=\"https:\u002F\u002Fwww.youtube.com\u002Fembed\u002F_2u7wLopWyw\" youtube-src-id=\"_2u7wLopWyw\" style=\"border: none;border-radius:8px;width: 100%;height: 100%\">\u003C\u002Fiframe>\n\u003C\u002Fdiv>\n\n\u003Cdiv class=\"iframe-link\" style=\"padding-bottom: 16px;\">\n\u003Ca data-index=\"2\" href=\"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=_2u7wLopWyw\" target=\"_blank\">If you can&#39;t watch it, please click here to go to YouTube.\u003C\u002Fa>\n\u003C\u002Fdiv>\n",{"title":5,"description":24},"yehangzhou\u002Fposts\u002Fmd-editor-v3-embed-youtube-custom","0_hgZ_yYuMEusUC2_wGiGQDpr6f-En0cpnVeGG4ne44",1784926484163]