@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 ADDED
@@ -0,0 +1,14 @@
1
+ # @tiptap/extension-youtube
2
+ [![Version](https://img.shields.io/npm/v/@tiptap/extension-youtube.svg?label=version)](https://www.npmjs.com/package/@tiptap/extension-youtube)
3
+ [![Downloads](https://img.shields.io/npm/dm/@tiptap/extension-youtube.svg)](https://npmcharts.com/compare/tiptap?minimal=true)
4
+ [![License](https://img.shields.io/npm/l/@tiptap/extension-youtube.svg)](https://www.npmjs.com/package/@tiptap/extension-youtube)
5
+ [![Sponsor](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub)](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,3 @@
1
+ import { Youtube } from './youtube';
2
+ export * from './youtube';
3
+ export default Youtube;
@@ -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
@@ -0,0 +1,5 @@
1
+ import { Youtube } from './youtube'
2
+
3
+ export * from './youtube'
4
+
5
+ export default Youtube
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
+ })