@tiptap/extension-youtube 2.0.0-beta.194
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/dist/packages/extension-youtube/src/index.d.ts +3 -0
- package/dist/packages/extension-youtube/src/utils.d.ts +11 -0
- package/dist/packages/extension-youtube/src/youtube.d.ts +27 -0
- package/dist/tiptap-extension-youtube.cjs.js +144 -0
- package/dist/tiptap-extension-youtube.cjs.js.map +1 -0
- package/dist/tiptap-extension-youtube.esm.js +139 -0
- package/dist/tiptap-extension-youtube.esm.js.map +1 -0
- package/dist/tiptap-extension-youtube.umd.js +148 -0
- package/dist/tiptap-extension-youtube.umd.js.map +1 -0
- package/package.json +31 -0
- package/src/index.ts +5 -0
- package/src/utils.ts +66 -0
- package/src/youtube.ts +136 -0
package/README.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# @tiptap/extension-youtube
|
|
2
|
+
[](https://www.npmjs.com/package/@tiptap/extension-youtube)
|
|
3
|
+
[](https://npmcharts.com/compare/tiptap?minimal=true)
|
|
4
|
+
[](https://www.npmjs.com/package/@tiptap/extension-youtube)
|
|
5
|
+
[](https://github.com/sponsors/ueberdosis)
|
|
6
|
+
|
|
7
|
+
## Introduction
|
|
8
|
+
Tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) – a toolkit for building rich text WYSIWYG editors, which is already in use at many well-known companies such as *New York Times*, *The Guardian* or *Atlassian*.
|
|
9
|
+
|
|
10
|
+
## Official Documentation
|
|
11
|
+
Documentation can be found on the [tiptap website](https://tiptap.dev).
|
|
12
|
+
|
|
13
|
+
## License
|
|
14
|
+
Tiptap is open sourced software licensed under the [MIT license](https://github.com/ueberdosis/tiptap/blob/main/LICENSE.md).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const YOUTUBE_REGEX: RegExp;
|
|
2
|
+
export declare const YOUTUBE_REGEX_GLOBAL: RegExp;
|
|
3
|
+
export declare const isValidYoutubeUrl: (url: string) => RegExpMatchArray | null;
|
|
4
|
+
export interface GetEmbedUrlOptions {
|
|
5
|
+
url: string;
|
|
6
|
+
controls?: boolean;
|
|
7
|
+
nocookie?: boolean;
|
|
8
|
+
startAt?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare const getYoutubeEmbedUrl: (nocookie?: boolean) => "https://www.youtube-nocookie.com/embed/" | "https://www.youtube.com/embed/";
|
|
11
|
+
export declare const getEmbedURLFromYoutubeURL: (options: GetEmbedUrlOptions) => string | null;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Node } from '@tiptap/core';
|
|
2
|
+
export interface YoutubeOptions {
|
|
3
|
+
addPasteHandler: boolean;
|
|
4
|
+
allowFullscreen: boolean;
|
|
5
|
+
controls: boolean;
|
|
6
|
+
height: number;
|
|
7
|
+
HTMLAttributes: Record<string, any>;
|
|
8
|
+
inline: boolean;
|
|
9
|
+
nocookie: boolean;
|
|
10
|
+
width: number;
|
|
11
|
+
}
|
|
12
|
+
declare module '@tiptap/core' {
|
|
13
|
+
interface Commands<ReturnType> {
|
|
14
|
+
youtube: {
|
|
15
|
+
/**
|
|
16
|
+
* Insert a youtube video
|
|
17
|
+
*/
|
|
18
|
+
setYoutubeVideo: (options: {
|
|
19
|
+
src: string;
|
|
20
|
+
width?: number;
|
|
21
|
+
height?: number;
|
|
22
|
+
start?: number;
|
|
23
|
+
}) => ReturnType;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export declare const Youtube: Node<YoutubeOptions, any>;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var core = require('@tiptap/core');
|
|
6
|
+
|
|
7
|
+
const YOUTUBE_REGEX = /^(https?:\/\/)?(www\.|music\.)?(youtube\.com|youtu\.be)(.+)?$/;
|
|
8
|
+
const YOUTUBE_REGEX_GLOBAL = /^(https?:\/\/)?(www\.|music\.)?(youtube\.com|youtu\.be)(.+)?$/g;
|
|
9
|
+
const isValidYoutubeUrl = (url) => {
|
|
10
|
+
return url.match(YOUTUBE_REGEX);
|
|
11
|
+
};
|
|
12
|
+
const getYoutubeEmbedUrl = (nocookie) => {
|
|
13
|
+
return nocookie ? 'https://www.youtube-nocookie.com/embed/' : 'https://www.youtube.com/embed/';
|
|
14
|
+
};
|
|
15
|
+
const getEmbedURLFromYoutubeURL = (options) => {
|
|
16
|
+
const { url, controls, nocookie, startAt, } = options;
|
|
17
|
+
// if is already an embed url, return it
|
|
18
|
+
if (url.includes('/embed/')) {
|
|
19
|
+
return url;
|
|
20
|
+
}
|
|
21
|
+
// if is a youtu.be url, get the id after the /
|
|
22
|
+
if (url.includes('youtu.be')) {
|
|
23
|
+
const id = url.split('/').pop();
|
|
24
|
+
if (!id) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return `${getYoutubeEmbedUrl(nocookie)}${id}`;
|
|
28
|
+
}
|
|
29
|
+
const videoIdRegex = /v=([-\w]+)/gm;
|
|
30
|
+
const matches = videoIdRegex.exec(url);
|
|
31
|
+
if (!matches || !matches[1]) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
let outputUrl = `${getYoutubeEmbedUrl(nocookie)}${matches[1]}`;
|
|
35
|
+
const params = [];
|
|
36
|
+
if (!controls) {
|
|
37
|
+
params.push('controls=0');
|
|
38
|
+
}
|
|
39
|
+
if (startAt) {
|
|
40
|
+
params.push(`start=${startAt}`);
|
|
41
|
+
}
|
|
42
|
+
if (params.length) {
|
|
43
|
+
outputUrl += `?${params.join('&')}`;
|
|
44
|
+
}
|
|
45
|
+
return outputUrl;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const Youtube = core.Node.create({
|
|
49
|
+
name: 'youtube',
|
|
50
|
+
addOptions() {
|
|
51
|
+
return {
|
|
52
|
+
addPasteHandler: true,
|
|
53
|
+
allowFullscreen: false,
|
|
54
|
+
controls: true,
|
|
55
|
+
height: 480,
|
|
56
|
+
HTMLAttributes: {},
|
|
57
|
+
inline: false,
|
|
58
|
+
nocookie: false,
|
|
59
|
+
width: 640,
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
inline() {
|
|
63
|
+
return this.options.inline;
|
|
64
|
+
},
|
|
65
|
+
group() {
|
|
66
|
+
return this.options.inline ? 'inline' : 'block';
|
|
67
|
+
},
|
|
68
|
+
draggable: true,
|
|
69
|
+
addAttributes() {
|
|
70
|
+
return {
|
|
71
|
+
src: {
|
|
72
|
+
default: null,
|
|
73
|
+
},
|
|
74
|
+
start: {
|
|
75
|
+
default: 0,
|
|
76
|
+
},
|
|
77
|
+
width: {
|
|
78
|
+
default: this.options.width,
|
|
79
|
+
},
|
|
80
|
+
height: {
|
|
81
|
+
default: this.options.height,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
parseHTML() {
|
|
86
|
+
return [
|
|
87
|
+
{
|
|
88
|
+
tag: 'div[data-youtube-video] iframe',
|
|
89
|
+
},
|
|
90
|
+
];
|
|
91
|
+
},
|
|
92
|
+
addCommands() {
|
|
93
|
+
return {
|
|
94
|
+
setYoutubeVideo: options => ({ commands }) => {
|
|
95
|
+
if (!isValidYoutubeUrl(options.src)) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
return commands.insertContent({
|
|
99
|
+
type: this.name,
|
|
100
|
+
attrs: options,
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
addPasteRules() {
|
|
106
|
+
if (!this.options.addPasteHandler) {
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
return [
|
|
110
|
+
core.nodePasteRule({
|
|
111
|
+
find: YOUTUBE_REGEX_GLOBAL,
|
|
112
|
+
type: this.type,
|
|
113
|
+
getAttributes: match => {
|
|
114
|
+
return { src: match.input };
|
|
115
|
+
},
|
|
116
|
+
}),
|
|
117
|
+
];
|
|
118
|
+
},
|
|
119
|
+
renderHTML({ HTMLAttributes }) {
|
|
120
|
+
const embedUrl = getEmbedURLFromYoutubeURL({
|
|
121
|
+
url: HTMLAttributes.src,
|
|
122
|
+
controls: this.options.controls,
|
|
123
|
+
nocookie: this.options.nocookie,
|
|
124
|
+
startAt: HTMLAttributes.start || 0,
|
|
125
|
+
});
|
|
126
|
+
HTMLAttributes.src = embedUrl;
|
|
127
|
+
return [
|
|
128
|
+
'div',
|
|
129
|
+
{ 'data-youtube-video': '' },
|
|
130
|
+
[
|
|
131
|
+
'iframe',
|
|
132
|
+
core.mergeAttributes(this.options.HTMLAttributes, {
|
|
133
|
+
width: this.options.width,
|
|
134
|
+
height: this.options.height,
|
|
135
|
+
allowfullscreen: this.options.allowFullscreen,
|
|
136
|
+
}, HTMLAttributes),
|
|
137
|
+
],
|
|
138
|
+
];
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
exports.Youtube = Youtube;
|
|
143
|
+
exports["default"] = Youtube;
|
|
144
|
+
//# sourceMappingURL=tiptap-extension-youtube.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tiptap-extension-youtube.cjs.js","sources":["../src/utils.ts","../src/youtube.ts"],"sourcesContent":["export const YOUTUBE_REGEX = /^(https?:\\/\\/)?(www\\.|music\\.)?(youtube\\.com|youtu\\.be)(.+)?$/\nexport const YOUTUBE_REGEX_GLOBAL = /^(https?:\\/\\/)?(www\\.|music\\.)?(youtube\\.com|youtu\\.be)(.+)?$/g\n\nexport const isValidYoutubeUrl = (url: string) => {\n return url.match(YOUTUBE_REGEX)\n}\n\nexport interface GetEmbedUrlOptions {\n url: string;\n controls?: boolean;\n nocookie?: boolean;\n startAt?: number;\n}\n\nexport const getYoutubeEmbedUrl = (nocookie?: boolean) => {\n return nocookie ? 'https://www.youtube-nocookie.com/embed/' : 'https://www.youtube.com/embed/'\n}\n\nexport const getEmbedURLFromYoutubeURL = (options: GetEmbedUrlOptions) => {\n const {\n url,\n controls,\n nocookie,\n startAt,\n } = options\n\n // if is already an embed url, return it\n if (url.includes('/embed/')) {\n return url\n }\n\n // if is a youtu.be url, get the id after the /\n if (url.includes('youtu.be')) {\n const id = url.split('/').pop()\n\n if (!id) {\n return null\n }\n return `${getYoutubeEmbedUrl(nocookie)}${id}`\n }\n\n const videoIdRegex = /v=([-\\w]+)/gm\n const matches = videoIdRegex.exec(url)\n\n if (!matches || !matches[1]) {\n return null\n }\n\n let outputUrl = `${getYoutubeEmbedUrl(nocookie)}${matches[1]}`\n\n const params = []\n\n if (!controls) {\n params.push('controls=0')\n }\n\n if (startAt) {\n params.push(`start=${startAt}`)\n }\n\n if (params.length) {\n outputUrl += `?${params.join('&')}`\n }\n\n return outputUrl\n}\n","import { mergeAttributes, Node, nodePasteRule } from '@tiptap/core'\n\nimport { getEmbedURLFromYoutubeURL, isValidYoutubeUrl, YOUTUBE_REGEX_GLOBAL } from './utils'\n\nexport interface YoutubeOptions {\n addPasteHandler: boolean;\n allowFullscreen: boolean;\n controls: boolean;\n height: number;\n HTMLAttributes: Record<string, any>,\n inline: boolean;\n nocookie: boolean;\n width: number;\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n youtube: {\n /**\n * Insert a youtube video\n */\n setYoutubeVideo: (options: { src: string, width?: number, height?: number, start?: number }) => ReturnType,\n }\n }\n}\n\nexport const Youtube = Node.create<YoutubeOptions>({\n name: 'youtube',\n\n addOptions() {\n return {\n addPasteHandler: true,\n allowFullscreen: false,\n controls: true,\n height: 480,\n HTMLAttributes: {},\n inline: false,\n nocookie: false,\n width: 640,\n }\n },\n\n inline() {\n return this.options.inline\n },\n\n group() {\n return this.options.inline ? 'inline' : 'block'\n },\n\n draggable: true,\n\n addAttributes() {\n return {\n src: {\n default: null,\n },\n start: {\n default: 0,\n },\n width: {\n default: this.options.width,\n },\n height: {\n default: this.options.height,\n },\n }\n },\n\n parseHTML() {\n return [\n {\n tag: 'div[data-youtube-video] iframe',\n },\n ]\n },\n\n addCommands() {\n return {\n setYoutubeVideo: options => ({ commands }) => {\n if (!isValidYoutubeUrl(options.src)) {\n return false\n }\n\n return commands.insertContent({\n type: this.name,\n attrs: options,\n })\n },\n }\n },\n\n addPasteRules() {\n if (!this.options.addPasteHandler) {\n return []\n }\n\n return [\n nodePasteRule({\n find: YOUTUBE_REGEX_GLOBAL,\n type: this.type,\n getAttributes: match => {\n return { src: match.input }\n },\n }),\n ]\n },\n\n renderHTML({ HTMLAttributes }) {\n const embedUrl = getEmbedURLFromYoutubeURL({\n url: HTMLAttributes.src,\n controls: this.options.controls,\n nocookie: this.options.nocookie,\n startAt: HTMLAttributes.start || 0,\n })\n\n HTMLAttributes.src = embedUrl\n\n return [\n 'div',\n { 'data-youtube-video': '' },\n [\n 'iframe',\n mergeAttributes(\n this.options.HTMLAttributes,\n {\n width: this.options.width,\n height: this.options.height,\n allowfullscreen: this.options.allowFullscreen,\n },\n HTMLAttributes,\n ),\n ],\n ]\n },\n})\n"],"names":["Node","nodePasteRule","mergeAttributes"],"mappings":"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"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { Node, nodePasteRule, mergeAttributes } from '@tiptap/core';
|
|
2
|
+
|
|
3
|
+
const YOUTUBE_REGEX = /^(https?:\/\/)?(www\.|music\.)?(youtube\.com|youtu\.be)(.+)?$/;
|
|
4
|
+
const YOUTUBE_REGEX_GLOBAL = /^(https?:\/\/)?(www\.|music\.)?(youtube\.com|youtu\.be)(.+)?$/g;
|
|
5
|
+
const isValidYoutubeUrl = (url) => {
|
|
6
|
+
return url.match(YOUTUBE_REGEX);
|
|
7
|
+
};
|
|
8
|
+
const getYoutubeEmbedUrl = (nocookie) => {
|
|
9
|
+
return nocookie ? 'https://www.youtube-nocookie.com/embed/' : 'https://www.youtube.com/embed/';
|
|
10
|
+
};
|
|
11
|
+
const getEmbedURLFromYoutubeURL = (options) => {
|
|
12
|
+
const { url, controls, nocookie, startAt, } = options;
|
|
13
|
+
// if is already an embed url, return it
|
|
14
|
+
if (url.includes('/embed/')) {
|
|
15
|
+
return url;
|
|
16
|
+
}
|
|
17
|
+
// if is a youtu.be url, get the id after the /
|
|
18
|
+
if (url.includes('youtu.be')) {
|
|
19
|
+
const id = url.split('/').pop();
|
|
20
|
+
if (!id) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
return `${getYoutubeEmbedUrl(nocookie)}${id}`;
|
|
24
|
+
}
|
|
25
|
+
const videoIdRegex = /v=([-\w]+)/gm;
|
|
26
|
+
const matches = videoIdRegex.exec(url);
|
|
27
|
+
if (!matches || !matches[1]) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
let outputUrl = `${getYoutubeEmbedUrl(nocookie)}${matches[1]}`;
|
|
31
|
+
const params = [];
|
|
32
|
+
if (!controls) {
|
|
33
|
+
params.push('controls=0');
|
|
34
|
+
}
|
|
35
|
+
if (startAt) {
|
|
36
|
+
params.push(`start=${startAt}`);
|
|
37
|
+
}
|
|
38
|
+
if (params.length) {
|
|
39
|
+
outputUrl += `?${params.join('&')}`;
|
|
40
|
+
}
|
|
41
|
+
return outputUrl;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const Youtube = Node.create({
|
|
45
|
+
name: 'youtube',
|
|
46
|
+
addOptions() {
|
|
47
|
+
return {
|
|
48
|
+
addPasteHandler: true,
|
|
49
|
+
allowFullscreen: false,
|
|
50
|
+
controls: true,
|
|
51
|
+
height: 480,
|
|
52
|
+
HTMLAttributes: {},
|
|
53
|
+
inline: false,
|
|
54
|
+
nocookie: false,
|
|
55
|
+
width: 640,
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
inline() {
|
|
59
|
+
return this.options.inline;
|
|
60
|
+
},
|
|
61
|
+
group() {
|
|
62
|
+
return this.options.inline ? 'inline' : 'block';
|
|
63
|
+
},
|
|
64
|
+
draggable: true,
|
|
65
|
+
addAttributes() {
|
|
66
|
+
return {
|
|
67
|
+
src: {
|
|
68
|
+
default: null,
|
|
69
|
+
},
|
|
70
|
+
start: {
|
|
71
|
+
default: 0,
|
|
72
|
+
},
|
|
73
|
+
width: {
|
|
74
|
+
default: this.options.width,
|
|
75
|
+
},
|
|
76
|
+
height: {
|
|
77
|
+
default: this.options.height,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
parseHTML() {
|
|
82
|
+
return [
|
|
83
|
+
{
|
|
84
|
+
tag: 'div[data-youtube-video] iframe',
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
},
|
|
88
|
+
addCommands() {
|
|
89
|
+
return {
|
|
90
|
+
setYoutubeVideo: options => ({ commands }) => {
|
|
91
|
+
if (!isValidYoutubeUrl(options.src)) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
return commands.insertContent({
|
|
95
|
+
type: this.name,
|
|
96
|
+
attrs: options,
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
addPasteRules() {
|
|
102
|
+
if (!this.options.addPasteHandler) {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
return [
|
|
106
|
+
nodePasteRule({
|
|
107
|
+
find: YOUTUBE_REGEX_GLOBAL,
|
|
108
|
+
type: this.type,
|
|
109
|
+
getAttributes: match => {
|
|
110
|
+
return { src: match.input };
|
|
111
|
+
},
|
|
112
|
+
}),
|
|
113
|
+
];
|
|
114
|
+
},
|
|
115
|
+
renderHTML({ HTMLAttributes }) {
|
|
116
|
+
const embedUrl = getEmbedURLFromYoutubeURL({
|
|
117
|
+
url: HTMLAttributes.src,
|
|
118
|
+
controls: this.options.controls,
|
|
119
|
+
nocookie: this.options.nocookie,
|
|
120
|
+
startAt: HTMLAttributes.start || 0,
|
|
121
|
+
});
|
|
122
|
+
HTMLAttributes.src = embedUrl;
|
|
123
|
+
return [
|
|
124
|
+
'div',
|
|
125
|
+
{ 'data-youtube-video': '' },
|
|
126
|
+
[
|
|
127
|
+
'iframe',
|
|
128
|
+
mergeAttributes(this.options.HTMLAttributes, {
|
|
129
|
+
width: this.options.width,
|
|
130
|
+
height: this.options.height,
|
|
131
|
+
allowfullscreen: this.options.allowFullscreen,
|
|
132
|
+
}, HTMLAttributes),
|
|
133
|
+
],
|
|
134
|
+
];
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
export { Youtube, Youtube as default };
|
|
139
|
+
//# sourceMappingURL=tiptap-extension-youtube.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tiptap-extension-youtube.esm.js","sources":["../src/utils.ts","../src/youtube.ts"],"sourcesContent":["export const YOUTUBE_REGEX = /^(https?:\\/\\/)?(www\\.|music\\.)?(youtube\\.com|youtu\\.be)(.+)?$/\nexport const YOUTUBE_REGEX_GLOBAL = /^(https?:\\/\\/)?(www\\.|music\\.)?(youtube\\.com|youtu\\.be)(.+)?$/g\n\nexport const isValidYoutubeUrl = (url: string) => {\n return url.match(YOUTUBE_REGEX)\n}\n\nexport interface GetEmbedUrlOptions {\n url: string;\n controls?: boolean;\n nocookie?: boolean;\n startAt?: number;\n}\n\nexport const getYoutubeEmbedUrl = (nocookie?: boolean) => {\n return nocookie ? 'https://www.youtube-nocookie.com/embed/' : 'https://www.youtube.com/embed/'\n}\n\nexport const getEmbedURLFromYoutubeURL = (options: GetEmbedUrlOptions) => {\n const {\n url,\n controls,\n nocookie,\n startAt,\n } = options\n\n // if is already an embed url, return it\n if (url.includes('/embed/')) {\n return url\n }\n\n // if is a youtu.be url, get the id after the /\n if (url.includes('youtu.be')) {\n const id = url.split('/').pop()\n\n if (!id) {\n return null\n }\n return `${getYoutubeEmbedUrl(nocookie)}${id}`\n }\n\n const videoIdRegex = /v=([-\\w]+)/gm\n const matches = videoIdRegex.exec(url)\n\n if (!matches || !matches[1]) {\n return null\n }\n\n let outputUrl = `${getYoutubeEmbedUrl(nocookie)}${matches[1]}`\n\n const params = []\n\n if (!controls) {\n params.push('controls=0')\n }\n\n if (startAt) {\n params.push(`start=${startAt}`)\n }\n\n if (params.length) {\n outputUrl += `?${params.join('&')}`\n }\n\n return outputUrl\n}\n","import { mergeAttributes, Node, nodePasteRule } from '@tiptap/core'\n\nimport { getEmbedURLFromYoutubeURL, isValidYoutubeUrl, YOUTUBE_REGEX_GLOBAL } from './utils'\n\nexport interface YoutubeOptions {\n addPasteHandler: boolean;\n allowFullscreen: boolean;\n controls: boolean;\n height: number;\n HTMLAttributes: Record<string, any>,\n inline: boolean;\n nocookie: boolean;\n width: number;\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n youtube: {\n /**\n * Insert a youtube video\n */\n setYoutubeVideo: (options: { src: string, width?: number, height?: number, start?: number }) => ReturnType,\n }\n }\n}\n\nexport const Youtube = Node.create<YoutubeOptions>({\n name: 'youtube',\n\n addOptions() {\n return {\n addPasteHandler: true,\n allowFullscreen: false,\n controls: true,\n height: 480,\n HTMLAttributes: {},\n inline: false,\n nocookie: false,\n width: 640,\n }\n },\n\n inline() {\n return this.options.inline\n },\n\n group() {\n return this.options.inline ? 'inline' : 'block'\n },\n\n draggable: true,\n\n addAttributes() {\n return {\n src: {\n default: null,\n },\n start: {\n default: 0,\n },\n width: {\n default: this.options.width,\n },\n height: {\n default: this.options.height,\n },\n }\n },\n\n parseHTML() {\n return [\n {\n tag: 'div[data-youtube-video] iframe',\n },\n ]\n },\n\n addCommands() {\n return {\n setYoutubeVideo: options => ({ commands }) => {\n if (!isValidYoutubeUrl(options.src)) {\n return false\n }\n\n return commands.insertContent({\n type: this.name,\n attrs: options,\n })\n },\n }\n },\n\n addPasteRules() {\n if (!this.options.addPasteHandler) {\n return []\n }\n\n return [\n nodePasteRule({\n find: YOUTUBE_REGEX_GLOBAL,\n type: this.type,\n getAttributes: match => {\n return { src: match.input }\n },\n }),\n ]\n },\n\n renderHTML({ HTMLAttributes }) {\n const embedUrl = getEmbedURLFromYoutubeURL({\n url: HTMLAttributes.src,\n controls: this.options.controls,\n nocookie: this.options.nocookie,\n startAt: HTMLAttributes.start || 0,\n })\n\n HTMLAttributes.src = embedUrl\n\n return [\n 'div',\n { 'data-youtube-video': '' },\n [\n 'iframe',\n mergeAttributes(\n this.options.HTMLAttributes,\n {\n width: this.options.width,\n height: this.options.height,\n allowfullscreen: this.options.allowFullscreen,\n },\n HTMLAttributes,\n ),\n ],\n ]\n },\n})\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
(function (global, factory) {
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@tiptap/core')) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['exports', '@tiptap/core'], factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["@tiptap/extension-youtube"] = {}, global.core));
|
|
5
|
+
})(this, (function (exports, core) { 'use strict';
|
|
6
|
+
|
|
7
|
+
const YOUTUBE_REGEX = /^(https?:\/\/)?(www\.|music\.)?(youtube\.com|youtu\.be)(.+)?$/;
|
|
8
|
+
const YOUTUBE_REGEX_GLOBAL = /^(https?:\/\/)?(www\.|music\.)?(youtube\.com|youtu\.be)(.+)?$/g;
|
|
9
|
+
const isValidYoutubeUrl = (url) => {
|
|
10
|
+
return url.match(YOUTUBE_REGEX);
|
|
11
|
+
};
|
|
12
|
+
const getYoutubeEmbedUrl = (nocookie) => {
|
|
13
|
+
return nocookie ? 'https://www.youtube-nocookie.com/embed/' : 'https://www.youtube.com/embed/';
|
|
14
|
+
};
|
|
15
|
+
const getEmbedURLFromYoutubeURL = (options) => {
|
|
16
|
+
const { url, controls, nocookie, startAt, } = options;
|
|
17
|
+
// if is already an embed url, return it
|
|
18
|
+
if (url.includes('/embed/')) {
|
|
19
|
+
return url;
|
|
20
|
+
}
|
|
21
|
+
// if is a youtu.be url, get the id after the /
|
|
22
|
+
if (url.includes('youtu.be')) {
|
|
23
|
+
const id = url.split('/').pop();
|
|
24
|
+
if (!id) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return `${getYoutubeEmbedUrl(nocookie)}${id}`;
|
|
28
|
+
}
|
|
29
|
+
const videoIdRegex = /v=([-\w]+)/gm;
|
|
30
|
+
const matches = videoIdRegex.exec(url);
|
|
31
|
+
if (!matches || !matches[1]) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
let outputUrl = `${getYoutubeEmbedUrl(nocookie)}${matches[1]}`;
|
|
35
|
+
const params = [];
|
|
36
|
+
if (!controls) {
|
|
37
|
+
params.push('controls=0');
|
|
38
|
+
}
|
|
39
|
+
if (startAt) {
|
|
40
|
+
params.push(`start=${startAt}`);
|
|
41
|
+
}
|
|
42
|
+
if (params.length) {
|
|
43
|
+
outputUrl += `?${params.join('&')}`;
|
|
44
|
+
}
|
|
45
|
+
return outputUrl;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const Youtube = core.Node.create({
|
|
49
|
+
name: 'youtube',
|
|
50
|
+
addOptions() {
|
|
51
|
+
return {
|
|
52
|
+
addPasteHandler: true,
|
|
53
|
+
allowFullscreen: false,
|
|
54
|
+
controls: true,
|
|
55
|
+
height: 480,
|
|
56
|
+
HTMLAttributes: {},
|
|
57
|
+
inline: false,
|
|
58
|
+
nocookie: false,
|
|
59
|
+
width: 640,
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
inline() {
|
|
63
|
+
return this.options.inline;
|
|
64
|
+
},
|
|
65
|
+
group() {
|
|
66
|
+
return this.options.inline ? 'inline' : 'block';
|
|
67
|
+
},
|
|
68
|
+
draggable: true,
|
|
69
|
+
addAttributes() {
|
|
70
|
+
return {
|
|
71
|
+
src: {
|
|
72
|
+
default: null,
|
|
73
|
+
},
|
|
74
|
+
start: {
|
|
75
|
+
default: 0,
|
|
76
|
+
},
|
|
77
|
+
width: {
|
|
78
|
+
default: this.options.width,
|
|
79
|
+
},
|
|
80
|
+
height: {
|
|
81
|
+
default: this.options.height,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
parseHTML() {
|
|
86
|
+
return [
|
|
87
|
+
{
|
|
88
|
+
tag: 'div[data-youtube-video] iframe',
|
|
89
|
+
},
|
|
90
|
+
];
|
|
91
|
+
},
|
|
92
|
+
addCommands() {
|
|
93
|
+
return {
|
|
94
|
+
setYoutubeVideo: options => ({ commands }) => {
|
|
95
|
+
if (!isValidYoutubeUrl(options.src)) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
return commands.insertContent({
|
|
99
|
+
type: this.name,
|
|
100
|
+
attrs: options,
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
addPasteRules() {
|
|
106
|
+
if (!this.options.addPasteHandler) {
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
return [
|
|
110
|
+
core.nodePasteRule({
|
|
111
|
+
find: YOUTUBE_REGEX_GLOBAL,
|
|
112
|
+
type: this.type,
|
|
113
|
+
getAttributes: match => {
|
|
114
|
+
return { src: match.input };
|
|
115
|
+
},
|
|
116
|
+
}),
|
|
117
|
+
];
|
|
118
|
+
},
|
|
119
|
+
renderHTML({ HTMLAttributes }) {
|
|
120
|
+
const embedUrl = getEmbedURLFromYoutubeURL({
|
|
121
|
+
url: HTMLAttributes.src,
|
|
122
|
+
controls: this.options.controls,
|
|
123
|
+
nocookie: this.options.nocookie,
|
|
124
|
+
startAt: HTMLAttributes.start || 0,
|
|
125
|
+
});
|
|
126
|
+
HTMLAttributes.src = embedUrl;
|
|
127
|
+
return [
|
|
128
|
+
'div',
|
|
129
|
+
{ 'data-youtube-video': '' },
|
|
130
|
+
[
|
|
131
|
+
'iframe',
|
|
132
|
+
core.mergeAttributes(this.options.HTMLAttributes, {
|
|
133
|
+
width: this.options.width,
|
|
134
|
+
height: this.options.height,
|
|
135
|
+
allowfullscreen: this.options.allowFullscreen,
|
|
136
|
+
}, HTMLAttributes),
|
|
137
|
+
],
|
|
138
|
+
];
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
exports.Youtube = Youtube;
|
|
143
|
+
exports["default"] = Youtube;
|
|
144
|
+
|
|
145
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
146
|
+
|
|
147
|
+
}));
|
|
148
|
+
//# sourceMappingURL=tiptap-extension-youtube.umd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tiptap-extension-youtube.umd.js","sources":["../src/utils.ts","../src/youtube.ts"],"sourcesContent":["export const YOUTUBE_REGEX = /^(https?:\\/\\/)?(www\\.|music\\.)?(youtube\\.com|youtu\\.be)(.+)?$/\nexport const YOUTUBE_REGEX_GLOBAL = /^(https?:\\/\\/)?(www\\.|music\\.)?(youtube\\.com|youtu\\.be)(.+)?$/g\n\nexport const isValidYoutubeUrl = (url: string) => {\n return url.match(YOUTUBE_REGEX)\n}\n\nexport interface GetEmbedUrlOptions {\n url: string;\n controls?: boolean;\n nocookie?: boolean;\n startAt?: number;\n}\n\nexport const getYoutubeEmbedUrl = (nocookie?: boolean) => {\n return nocookie ? 'https://www.youtube-nocookie.com/embed/' : 'https://www.youtube.com/embed/'\n}\n\nexport const getEmbedURLFromYoutubeURL = (options: GetEmbedUrlOptions) => {\n const {\n url,\n controls,\n nocookie,\n startAt,\n } = options\n\n // if is already an embed url, return it\n if (url.includes('/embed/')) {\n return url\n }\n\n // if is a youtu.be url, get the id after the /\n if (url.includes('youtu.be')) {\n const id = url.split('/').pop()\n\n if (!id) {\n return null\n }\n return `${getYoutubeEmbedUrl(nocookie)}${id}`\n }\n\n const videoIdRegex = /v=([-\\w]+)/gm\n const matches = videoIdRegex.exec(url)\n\n if (!matches || !matches[1]) {\n return null\n }\n\n let outputUrl = `${getYoutubeEmbedUrl(nocookie)}${matches[1]}`\n\n const params = []\n\n if (!controls) {\n params.push('controls=0')\n }\n\n if (startAt) {\n params.push(`start=${startAt}`)\n }\n\n if (params.length) {\n outputUrl += `?${params.join('&')}`\n }\n\n return outputUrl\n}\n","import { mergeAttributes, Node, nodePasteRule } from '@tiptap/core'\n\nimport { getEmbedURLFromYoutubeURL, isValidYoutubeUrl, YOUTUBE_REGEX_GLOBAL } from './utils'\n\nexport interface YoutubeOptions {\n addPasteHandler: boolean;\n allowFullscreen: boolean;\n controls: boolean;\n height: number;\n HTMLAttributes: Record<string, any>,\n inline: boolean;\n nocookie: boolean;\n width: number;\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n youtube: {\n /**\n * Insert a youtube video\n */\n setYoutubeVideo: (options: { src: string, width?: number, height?: number, start?: number }) => ReturnType,\n }\n }\n}\n\nexport const Youtube = Node.create<YoutubeOptions>({\n name: 'youtube',\n\n addOptions() {\n return {\n addPasteHandler: true,\n allowFullscreen: false,\n controls: true,\n height: 480,\n HTMLAttributes: {},\n inline: false,\n nocookie: false,\n width: 640,\n }\n },\n\n inline() {\n return this.options.inline\n },\n\n group() {\n return this.options.inline ? 'inline' : 'block'\n },\n\n draggable: true,\n\n addAttributes() {\n return {\n src: {\n default: null,\n },\n start: {\n default: 0,\n },\n width: {\n default: this.options.width,\n },\n height: {\n default: this.options.height,\n },\n }\n },\n\n parseHTML() {\n return [\n {\n tag: 'div[data-youtube-video] iframe',\n },\n ]\n },\n\n addCommands() {\n return {\n setYoutubeVideo: options => ({ commands }) => {\n if (!isValidYoutubeUrl(options.src)) {\n return false\n }\n\n return commands.insertContent({\n type: this.name,\n attrs: options,\n })\n },\n }\n },\n\n addPasteRules() {\n if (!this.options.addPasteHandler) {\n return []\n }\n\n return [\n nodePasteRule({\n find: YOUTUBE_REGEX_GLOBAL,\n type: this.type,\n getAttributes: match => {\n return { src: match.input }\n },\n }),\n ]\n },\n\n renderHTML({ HTMLAttributes }) {\n const embedUrl = getEmbedURLFromYoutubeURL({\n url: HTMLAttributes.src,\n controls: this.options.controls,\n nocookie: this.options.nocookie,\n startAt: HTMLAttributes.start || 0,\n })\n\n HTMLAttributes.src = embedUrl\n\n return [\n 'div',\n { 'data-youtube-video': '' },\n [\n 'iframe',\n mergeAttributes(\n this.options.HTMLAttributes,\n {\n width: this.options.width,\n height: this.options.height,\n allowfullscreen: this.options.allowFullscreen,\n },\n HTMLAttributes,\n ),\n ],\n ]\n },\n})\n"],"names":["Node","nodePasteRule","mergeAttributes"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tiptap/extension-youtube",
|
|
3
|
+
"description": "a youtube embed extension for tiptap",
|
|
4
|
+
"version": "2.0.0-beta.194",
|
|
5
|
+
"homepage": "https://tiptap.dev",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"tiptap",
|
|
8
|
+
"tiptap extension"
|
|
9
|
+
],
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"funding": {
|
|
12
|
+
"type": "github",
|
|
13
|
+
"url": "https://github.com/sponsors/ueberdosis"
|
|
14
|
+
},
|
|
15
|
+
"main": "dist/tiptap-extension-youtube.cjs.js",
|
|
16
|
+
"umd": "dist/tiptap-extension-youtube.umd.js",
|
|
17
|
+
"module": "dist/tiptap-extension-youtube.esm.js",
|
|
18
|
+
"types": "dist/packages/extension-youtube/src/index.d.ts",
|
|
19
|
+
"files": [
|
|
20
|
+
"src",
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"@tiptap/core": "^2.0.0-beta.193"
|
|
25
|
+
},
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/ueberdosis/tiptap",
|
|
29
|
+
"directory": "packages/extension-youtube"
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/index.ts
ADDED
package/src/utils.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export const YOUTUBE_REGEX = /^(https?:\/\/)?(www\.|music\.)?(youtube\.com|youtu\.be)(.+)?$/
|
|
2
|
+
export const YOUTUBE_REGEX_GLOBAL = /^(https?:\/\/)?(www\.|music\.)?(youtube\.com|youtu\.be)(.+)?$/g
|
|
3
|
+
|
|
4
|
+
export const isValidYoutubeUrl = (url: string) => {
|
|
5
|
+
return url.match(YOUTUBE_REGEX)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface GetEmbedUrlOptions {
|
|
9
|
+
url: string;
|
|
10
|
+
controls?: boolean;
|
|
11
|
+
nocookie?: boolean;
|
|
12
|
+
startAt?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const getYoutubeEmbedUrl = (nocookie?: boolean) => {
|
|
16
|
+
return nocookie ? 'https://www.youtube-nocookie.com/embed/' : 'https://www.youtube.com/embed/'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const getEmbedURLFromYoutubeURL = (options: GetEmbedUrlOptions) => {
|
|
20
|
+
const {
|
|
21
|
+
url,
|
|
22
|
+
controls,
|
|
23
|
+
nocookie,
|
|
24
|
+
startAt,
|
|
25
|
+
} = options
|
|
26
|
+
|
|
27
|
+
// if is already an embed url, return it
|
|
28
|
+
if (url.includes('/embed/')) {
|
|
29
|
+
return url
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// if is a youtu.be url, get the id after the /
|
|
33
|
+
if (url.includes('youtu.be')) {
|
|
34
|
+
const id = url.split('/').pop()
|
|
35
|
+
|
|
36
|
+
if (!id) {
|
|
37
|
+
return null
|
|
38
|
+
}
|
|
39
|
+
return `${getYoutubeEmbedUrl(nocookie)}${id}`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const videoIdRegex = /v=([-\w]+)/gm
|
|
43
|
+
const matches = videoIdRegex.exec(url)
|
|
44
|
+
|
|
45
|
+
if (!matches || !matches[1]) {
|
|
46
|
+
return null
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let outputUrl = `${getYoutubeEmbedUrl(nocookie)}${matches[1]}`
|
|
50
|
+
|
|
51
|
+
const params = []
|
|
52
|
+
|
|
53
|
+
if (!controls) {
|
|
54
|
+
params.push('controls=0')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (startAt) {
|
|
58
|
+
params.push(`start=${startAt}`)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (params.length) {
|
|
62
|
+
outputUrl += `?${params.join('&')}`
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return outputUrl
|
|
66
|
+
}
|
package/src/youtube.ts
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { mergeAttributes, Node, nodePasteRule } from '@tiptap/core'
|
|
2
|
+
|
|
3
|
+
import { getEmbedURLFromYoutubeURL, isValidYoutubeUrl, YOUTUBE_REGEX_GLOBAL } from './utils'
|
|
4
|
+
|
|
5
|
+
export interface YoutubeOptions {
|
|
6
|
+
addPasteHandler: boolean;
|
|
7
|
+
allowFullscreen: boolean;
|
|
8
|
+
controls: boolean;
|
|
9
|
+
height: number;
|
|
10
|
+
HTMLAttributes: Record<string, any>,
|
|
11
|
+
inline: boolean;
|
|
12
|
+
nocookie: boolean;
|
|
13
|
+
width: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
declare module '@tiptap/core' {
|
|
17
|
+
interface Commands<ReturnType> {
|
|
18
|
+
youtube: {
|
|
19
|
+
/**
|
|
20
|
+
* Insert a youtube video
|
|
21
|
+
*/
|
|
22
|
+
setYoutubeVideo: (options: { src: string, width?: number, height?: number, start?: number }) => ReturnType,
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const Youtube = Node.create<YoutubeOptions>({
|
|
28
|
+
name: 'youtube',
|
|
29
|
+
|
|
30
|
+
addOptions() {
|
|
31
|
+
return {
|
|
32
|
+
addPasteHandler: true,
|
|
33
|
+
allowFullscreen: false,
|
|
34
|
+
controls: true,
|
|
35
|
+
height: 480,
|
|
36
|
+
HTMLAttributes: {},
|
|
37
|
+
inline: false,
|
|
38
|
+
nocookie: false,
|
|
39
|
+
width: 640,
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
inline() {
|
|
44
|
+
return this.options.inline
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
group() {
|
|
48
|
+
return this.options.inline ? 'inline' : 'block'
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
draggable: true,
|
|
52
|
+
|
|
53
|
+
addAttributes() {
|
|
54
|
+
return {
|
|
55
|
+
src: {
|
|
56
|
+
default: null,
|
|
57
|
+
},
|
|
58
|
+
start: {
|
|
59
|
+
default: 0,
|
|
60
|
+
},
|
|
61
|
+
width: {
|
|
62
|
+
default: this.options.width,
|
|
63
|
+
},
|
|
64
|
+
height: {
|
|
65
|
+
default: this.options.height,
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
parseHTML() {
|
|
71
|
+
return [
|
|
72
|
+
{
|
|
73
|
+
tag: 'div[data-youtube-video] iframe',
|
|
74
|
+
},
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
addCommands() {
|
|
79
|
+
return {
|
|
80
|
+
setYoutubeVideo: options => ({ commands }) => {
|
|
81
|
+
if (!isValidYoutubeUrl(options.src)) {
|
|
82
|
+
return false
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return commands.insertContent({
|
|
86
|
+
type: this.name,
|
|
87
|
+
attrs: options,
|
|
88
|
+
})
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
addPasteRules() {
|
|
94
|
+
if (!this.options.addPasteHandler) {
|
|
95
|
+
return []
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return [
|
|
99
|
+
nodePasteRule({
|
|
100
|
+
find: YOUTUBE_REGEX_GLOBAL,
|
|
101
|
+
type: this.type,
|
|
102
|
+
getAttributes: match => {
|
|
103
|
+
return { src: match.input }
|
|
104
|
+
},
|
|
105
|
+
}),
|
|
106
|
+
]
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
renderHTML({ HTMLAttributes }) {
|
|
110
|
+
const embedUrl = getEmbedURLFromYoutubeURL({
|
|
111
|
+
url: HTMLAttributes.src,
|
|
112
|
+
controls: this.options.controls,
|
|
113
|
+
nocookie: this.options.nocookie,
|
|
114
|
+
startAt: HTMLAttributes.start || 0,
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
HTMLAttributes.src = embedUrl
|
|
118
|
+
|
|
119
|
+
return [
|
|
120
|
+
'div',
|
|
121
|
+
{ 'data-youtube-video': '' },
|
|
122
|
+
[
|
|
123
|
+
'iframe',
|
|
124
|
+
mergeAttributes(
|
|
125
|
+
this.options.HTMLAttributes,
|
|
126
|
+
{
|
|
127
|
+
width: this.options.width,
|
|
128
|
+
height: this.options.height,
|
|
129
|
+
allowfullscreen: this.options.allowFullscreen,
|
|
130
|
+
},
|
|
131
|
+
HTMLAttributes,
|
|
132
|
+
),
|
|
133
|
+
],
|
|
134
|
+
]
|
|
135
|
+
},
|
|
136
|
+
})
|