mirror of
https://github.com/ueberdosis/tiptap.git
synced 2025-06-07 09:25:29 +08:00
Merge pull request #6234 from purfectliterature/develop
Support start timestamps, fix Shorts & Live embed URLs
This commit is contained in:
parent
70eeda8ad8
commit
8e99fd237d
5
.changeset/clever-hats-count.md
Normal file
5
.changeset/clever-hats-count.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@tiptap/extension-youtube": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Improve YouTube embed URL creation, support start timestamps, fix Shorts & Live embeds
|
@ -34,6 +34,28 @@ export const getYoutubeEmbedUrl = (nocookie?: boolean, isPlaylist?:boolean) => {
|
|||||||
return nocookie ? 'https://www.youtube-nocookie.com/embed/' : 'https://www.youtube.com/embed/'
|
return nocookie ? 'https://www.youtube-nocookie.com/embed/' : 'https://www.youtube.com/embed/'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getYoutubeVideoOrPlaylistId = (
|
||||||
|
url: URL,
|
||||||
|
): { id: string; isPlaylist?: boolean } | null => {
|
||||||
|
if (url.searchParams.has('v')) {
|
||||||
|
return { id: url.searchParams.get('v')! }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
url.hostname === 'youtu.be'
|
||||||
|
|| url.pathname.includes('shorts')
|
||||||
|
|| url.pathname.includes('live')
|
||||||
|
) {
|
||||||
|
return { id: url.pathname.split('/').pop()! }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.searchParams.has('list')) {
|
||||||
|
return { id: url.searchParams.get('list')!, isPlaylist: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
export const getEmbedUrlFromYoutubeUrl = (options: GetEmbedUrlOptions) => {
|
export const getEmbedUrlFromYoutubeUrl = (options: GetEmbedUrlOptions) => {
|
||||||
const {
|
const {
|
||||||
url,
|
url,
|
||||||
@ -66,98 +88,84 @@ export const getEmbedUrlFromYoutubeUrl = (options: GetEmbedUrlOptions) => {
|
|||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
// if is a youtu.be url, get the id after the /
|
const urlObject = new URL(url)
|
||||||
if (url.includes('youtu.be')) {
|
const { id, isPlaylist } = getYoutubeVideoOrPlaylistId(urlObject) ?? {}
|
||||||
const id = url.split('/').pop()
|
|
||||||
|
|
||||||
if (!id) {
|
if (!id) { return null }
|
||||||
return null
|
|
||||||
}
|
const embedUrl = new URL(`${getYoutubeEmbedUrl(nocookie, isPlaylist)}${id}`)
|
||||||
return `${getYoutubeEmbedUrl(nocookie)}${id}`
|
|
||||||
|
if (urlObject.searchParams.has('t')) {
|
||||||
|
embedUrl.searchParams.set('start', urlObject.searchParams.get('t')!.replaceAll('s', ''))
|
||||||
}
|
}
|
||||||
|
|
||||||
const videoIdRegex = /(?:(v|list)=|shorts\/)([-\w]+)/gm
|
|
||||||
const matches = videoIdRegex.exec(url)
|
|
||||||
|
|
||||||
if (!matches || !matches[2]) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
let outputUrl = `${getYoutubeEmbedUrl(nocookie, matches[1] === 'list')}${matches[2]}`
|
|
||||||
|
|
||||||
const params = []
|
|
||||||
|
|
||||||
if (allowFullscreen === false) {
|
if (allowFullscreen === false) {
|
||||||
params.push('fs=0')
|
embedUrl.searchParams.set('fs', '0')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (autoplay) {
|
if (autoplay) {
|
||||||
params.push('autoplay=1')
|
embedUrl.searchParams.set('autoplay', '1')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ccLanguage) {
|
if (ccLanguage) {
|
||||||
params.push(`cc_lang_pref=${ccLanguage}`)
|
embedUrl.searchParams.set('cc_lang_pref', ccLanguage)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ccLoadPolicy) {
|
if (ccLoadPolicy) {
|
||||||
params.push('cc_load_policy=1')
|
embedUrl.searchParams.set('cc_load_policy', '1')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!controls) {
|
if (!controls) {
|
||||||
params.push('controls=0')
|
embedUrl.searchParams.set('controls', '0')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (disableKBcontrols) {
|
if (disableKBcontrols) {
|
||||||
params.push('disablekb=1')
|
embedUrl.searchParams.set('disablekb', '1')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enableIFrameApi) {
|
if (enableIFrameApi) {
|
||||||
params.push('enablejsapi=1')
|
embedUrl.searchParams.set('enablejsapi', '1')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (endTime) {
|
if (endTime) {
|
||||||
params.push(`end=${endTime}`)
|
embedUrl.searchParams.set('end', endTime.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (interfaceLanguage) {
|
if (interfaceLanguage) {
|
||||||
params.push(`hl=${interfaceLanguage}`)
|
embedUrl.searchParams.set('hl', interfaceLanguage)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ivLoadPolicy) {
|
if (ivLoadPolicy) {
|
||||||
params.push(`iv_load_policy=${ivLoadPolicy}`)
|
embedUrl.searchParams.set('iv_load_policy', ivLoadPolicy.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loop) {
|
if (loop) {
|
||||||
params.push('loop=1')
|
embedUrl.searchParams.set('loop', '1')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modestBranding) {
|
if (modestBranding) {
|
||||||
params.push('modestbranding=1')
|
embedUrl.searchParams.set('modestbranding', '1')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (origin) {
|
if (origin) {
|
||||||
params.push(`origin=${origin}`)
|
embedUrl.searchParams.set('origin', origin)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playlist) {
|
if (playlist) {
|
||||||
params.push(`playlist=${playlist}`)
|
embedUrl.searchParams.set('playlist', playlist)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (startAt) {
|
if (startAt) {
|
||||||
params.push(`start=${startAt}`)
|
embedUrl.searchParams.set('start', startAt.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (progressBarColor) {
|
if (progressBarColor) {
|
||||||
params.push(`color=${progressBarColor}`)
|
embedUrl.searchParams.set('color', progressBarColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rel !== undefined) {
|
if (rel !== undefined) {
|
||||||
params.push(`rel=${rel}`)
|
embedUrl.searchParams.set('rel', rel.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.length) {
|
return embedUrl.toString()
|
||||||
outputUrl += `${matches[1] === 'v' ? '?' : '&'}${params.join('&')}`
|
|
||||||
}
|
|
||||||
|
|
||||||
return outputUrl
|
|
||||||
}
|
}
|
||||||
|
@ -85,4 +85,109 @@ describe('extension-youtube', () => {
|
|||||||
editor?.destroy()
|
editor?.destroy()
|
||||||
getEditorEl()?.remove()
|
getEditorEl()?.remove()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('start timestamp', () => {
|
||||||
|
const timestamp = 60
|
||||||
|
|
||||||
|
const urls = [
|
||||||
|
{
|
||||||
|
url: `https://www.youtube.com/watch?v=testvideoid&t=${timestamp}s`,
|
||||||
|
expected: `https://www.youtube.com/embed/testvideoid?start=${timestamp}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: `https://youtu.be/testvideoid?t=${timestamp}`,
|
||||||
|
expected: `https://www.youtube.com/embed/testvideoid?start=${timestamp}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: `https://www.youtube.com/embed/testvideoid?start=${timestamp}`,
|
||||||
|
expected: `https://www.youtube.com/embed/testvideoid?start=${timestamp}`,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
urls.forEach(({ url, expected }) => {
|
||||||
|
it(`parses the start timestamp for url ${url}`, () => {
|
||||||
|
editor = new Editor({
|
||||||
|
element: createEditorEl(),
|
||||||
|
extensions: [
|
||||||
|
Document,
|
||||||
|
Text,
|
||||||
|
Paragraph,
|
||||||
|
Youtube,
|
||||||
|
],
|
||||||
|
content: {
|
||||||
|
type: 'doc',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'youtube',
|
||||||
|
attrs: {
|
||||||
|
src: url,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(editor.getHTML()).to.include(expected)
|
||||||
|
|
||||||
|
editor?.destroy()
|
||||||
|
getEditorEl()?.remove()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('parses live url', () => {
|
||||||
|
editor = new Editor({
|
||||||
|
element: createEditorEl(),
|
||||||
|
extensions: [
|
||||||
|
Document,
|
||||||
|
Text,
|
||||||
|
Paragraph,
|
||||||
|
Youtube,
|
||||||
|
],
|
||||||
|
content: {
|
||||||
|
type: 'doc',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'youtube',
|
||||||
|
attrs: {
|
||||||
|
src: 'https://www.youtube.com/live/testvideoid',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(editor.getHTML()).to.include('https://www.youtube.com/embed/testvideoid')
|
||||||
|
|
||||||
|
editor?.destroy()
|
||||||
|
getEditorEl()?.remove()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('parses shorts url', () => {
|
||||||
|
editor = new Editor({
|
||||||
|
element: createEditorEl(),
|
||||||
|
extensions: [
|
||||||
|
Document,
|
||||||
|
Text,
|
||||||
|
Paragraph,
|
||||||
|
Youtube,
|
||||||
|
],
|
||||||
|
content: {
|
||||||
|
type: 'doc',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'youtube',
|
||||||
|
attrs: {
|
||||||
|
src: 'https://www.youtube.com/shorts/testvideoid',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(editor.getHTML()).to.include('https://www.youtube.com/embed/testvideoid')
|
||||||
|
|
||||||
|
editor?.destroy()
|
||||||
|
getEditorEl()?.remove()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user