import { createAtomBlockMarkdownSpec, mergeAttributes, Node, nodePasteRule } from '@tiptap/core' import { getEmbedUrlFromYoutubeUrl, isValidYoutubeUrl, YOUTUBE_REGEX_GLOBAL } from './utils.js' export interface YoutubeOptions { /** * Controls if the paste handler for youtube videos should be added. * @default true * @example false */ addPasteHandler: boolean /** * Controls if the youtube video should be allowed to go fullscreen. * @default true * @example false */ allowFullscreen: boolean /** * Controls if the youtube video should autoplay. * @default false * @example true */ autoplay: boolean /** * The language of the captions shown in the youtube video. * @default undefined * @example 'en' */ ccLanguage?: string /** * Controls if the captions should be shown in the youtube video. * @default undefined * @example true */ ccLoadPolicy?: boolean /** * Controls if the controls should be shown in the youtube video. * @default true * @example false */ controls: boolean /** * Controls if the keyboard controls should be disabled in the youtube video. * @default false * @example true */ disableKBcontrols: boolean /** * Controls if the iframe api should be enabled in the youtube video. * @default false * @example true */ enableIFrameApi: boolean /** * The end time of the youtube video. * @default 0 * @example 120 */ endTime: number /** * The height of the youtube video. * @default 480 * @example 720 */ height: number /** * The language of the youtube video. * @default undefined * @example 'en' */ interfaceLanguage?: string /** * Controls if the video annotations should be shown in the youtube video. * @default 0 * @example 1 */ ivLoadPolicy: number /** * Controls if the youtube video should loop. * @default false * @example true */ loop: boolean /** * Controls if the youtube video should show a small youtube logo. * @default false * @example true */ modestBranding: boolean /** * The HTML attributes for a youtube video node. * @default {} * @example { class: 'foo' } */ HTMLAttributes: Record /** * Controls if the youtube node should be inline or not. * @default false * @example true */ inline: boolean /** * Controls if the youtube video should be loaded from youtube-nocookie.com. * @default false * @example true */ nocookie: boolean /** * The origin of the youtube video. * @default '' * @example 'https://tiptap.dev' */ origin: string /** * The playlist of the youtube video. * @default '' * @example 'PLQg6GaokU5CwiVmsZ0dZm6VeIg0V5z1tK' */ playlist: string /** * The color of the youtube video progress bar. * @default undefined * @example 'red' */ progressBarColor?: string /** * The width of the youtube video. * @default 640 * @example 1280 */ width: number /** * Controls if the related youtube videos at the end are from the same channel. * @default 1 * @example 0 */ rel: number } /** * The options for setting a youtube video. */ type SetYoutubeVideoOptions = { src: string; width?: number; height?: number; start?: number } declare module '@tiptap/core' { interface Commands { youtube: { /** * Insert a youtube video * @param options The youtube video attributes * @example editor.commands.setYoutubeVideo({ src: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ' }) */ setYoutubeVideo: (options: SetYoutubeVideoOptions) => ReturnType } } } /** * This extension adds support for youtube videos. * @see https://www.tiptap.dev/api/nodes/youtube */ export const Youtube = Node.create({ name: 'youtube', addOptions() { return { addPasteHandler: true, allowFullscreen: true, autoplay: false, ccLanguage: undefined, ccLoadPolicy: undefined, controls: true, disableKBcontrols: false, enableIFrameApi: false, endTime: 0, height: 480, interfaceLanguage: undefined, ivLoadPolicy: 0, loop: false, modestBranding: false, HTMLAttributes: {}, inline: false, nocookie: false, origin: '', playlist: '', progressBarColor: undefined, width: 640, rel: 1, } }, inline() { return this.options.inline }, group() { return this.options.inline ? 'inline' : 'block' }, draggable: true, addAttributes() { return { src: { default: null, }, start: { default: 0, }, width: { default: this.options.width, }, height: { default: this.options.height, }, } }, parseHTML() { return [ { tag: 'div[data-youtube-video] iframe', }, ] }, addCommands() { return { setYoutubeVideo: (options: SetYoutubeVideoOptions) => ({ commands }) => { if (!isValidYoutubeUrl(options.src)) { return false } return commands.insertContent({ type: this.name, attrs: options, }) }, } }, addPasteRules() { if (!this.options.addPasteHandler) { return [] } return [ nodePasteRule({ find: YOUTUBE_REGEX_GLOBAL, type: this.type, getAttributes: match => { return { src: match.input } }, }), ] }, renderHTML({ HTMLAttributes }) { const embedUrl = getEmbedUrlFromYoutubeUrl({ url: HTMLAttributes.src, allowFullscreen: this.options.allowFullscreen, autoplay: this.options.autoplay, ccLanguage: this.options.ccLanguage, ccLoadPolicy: this.options.ccLoadPolicy, controls: this.options.controls, disableKBcontrols: this.options.disableKBcontrols, enableIFrameApi: this.options.enableIFrameApi, endTime: this.options.endTime, interfaceLanguage: this.options.interfaceLanguage, ivLoadPolicy: this.options.ivLoadPolicy, loop: this.options.loop, modestBranding: this.options.modestBranding, nocookie: this.options.nocookie, origin: this.options.origin, playlist: this.options.playlist, progressBarColor: this.options.progressBarColor, startAt: HTMLAttributes.start || 0, rel: this.options.rel, }) HTMLAttributes.src = embedUrl return [ 'div', { 'data-youtube-video': '' }, [ 'iframe', mergeAttributes( this.options.HTMLAttributes, { width: this.options.width, height: this.options.height, allowfullscreen: this.options.allowFullscreen, autoplay: this.options.autoplay, ccLanguage: this.options.ccLanguage, ccLoadPolicy: this.options.ccLoadPolicy, disableKBcontrols: this.options.disableKBcontrols, enableIFrameApi: this.options.enableIFrameApi, endTime: this.options.endTime, interfaceLanguage: this.options.interfaceLanguage, ivLoadPolicy: this.options.ivLoadPolicy, loop: this.options.loop, modestBranding: this.options.modestBranding, origin: this.options.origin, playlist: this.options.playlist, progressBarColor: this.options.progressBarColor, rel: this.options.rel, }, HTMLAttributes, ), ], ] }, ...createAtomBlockMarkdownSpec({ nodeName: 'youtube', allowedAttributes: ['src', 'width', 'height', 'start'], }), })