snapshot of https://github.com/ueberdosis/tiptap.git (main, 2026-09-15; blobs > 131072B omitted)
Some checks failed
Verify / Install node dependencies (push) Failing after 2s
Verify / Build packages (push) Has been skipped
Verify / Check linting & formatting (push) Has been skipped
Verify / Run unit tests (push) Has been skipped
Verify / Run e2e tests (shard 1/4) (push) Has been skipped
Verify / Run e2e tests (shard 2/4) (push) Has been skipped
Verify / Run e2e tests (shard 3/4) (push) Has been skipped
Verify / Run e2e tests (shard 4/4) (push) Has been skipped
Publish / Resolve publish config (push) Failing after 2s
Publish / Send notification to Slack (push) Has been skipped
Publish / Send failure notification to Slack (push) Failing after 8s
Verify / Merge e2e reports (push) Has been skipped
Publish / Build (push) Has been skipped
Publish / Bump Versions OR Release to Registry (push) Has been skipped
Some checks failed
Verify / Install node dependencies (push) Failing after 2s
Verify / Build packages (push) Has been skipped
Verify / Check linting & formatting (push) Has been skipped
Verify / Run unit tests (push) Has been skipped
Verify / Run e2e tests (shard 1/4) (push) Has been skipped
Verify / Run e2e tests (shard 2/4) (push) Has been skipped
Verify / Run e2e tests (shard 3/4) (push) Has been skipped
Verify / Run e2e tests (shard 4/4) (push) Has been skipped
Publish / Resolve publish config (push) Failing after 2s
Publish / Send notification to Slack (push) Has been skipped
Publish / Send failure notification to Slack (push) Failing after 8s
Verify / Merge e2e reports (push) Has been skipped
Publish / Build (push) Has been skipped
Publish / Bump Versions OR Release to Registry (push) Has been skipped
This commit is contained in:
234
packages/extension-youtube/src/utils.ts
Normal file
234
packages/extension-youtube/src/utils.ts
Normal file
@@ -0,0 +1,234 @@
|
||||
export const YOUTUBE_REGEX =
|
||||
/^((?:https?:)?\/\/)?((?:www|m|music)\.)?((?:youtube\.com|youtu\.be|youtube-nocookie\.com))(\/(?:[\w-]+\?v=|embed\/|v\/)?)([\w-]+)(\S+)?$/
|
||||
export const YOUTUBE_REGEX_GLOBAL =
|
||||
/^((?:https?:)?\/\/)?((?:www|m|music)\.)?((?:youtube\.com|youtu\.be|youtube-nocookie\.com))(\/(?:[\w-]+\?v=|embed\/|v\/)?)([\w-]+)(\S+)?$/g
|
||||
|
||||
/**
|
||||
* Checks whether a url is a youtube url that the extension can embed.
|
||||
*
|
||||
* The `src` attribute of a youtube node is declared with `default: null`, so a node
|
||||
* parsed from an iframe without a `src` attribute legitimately carries no url. Guard
|
||||
* against that here instead of letting the caller dereference it.
|
||||
*/
|
||||
export const isValidYoutubeUrl = (url: string | null | undefined) => {
|
||||
if (!url) {
|
||||
return null
|
||||
}
|
||||
|
||||
return url.match(YOUTUBE_REGEX)
|
||||
}
|
||||
|
||||
export interface GetEmbedUrlOptions {
|
||||
url: string
|
||||
allowFullscreen?: boolean
|
||||
autoplay?: boolean
|
||||
ccLanguage?: string
|
||||
ccLoadPolicy?: boolean
|
||||
controls?: boolean
|
||||
disableKBcontrols?: boolean
|
||||
enableIFrameApi?: boolean
|
||||
endTime?: number
|
||||
interfaceLanguage?: string
|
||||
ivLoadPolicy?: number
|
||||
loop?: boolean
|
||||
modestBranding?: boolean
|
||||
nocookie?: boolean
|
||||
origin?: string
|
||||
playlist?: string
|
||||
progressBarColor?: string
|
||||
startAt?: number
|
||||
rel?: number
|
||||
}
|
||||
|
||||
export interface YoutubeEmbedAttributes {
|
||||
src: string
|
||||
start?: number
|
||||
}
|
||||
|
||||
export const getYoutubeEmbedUrl = (nocookie?: boolean, isPlaylist?: boolean) => {
|
||||
if (isPlaylist) {
|
||||
return 'https://www.youtube-nocookie.com/embed/videoseries?list='
|
||||
}
|
||||
return nocookie ? 'https://www.youtube-nocookie.com/embed/' : 'https://www.youtube.com/embed/'
|
||||
}
|
||||
|
||||
export const getEmbedUrlFromYoutubeUrl = (options: GetEmbedUrlOptions) => {
|
||||
const {
|
||||
url,
|
||||
allowFullscreen,
|
||||
autoplay,
|
||||
ccLanguage,
|
||||
ccLoadPolicy,
|
||||
controls,
|
||||
disableKBcontrols,
|
||||
enableIFrameApi,
|
||||
endTime,
|
||||
interfaceLanguage,
|
||||
ivLoadPolicy,
|
||||
loop,
|
||||
modestBranding,
|
||||
nocookie,
|
||||
origin,
|
||||
playlist,
|
||||
progressBarColor,
|
||||
startAt,
|
||||
rel,
|
||||
} = options
|
||||
|
||||
if (!isValidYoutubeUrl(url)) {
|
||||
return null
|
||||
}
|
||||
|
||||
// if is already an embed url, return it
|
||||
if (url.includes('/embed/')) {
|
||||
return url
|
||||
}
|
||||
|
||||
// if is a youtu.be url, get the id after the /
|
||||
if (url.includes('youtu.be')) {
|
||||
const id = url.split('/').pop()
|
||||
|
||||
if (!id) {
|
||||
return null
|
||||
}
|
||||
return `${getYoutubeEmbedUrl(nocookie)}${id}`
|
||||
}
|
||||
|
||||
const videoIdRegex = /(?:(v|list)=|\/(?:shorts|live)\/)([-\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) {
|
||||
params.push('fs=0')
|
||||
}
|
||||
|
||||
if (autoplay) {
|
||||
params.push('autoplay=1')
|
||||
}
|
||||
|
||||
if (ccLanguage) {
|
||||
params.push(`cc_lang_pref=${ccLanguage}`)
|
||||
}
|
||||
|
||||
if (ccLoadPolicy) {
|
||||
params.push('cc_load_policy=1')
|
||||
}
|
||||
|
||||
if (!controls) {
|
||||
params.push('controls=0')
|
||||
}
|
||||
|
||||
if (disableKBcontrols) {
|
||||
params.push('disablekb=1')
|
||||
}
|
||||
|
||||
if (enableIFrameApi) {
|
||||
params.push('enablejsapi=1')
|
||||
}
|
||||
|
||||
if (endTime) {
|
||||
params.push(`end=${endTime}`)
|
||||
}
|
||||
|
||||
if (interfaceLanguage) {
|
||||
params.push(`hl=${interfaceLanguage}`)
|
||||
}
|
||||
|
||||
if (ivLoadPolicy) {
|
||||
params.push(`iv_load_policy=${ivLoadPolicy}`)
|
||||
}
|
||||
|
||||
if (loop) {
|
||||
params.push('loop=1')
|
||||
}
|
||||
|
||||
if (modestBranding) {
|
||||
params.push('modestbranding=1')
|
||||
}
|
||||
|
||||
if (origin) {
|
||||
params.push(`origin=${origin}`)
|
||||
}
|
||||
|
||||
if (playlist) {
|
||||
params.push(`playlist=${playlist}`)
|
||||
}
|
||||
|
||||
if (startAt) {
|
||||
params.push(`start=${startAt}`)
|
||||
}
|
||||
|
||||
if (progressBarColor) {
|
||||
params.push(`color=${progressBarColor}`)
|
||||
}
|
||||
|
||||
if (rel !== undefined) {
|
||||
params.push(`rel=${rel}`)
|
||||
}
|
||||
|
||||
if (params.length) {
|
||||
outputUrl += `${matches[1] === 'list' ? '&' : '?'}${params.join('&')}`
|
||||
}
|
||||
|
||||
return outputUrl
|
||||
}
|
||||
|
||||
export const getAttributesFromYoutubeEmbedUrl = (url: string): YoutubeEmbedAttributes | null => {
|
||||
let parsedUrl: URL
|
||||
|
||||
try {
|
||||
parsedUrl = new URL(url)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
|
||||
const hostname = parsedUrl.hostname.replace(/^www\./, '')
|
||||
|
||||
if (hostname !== 'youtube.com' && hostname !== 'youtube-nocookie.com') {
|
||||
return null
|
||||
}
|
||||
|
||||
let src: string | null = null
|
||||
|
||||
if (parsedUrl.pathname === '/embed/videoseries') {
|
||||
const list = parsedUrl.searchParams.get('list')
|
||||
|
||||
if (!list) {
|
||||
return null
|
||||
}
|
||||
|
||||
src = `https://www.youtube.com/playlist?list=${list}`
|
||||
} else {
|
||||
const matches = parsedUrl.pathname.match(/^\/embed\/([\w-]+)$/)
|
||||
|
||||
if (!matches?.[1]) {
|
||||
return null
|
||||
}
|
||||
|
||||
src = `https://www.youtube.com/watch?v=${matches[1]}`
|
||||
}
|
||||
|
||||
if (!isValidYoutubeUrl(src)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const attributes: YoutubeEmbedAttributes = { src }
|
||||
const start = parsedUrl.searchParams.get('start')
|
||||
|
||||
if (start) {
|
||||
const parsedStart = Number.parseInt(start, 10)
|
||||
|
||||
if (!Number.isNaN(parsedStart)) {
|
||||
attributes.start = parsedStart
|
||||
}
|
||||
}
|
||||
|
||||
return attributes
|
||||
}
|
||||
Reference in New Issue
Block a user