@tiptap/extension-audio 3.20.2 → 3.20.3

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tiptap/extension-audio",
3
3
  "description": "audio extension for tiptap",
4
- "version": "3.20.2",
4
+ "version": "3.20.3",
5
5
  "homepage": "https://tiptap.dev",
6
6
  "keywords": [
7
7
  "tiptap",
@@ -31,10 +31,10 @@
31
31
  "dist"
32
32
  ],
33
33
  "devDependencies": {
34
- "@tiptap/core": "^3.20.2"
34
+ "@tiptap/core": "^3.20.3"
35
35
  },
36
36
  "peerDependencies": {
37
- "@tiptap/core": "^3.20.2"
37
+ "@tiptap/core": "^3.20.3"
38
38
  },
39
39
  "repository": {
40
40
  "type": "git",
package/dist/index.cjs DELETED
@@ -1,208 +0,0 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
23
- AUDIO_URL_REGEX: () => AUDIO_URL_REGEX,
24
- AUDIO_URL_REGEX_GLOBAL: () => AUDIO_URL_REGEX_GLOBAL,
25
- Audio: () => Audio,
26
- default: () => index_default,
27
- isValidAudioUrl: () => isValidAudioUrl,
28
- sanitizeAudioSrc: () => sanitizeAudioSrc
29
- });
30
- module.exports = __toCommonJS(index_exports);
31
-
32
- // src/audio.ts
33
- var import_core = require("@tiptap/core");
34
-
35
- // src/utils.ts
36
- var AUDIO_URL_REGEX = /https?:\/\/[^\s]+?\.(?:mp3|wav|ogg|oga|flac|m4a|aac|opus|weba|webm)(?:\?[^\s]*)?/i;
37
- var AUDIO_URL_REGEX_GLOBAL = /https?:\/\/[^\s]+?\.(?:mp3|wav|ogg|oga|flac|m4a|aac|opus|weba|webm)(?:\?[^\s]*)?/gi;
38
- var AUDIO_DATA_URL_REGEX = /^data:audio\/[a-zA-Z0-9.+-]+;base64,/i;
39
- var isValidAudioUrl = (url, allowBase64 = false) => {
40
- if (!url) {
41
- return false;
42
- }
43
- if (allowBase64 && AUDIO_DATA_URL_REGEX.test(url)) {
44
- return true;
45
- }
46
- if (url.startsWith("//")) {
47
- return true;
48
- }
49
- if (url.startsWith("/") || url.startsWith("./") || url.startsWith("../")) {
50
- return true;
51
- }
52
- try {
53
- const parsedUrl = new URL(url);
54
- return parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:";
55
- } catch {
56
- return false;
57
- }
58
- };
59
- var sanitizeAudioSrc = (url, allowBase64 = false) => {
60
- if (!url) {
61
- return null;
62
- }
63
- return isValidAudioUrl(url, allowBase64) ? url : null;
64
- };
65
-
66
- // src/audio.ts
67
- var Audio = import_core.Node.create({
68
- name: "audio",
69
- addOptions() {
70
- return {
71
- addPasteHandler: true,
72
- allowBase64: false,
73
- autoplay: false,
74
- controls: true,
75
- loop: false,
76
- muted: false,
77
- preload: "metadata",
78
- controlslist: void 0,
79
- crossorigin: void 0,
80
- disableRemotePlayback: false,
81
- HTMLAttributes: {},
82
- inline: false
83
- };
84
- },
85
- inline() {
86
- return this.options.inline;
87
- },
88
- group() {
89
- return this.options.inline ? "inline" : "block";
90
- },
91
- atom: true,
92
- draggable: true,
93
- addAttributes() {
94
- return {
95
- src: {
96
- default: null
97
- },
98
- controls: {
99
- default: this.options.controls
100
- },
101
- autoplay: {
102
- default: this.options.autoplay
103
- },
104
- loop: {
105
- default: this.options.loop
106
- },
107
- muted: {
108
- default: this.options.muted
109
- },
110
- preload: {
111
- default: this.options.preload
112
- },
113
- controlslist: {
114
- default: this.options.controlslist
115
- },
116
- crossorigin: {
117
- default: this.options.crossorigin
118
- },
119
- disableremoteplayback: {
120
- default: this.options.disableRemotePlayback
121
- }
122
- };
123
- },
124
- parseHTML() {
125
- return [
126
- {
127
- tag: this.options.allowBase64 ? "audio[src]" : 'audio[src]:not([src^="data:"])'
128
- }
129
- ];
130
- },
131
- addCommands() {
132
- return {
133
- setAudio: (options) => ({ commands }) => {
134
- if (!isValidAudioUrl(options.src, this.options.allowBase64)) {
135
- return false;
136
- }
137
- return commands.insertContent({
138
- type: this.name,
139
- attrs: options
140
- });
141
- }
142
- };
143
- },
144
- addPasteRules() {
145
- if (!this.options.addPasteHandler) {
146
- return [];
147
- }
148
- return [
149
- (0, import_core.nodePasteRule)({
150
- find: AUDIO_URL_REGEX_GLOBAL,
151
- type: this.type,
152
- getAttributes: (match) => {
153
- return { src: match[0] };
154
- }
155
- })
156
- ];
157
- },
158
- renderHTML({ HTMLAttributes }) {
159
- const sanitizedSrc = sanitizeAudioSrc(HTMLAttributes.src, this.options.allowBase64);
160
- const mergedAttributes = (0, import_core.mergeAttributes)(
161
- this.options.HTMLAttributes,
162
- {
163
- controls: this.options.controls,
164
- autoplay: this.options.autoplay,
165
- loop: this.options.loop,
166
- muted: this.options.muted,
167
- preload: this.options.preload,
168
- controlslist: this.options.controlslist,
169
- crossorigin: this.options.crossorigin,
170
- disableremoteplayback: this.options.disableRemotePlayback
171
- },
172
- {
173
- ...HTMLAttributes,
174
- src: sanitizedSrc
175
- }
176
- );
177
- const cleanedAttributes = Object.fromEntries(
178
- Object.entries(mergedAttributes).filter(([, value]) => value !== null && value !== void 0 && value !== false)
179
- );
180
- return ["audio", cleanedAttributes];
181
- },
182
- ...(0, import_core.createAtomBlockMarkdownSpec)({
183
- nodeName: "audio",
184
- allowedAttributes: [
185
- "src",
186
- "controls",
187
- "autoplay",
188
- "loop",
189
- "muted",
190
- "preload",
191
- "controlslist",
192
- "crossorigin",
193
- "disableremoteplayback"
194
- ]
195
- })
196
- });
197
-
198
- // src/index.ts
199
- var index_default = Audio;
200
- // Annotate the CommonJS export names for ESM import in node:
201
- 0 && (module.exports = {
202
- AUDIO_URL_REGEX,
203
- AUDIO_URL_REGEX_GLOBAL,
204
- Audio,
205
- isValidAudioUrl,
206
- sanitizeAudioSrc
207
- });
208
- //# sourceMappingURL=index.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/index.ts","../src/audio.ts","../src/utils.ts"],"sourcesContent":["import { Audio } from './audio.js'\n\nexport * from './audio.js'\nexport * from './utils.js'\n\nexport default Audio\n","import { createAtomBlockMarkdownSpec, mergeAttributes, Node, nodePasteRule } from '@tiptap/core'\n\nimport { AUDIO_URL_REGEX_GLOBAL, isValidAudioUrl, sanitizeAudioSrc } from './utils.js'\n\nexport interface AudioOptions {\n /**\n * Controls if the paste handler for audio sources should be added.\n * @default true\n * @example false\n */\n addPasteHandler: boolean\n\n /**\n * Controls if the audio should autoplay.\n * @default false\n * @example true\n */\n autoplay: boolean\n\n /**\n * Controls if the audio element should render the native controls.\n * @default true\n * @example false\n */\n controls: boolean\n\n /**\n * Controls if the audio should loop.\n * @default false\n * @example true\n */\n loop: boolean\n\n /**\n * Controls if the audio should be muted.\n * @default false\n * @example true\n */\n muted: boolean\n\n /**\n * The preload behavior for the audio element.\n * @default 'metadata'\n * @example 'none'\n */\n preload: 'auto' | 'metadata' | 'none' | null\n\n /**\n * Provides the controlslist attribute for the audio element.\n * @default undefined\n * @example 'nodownload'\n */\n controlslist?: string\n\n /**\n * Sets the crossorigin attribute for the audio element.\n * @default undefined\n * @example 'anonymous'\n */\n crossorigin?: '' | 'anonymous' | 'use-credentials'\n\n /**\n * Controls if remote playback should be disabled on the audio element.\n * @default false\n * @example true\n */\n disableRemotePlayback: boolean\n\n /**\n * Allows data: URLs for audio sources.\n * @default false\n * @example true\n */\n allowBase64: boolean\n\n /**\n * The HTML attributes for an audio node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>\n\n /**\n * Controls if the audio node should be inline or not.\n * @default false\n * @example true\n */\n inline: boolean\n}\n\n/**\n * The options for setting an audio element.\n */\ntype SetAudioOptions = {\n src: string\n controls?: boolean\n autoplay?: boolean\n loop?: boolean\n muted?: boolean\n preload?: 'auto' | 'metadata' | 'none' | null\n controlslist?: string\n crossorigin?: '' | 'anonymous' | 'use-credentials'\n disableremoteplayback?: boolean\n HTMLAttributes?: Record<string, any>\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n audio: {\n /**\n * Insert an audio element\n * @param options The audio attributes\n * @example editor.commands.setAudio({ src: 'https://assets.tiptap.dev/sounds/loop.mp3' })\n */\n setAudio: (options: SetAudioOptions) => ReturnType\n }\n }\n}\n\n/**\n * This extension adds support for native audio elements.\n */\nexport const Audio = Node.create<AudioOptions>({\n name: 'audio',\n\n addOptions() {\n return {\n addPasteHandler: true,\n allowBase64: false,\n autoplay: false,\n controls: true,\n loop: false,\n muted: false,\n preload: 'metadata',\n controlslist: undefined,\n crossorigin: undefined,\n disableRemotePlayback: false,\n HTMLAttributes: {},\n inline: false,\n }\n },\n\n inline() {\n return this.options.inline\n },\n\n group() {\n return this.options.inline ? 'inline' : 'block'\n },\n\n atom: true,\n\n draggable: true,\n\n addAttributes() {\n return {\n src: {\n default: null,\n },\n controls: {\n default: this.options.controls,\n },\n autoplay: {\n default: this.options.autoplay,\n },\n loop: {\n default: this.options.loop,\n },\n muted: {\n default: this.options.muted,\n },\n preload: {\n default: this.options.preload,\n },\n controlslist: {\n default: this.options.controlslist,\n },\n crossorigin: {\n default: this.options.crossorigin,\n },\n disableremoteplayback: {\n default: this.options.disableRemotePlayback,\n },\n }\n },\n\n parseHTML() {\n return [\n {\n tag: this.options.allowBase64 ? 'audio[src]' : 'audio[src]:not([src^=\"data:\"])',\n },\n ]\n },\n\n addCommands() {\n return {\n setAudio:\n options =>\n ({ commands }) => {\n if (!isValidAudioUrl(options.src, this.options.allowBase64)) {\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: AUDIO_URL_REGEX_GLOBAL,\n type: this.type,\n getAttributes: match => {\n return { src: match[0] }\n },\n }),\n ]\n },\n\n renderHTML({ HTMLAttributes }) {\n const sanitizedSrc = sanitizeAudioSrc(HTMLAttributes.src, this.options.allowBase64)\n\n const mergedAttributes = mergeAttributes(\n this.options.HTMLAttributes,\n {\n controls: this.options.controls,\n autoplay: this.options.autoplay,\n loop: this.options.loop,\n muted: this.options.muted,\n preload: this.options.preload,\n controlslist: this.options.controlslist,\n crossorigin: this.options.crossorigin,\n disableremoteplayback: this.options.disableRemotePlayback,\n },\n {\n ...HTMLAttributes,\n src: sanitizedSrc,\n },\n )\n\n const cleanedAttributes = Object.fromEntries(\n Object.entries(mergedAttributes).filter(([, value]) => value !== null && value !== undefined && value !== false),\n )\n\n return ['audio', cleanedAttributes]\n },\n\n ...createAtomBlockMarkdownSpec({\n nodeName: 'audio',\n allowedAttributes: [\n 'src',\n 'controls',\n 'autoplay',\n 'loop',\n 'muted',\n 'preload',\n 'controlslist',\n 'crossorigin',\n 'disableremoteplayback',\n ],\n }),\n})\n","/**\n * Regular expression to match URLs pointing to common audio file formats.\n * Matches http/https URLs ending in: mp3, wav, ogg, oga, flac, m4a, aac, opus, weba, webm.\n * Supports optional query strings. Case-insensitive, single match.\n */\nexport const AUDIO_URL_REGEX = /https?:\\/\\/[^\\s]+?\\.(?:mp3|wav|ogg|oga|flac|m4a|aac|opus|weba|webm)(?:\\?[^\\s]*)?/i\n\n/**\n * Global variant of AUDIO_URL_REGEX for matching multiple audio URLs in a string.\n * Use this when you need to find all audio URLs in a block of text.\n */\nexport const AUDIO_URL_REGEX_GLOBAL =\n /https?:\\/\\/[^\\s]+?\\.(?:mp3|wav|ogg|oga|flac|m4a|aac|opus|weba|webm)(?:\\?[^\\s]*)?/gi\n\n/**\n * Regular expression to match base64-encoded audio data URLs.\n */\nconst AUDIO_DATA_URL_REGEX = /^data:audio\\/[a-zA-Z0-9.+-]+;base64,/i\n\n/**\n * Validates whether a URL is safe for use in an audio element.\n * Allows http, https, protocol-relative, and relative paths by default.\n * Data URLs can be enabled via the allowBase64 flag.\n *\n * @param url - The URL string to validate\n * @param allowBase64 - Whether to allow base64 data URLs (default: false)\n * @returns True if the URL is valid and safe, false otherwise\n */\nexport const isValidAudioUrl = (url: string, allowBase64 = false) => {\n if (!url) {\n return false\n }\n\n if (allowBase64 && AUDIO_DATA_URL_REGEX.test(url)) {\n return true\n }\n\n if (url.startsWith('//')) {\n return true\n }\n\n if (url.startsWith('/') || url.startsWith('./') || url.startsWith('../')) {\n return true\n }\n\n try {\n const parsedUrl = new URL(url)\n\n return parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:'\n } catch {\n return false\n }\n}\n\n/**\n * Sanitizes an audio source URL by validating it against allowed protocols.\n * Returns null for invalid or potentially unsafe URLs.\n *\n * @param url - The URL string to sanitize (can be null or undefined)\n * @param allowBase64 - Whether to allow base64 data URLs (default: false)\n * @returns The original URL if valid, or null if invalid/unsafe\n */\nexport const sanitizeAudioSrc = (url: string | null | undefined, allowBase64 = false) => {\n if (!url) {\n return null\n }\n\n return isValidAudioUrl(url, allowBase64) ? url : null\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.cts DELETED
@@ -1,140 +0,0 @@
1
- import { Node } from '@tiptap/core';
2
-
3
- interface AudioOptions {
4
- /**
5
- * Controls if the paste handler for audio sources should be added.
6
- * @default true
7
- * @example false
8
- */
9
- addPasteHandler: boolean;
10
- /**
11
- * Controls if the audio should autoplay.
12
- * @default false
13
- * @example true
14
- */
15
- autoplay: boolean;
16
- /**
17
- * Controls if the audio element should render the native controls.
18
- * @default true
19
- * @example false
20
- */
21
- controls: boolean;
22
- /**
23
- * Controls if the audio should loop.
24
- * @default false
25
- * @example true
26
- */
27
- loop: boolean;
28
- /**
29
- * Controls if the audio should be muted.
30
- * @default false
31
- * @example true
32
- */
33
- muted: boolean;
34
- /**
35
- * The preload behavior for the audio element.
36
- * @default 'metadata'
37
- * @example 'none'
38
- */
39
- preload: 'auto' | 'metadata' | 'none' | null;
40
- /**
41
- * Provides the controlslist attribute for the audio element.
42
- * @default undefined
43
- * @example 'nodownload'
44
- */
45
- controlslist?: string;
46
- /**
47
- * Sets the crossorigin attribute for the audio element.
48
- * @default undefined
49
- * @example 'anonymous'
50
- */
51
- crossorigin?: '' | 'anonymous' | 'use-credentials';
52
- /**
53
- * Controls if remote playback should be disabled on the audio element.
54
- * @default false
55
- * @example true
56
- */
57
- disableRemotePlayback: boolean;
58
- /**
59
- * Allows data: URLs for audio sources.
60
- * @default false
61
- * @example true
62
- */
63
- allowBase64: boolean;
64
- /**
65
- * The HTML attributes for an audio node.
66
- * @default {}
67
- * @example { class: 'foo' }
68
- */
69
- HTMLAttributes: Record<string, any>;
70
- /**
71
- * Controls if the audio node should be inline or not.
72
- * @default false
73
- * @example true
74
- */
75
- inline: boolean;
76
- }
77
- /**
78
- * The options for setting an audio element.
79
- */
80
- type SetAudioOptions = {
81
- src: string;
82
- controls?: boolean;
83
- autoplay?: boolean;
84
- loop?: boolean;
85
- muted?: boolean;
86
- preload?: 'auto' | 'metadata' | 'none' | null;
87
- controlslist?: string;
88
- crossorigin?: '' | 'anonymous' | 'use-credentials';
89
- disableremoteplayback?: boolean;
90
- HTMLAttributes?: Record<string, any>;
91
- };
92
- declare module '@tiptap/core' {
93
- interface Commands<ReturnType> {
94
- audio: {
95
- /**
96
- * Insert an audio element
97
- * @param options The audio attributes
98
- * @example editor.commands.setAudio({ src: 'https://assets.tiptap.dev/sounds/loop.mp3' })
99
- */
100
- setAudio: (options: SetAudioOptions) => ReturnType;
101
- };
102
- }
103
- }
104
- /**
105
- * This extension adds support for native audio elements.
106
- */
107
- declare const Audio: Node<AudioOptions, any>;
108
-
109
- /**
110
- * Regular expression to match URLs pointing to common audio file formats.
111
- * Matches http/https URLs ending in: mp3, wav, ogg, oga, flac, m4a, aac, opus, weba, webm.
112
- * Supports optional query strings. Case-insensitive, single match.
113
- */
114
- declare const AUDIO_URL_REGEX: RegExp;
115
- /**
116
- * Global variant of AUDIO_URL_REGEX for matching multiple audio URLs in a string.
117
- * Use this when you need to find all audio URLs in a block of text.
118
- */
119
- declare const AUDIO_URL_REGEX_GLOBAL: RegExp;
120
- /**
121
- * Validates whether a URL is safe for use in an audio element.
122
- * Allows http, https, protocol-relative, and relative paths by default.
123
- * Data URLs can be enabled via the allowBase64 flag.
124
- *
125
- * @param url - The URL string to validate
126
- * @param allowBase64 - Whether to allow base64 data URLs (default: false)
127
- * @returns True if the URL is valid and safe, false otherwise
128
- */
129
- declare const isValidAudioUrl: (url: string, allowBase64?: boolean) => boolean;
130
- /**
131
- * Sanitizes an audio source URL by validating it against allowed protocols.
132
- * Returns null for invalid or potentially unsafe URLs.
133
- *
134
- * @param url - The URL string to sanitize (can be null or undefined)
135
- * @param allowBase64 - Whether to allow base64 data URLs (default: false)
136
- * @returns The original URL if valid, or null if invalid/unsafe
137
- */
138
- declare const sanitizeAudioSrc: (url: string | null | undefined, allowBase64?: boolean) => string | null;
139
-
140
- export { AUDIO_URL_REGEX, AUDIO_URL_REGEX_GLOBAL, Audio, type AudioOptions, Audio as default, isValidAudioUrl, sanitizeAudioSrc };
package/dist/index.d.ts DELETED
@@ -1,140 +0,0 @@
1
- import { Node } from '@tiptap/core';
2
-
3
- interface AudioOptions {
4
- /**
5
- * Controls if the paste handler for audio sources should be added.
6
- * @default true
7
- * @example false
8
- */
9
- addPasteHandler: boolean;
10
- /**
11
- * Controls if the audio should autoplay.
12
- * @default false
13
- * @example true
14
- */
15
- autoplay: boolean;
16
- /**
17
- * Controls if the audio element should render the native controls.
18
- * @default true
19
- * @example false
20
- */
21
- controls: boolean;
22
- /**
23
- * Controls if the audio should loop.
24
- * @default false
25
- * @example true
26
- */
27
- loop: boolean;
28
- /**
29
- * Controls if the audio should be muted.
30
- * @default false
31
- * @example true
32
- */
33
- muted: boolean;
34
- /**
35
- * The preload behavior for the audio element.
36
- * @default 'metadata'
37
- * @example 'none'
38
- */
39
- preload: 'auto' | 'metadata' | 'none' | null;
40
- /**
41
- * Provides the controlslist attribute for the audio element.
42
- * @default undefined
43
- * @example 'nodownload'
44
- */
45
- controlslist?: string;
46
- /**
47
- * Sets the crossorigin attribute for the audio element.
48
- * @default undefined
49
- * @example 'anonymous'
50
- */
51
- crossorigin?: '' | 'anonymous' | 'use-credentials';
52
- /**
53
- * Controls if remote playback should be disabled on the audio element.
54
- * @default false
55
- * @example true
56
- */
57
- disableRemotePlayback: boolean;
58
- /**
59
- * Allows data: URLs for audio sources.
60
- * @default false
61
- * @example true
62
- */
63
- allowBase64: boolean;
64
- /**
65
- * The HTML attributes for an audio node.
66
- * @default {}
67
- * @example { class: 'foo' }
68
- */
69
- HTMLAttributes: Record<string, any>;
70
- /**
71
- * Controls if the audio node should be inline or not.
72
- * @default false
73
- * @example true
74
- */
75
- inline: boolean;
76
- }
77
- /**
78
- * The options for setting an audio element.
79
- */
80
- type SetAudioOptions = {
81
- src: string;
82
- controls?: boolean;
83
- autoplay?: boolean;
84
- loop?: boolean;
85
- muted?: boolean;
86
- preload?: 'auto' | 'metadata' | 'none' | null;
87
- controlslist?: string;
88
- crossorigin?: '' | 'anonymous' | 'use-credentials';
89
- disableremoteplayback?: boolean;
90
- HTMLAttributes?: Record<string, any>;
91
- };
92
- declare module '@tiptap/core' {
93
- interface Commands<ReturnType> {
94
- audio: {
95
- /**
96
- * Insert an audio element
97
- * @param options The audio attributes
98
- * @example editor.commands.setAudio({ src: 'https://assets.tiptap.dev/sounds/loop.mp3' })
99
- */
100
- setAudio: (options: SetAudioOptions) => ReturnType;
101
- };
102
- }
103
- }
104
- /**
105
- * This extension adds support for native audio elements.
106
- */
107
- declare const Audio: Node<AudioOptions, any>;
108
-
109
- /**
110
- * Regular expression to match URLs pointing to common audio file formats.
111
- * Matches http/https URLs ending in: mp3, wav, ogg, oga, flac, m4a, aac, opus, weba, webm.
112
- * Supports optional query strings. Case-insensitive, single match.
113
- */
114
- declare const AUDIO_URL_REGEX: RegExp;
115
- /**
116
- * Global variant of AUDIO_URL_REGEX for matching multiple audio URLs in a string.
117
- * Use this when you need to find all audio URLs in a block of text.
118
- */
119
- declare const AUDIO_URL_REGEX_GLOBAL: RegExp;
120
- /**
121
- * Validates whether a URL is safe for use in an audio element.
122
- * Allows http, https, protocol-relative, and relative paths by default.
123
- * Data URLs can be enabled via the allowBase64 flag.
124
- *
125
- * @param url - The URL string to validate
126
- * @param allowBase64 - Whether to allow base64 data URLs (default: false)
127
- * @returns True if the URL is valid and safe, false otherwise
128
- */
129
- declare const isValidAudioUrl: (url: string, allowBase64?: boolean) => boolean;
130
- /**
131
- * Sanitizes an audio source URL by validating it against allowed protocols.
132
- * Returns null for invalid or potentially unsafe URLs.
133
- *
134
- * @param url - The URL string to sanitize (can be null or undefined)
135
- * @param allowBase64 - Whether to allow base64 data URLs (default: false)
136
- * @returns The original URL if valid, or null if invalid/unsafe
137
- */
138
- declare const sanitizeAudioSrc: (url: string | null | undefined, allowBase64?: boolean) => string | null;
139
-
140
- export { AUDIO_URL_REGEX, AUDIO_URL_REGEX_GLOBAL, Audio, type AudioOptions, Audio as default, isValidAudioUrl, sanitizeAudioSrc };
package/dist/index.js DELETED
@@ -1,177 +0,0 @@
1
- // src/audio.ts
2
- import { createAtomBlockMarkdownSpec, mergeAttributes, Node, nodePasteRule } from "@tiptap/core";
3
-
4
- // src/utils.ts
5
- var AUDIO_URL_REGEX = /https?:\/\/[^\s]+?\.(?:mp3|wav|ogg|oga|flac|m4a|aac|opus|weba|webm)(?:\?[^\s]*)?/i;
6
- var AUDIO_URL_REGEX_GLOBAL = /https?:\/\/[^\s]+?\.(?:mp3|wav|ogg|oga|flac|m4a|aac|opus|weba|webm)(?:\?[^\s]*)?/gi;
7
- var AUDIO_DATA_URL_REGEX = /^data:audio\/[a-zA-Z0-9.+-]+;base64,/i;
8
- var isValidAudioUrl = (url, allowBase64 = false) => {
9
- if (!url) {
10
- return false;
11
- }
12
- if (allowBase64 && AUDIO_DATA_URL_REGEX.test(url)) {
13
- return true;
14
- }
15
- if (url.startsWith("//")) {
16
- return true;
17
- }
18
- if (url.startsWith("/") || url.startsWith("./") || url.startsWith("../")) {
19
- return true;
20
- }
21
- try {
22
- const parsedUrl = new URL(url);
23
- return parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:";
24
- } catch {
25
- return false;
26
- }
27
- };
28
- var sanitizeAudioSrc = (url, allowBase64 = false) => {
29
- if (!url) {
30
- return null;
31
- }
32
- return isValidAudioUrl(url, allowBase64) ? url : null;
33
- };
34
-
35
- // src/audio.ts
36
- var Audio = Node.create({
37
- name: "audio",
38
- addOptions() {
39
- return {
40
- addPasteHandler: true,
41
- allowBase64: false,
42
- autoplay: false,
43
- controls: true,
44
- loop: false,
45
- muted: false,
46
- preload: "metadata",
47
- controlslist: void 0,
48
- crossorigin: void 0,
49
- disableRemotePlayback: false,
50
- HTMLAttributes: {},
51
- inline: false
52
- };
53
- },
54
- inline() {
55
- return this.options.inline;
56
- },
57
- group() {
58
- return this.options.inline ? "inline" : "block";
59
- },
60
- atom: true,
61
- draggable: true,
62
- addAttributes() {
63
- return {
64
- src: {
65
- default: null
66
- },
67
- controls: {
68
- default: this.options.controls
69
- },
70
- autoplay: {
71
- default: this.options.autoplay
72
- },
73
- loop: {
74
- default: this.options.loop
75
- },
76
- muted: {
77
- default: this.options.muted
78
- },
79
- preload: {
80
- default: this.options.preload
81
- },
82
- controlslist: {
83
- default: this.options.controlslist
84
- },
85
- crossorigin: {
86
- default: this.options.crossorigin
87
- },
88
- disableremoteplayback: {
89
- default: this.options.disableRemotePlayback
90
- }
91
- };
92
- },
93
- parseHTML() {
94
- return [
95
- {
96
- tag: this.options.allowBase64 ? "audio[src]" : 'audio[src]:not([src^="data:"])'
97
- }
98
- ];
99
- },
100
- addCommands() {
101
- return {
102
- setAudio: (options) => ({ commands }) => {
103
- if (!isValidAudioUrl(options.src, this.options.allowBase64)) {
104
- return false;
105
- }
106
- return commands.insertContent({
107
- type: this.name,
108
- attrs: options
109
- });
110
- }
111
- };
112
- },
113
- addPasteRules() {
114
- if (!this.options.addPasteHandler) {
115
- return [];
116
- }
117
- return [
118
- nodePasteRule({
119
- find: AUDIO_URL_REGEX_GLOBAL,
120
- type: this.type,
121
- getAttributes: (match) => {
122
- return { src: match[0] };
123
- }
124
- })
125
- ];
126
- },
127
- renderHTML({ HTMLAttributes }) {
128
- const sanitizedSrc = sanitizeAudioSrc(HTMLAttributes.src, this.options.allowBase64);
129
- const mergedAttributes = mergeAttributes(
130
- this.options.HTMLAttributes,
131
- {
132
- controls: this.options.controls,
133
- autoplay: this.options.autoplay,
134
- loop: this.options.loop,
135
- muted: this.options.muted,
136
- preload: this.options.preload,
137
- controlslist: this.options.controlslist,
138
- crossorigin: this.options.crossorigin,
139
- disableremoteplayback: this.options.disableRemotePlayback
140
- },
141
- {
142
- ...HTMLAttributes,
143
- src: sanitizedSrc
144
- }
145
- );
146
- const cleanedAttributes = Object.fromEntries(
147
- Object.entries(mergedAttributes).filter(([, value]) => value !== null && value !== void 0 && value !== false)
148
- );
149
- return ["audio", cleanedAttributes];
150
- },
151
- ...createAtomBlockMarkdownSpec({
152
- nodeName: "audio",
153
- allowedAttributes: [
154
- "src",
155
- "controls",
156
- "autoplay",
157
- "loop",
158
- "muted",
159
- "preload",
160
- "controlslist",
161
- "crossorigin",
162
- "disableremoteplayback"
163
- ]
164
- })
165
- });
166
-
167
- // src/index.ts
168
- var index_default = Audio;
169
- export {
170
- AUDIO_URL_REGEX,
171
- AUDIO_URL_REGEX_GLOBAL,
172
- Audio,
173
- index_default as default,
174
- isValidAudioUrl,
175
- sanitizeAudioSrc
176
- };
177
- //# sourceMappingURL=index.js.map
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/audio.ts","../src/utils.ts","../src/index.ts"],"sourcesContent":["import { createAtomBlockMarkdownSpec, mergeAttributes, Node, nodePasteRule } from '@tiptap/core'\n\nimport { AUDIO_URL_REGEX_GLOBAL, isValidAudioUrl, sanitizeAudioSrc } from './utils.js'\n\nexport interface AudioOptions {\n /**\n * Controls if the paste handler for audio sources should be added.\n * @default true\n * @example false\n */\n addPasteHandler: boolean\n\n /**\n * Controls if the audio should autoplay.\n * @default false\n * @example true\n */\n autoplay: boolean\n\n /**\n * Controls if the audio element should render the native controls.\n * @default true\n * @example false\n */\n controls: boolean\n\n /**\n * Controls if the audio should loop.\n * @default false\n * @example true\n */\n loop: boolean\n\n /**\n * Controls if the audio should be muted.\n * @default false\n * @example true\n */\n muted: boolean\n\n /**\n * The preload behavior for the audio element.\n * @default 'metadata'\n * @example 'none'\n */\n preload: 'auto' | 'metadata' | 'none' | null\n\n /**\n * Provides the controlslist attribute for the audio element.\n * @default undefined\n * @example 'nodownload'\n */\n controlslist?: string\n\n /**\n * Sets the crossorigin attribute for the audio element.\n * @default undefined\n * @example 'anonymous'\n */\n crossorigin?: '' | 'anonymous' | 'use-credentials'\n\n /**\n * Controls if remote playback should be disabled on the audio element.\n * @default false\n * @example true\n */\n disableRemotePlayback: boolean\n\n /**\n * Allows data: URLs for audio sources.\n * @default false\n * @example true\n */\n allowBase64: boolean\n\n /**\n * The HTML attributes for an audio node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>\n\n /**\n * Controls if the audio node should be inline or not.\n * @default false\n * @example true\n */\n inline: boolean\n}\n\n/**\n * The options for setting an audio element.\n */\ntype SetAudioOptions = {\n src: string\n controls?: boolean\n autoplay?: boolean\n loop?: boolean\n muted?: boolean\n preload?: 'auto' | 'metadata' | 'none' | null\n controlslist?: string\n crossorigin?: '' | 'anonymous' | 'use-credentials'\n disableremoteplayback?: boolean\n HTMLAttributes?: Record<string, any>\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n audio: {\n /**\n * Insert an audio element\n * @param options The audio attributes\n * @example editor.commands.setAudio({ src: 'https://assets.tiptap.dev/sounds/loop.mp3' })\n */\n setAudio: (options: SetAudioOptions) => ReturnType\n }\n }\n}\n\n/**\n * This extension adds support for native audio elements.\n */\nexport const Audio = Node.create<AudioOptions>({\n name: 'audio',\n\n addOptions() {\n return {\n addPasteHandler: true,\n allowBase64: false,\n autoplay: false,\n controls: true,\n loop: false,\n muted: false,\n preload: 'metadata',\n controlslist: undefined,\n crossorigin: undefined,\n disableRemotePlayback: false,\n HTMLAttributes: {},\n inline: false,\n }\n },\n\n inline() {\n return this.options.inline\n },\n\n group() {\n return this.options.inline ? 'inline' : 'block'\n },\n\n atom: true,\n\n draggable: true,\n\n addAttributes() {\n return {\n src: {\n default: null,\n },\n controls: {\n default: this.options.controls,\n },\n autoplay: {\n default: this.options.autoplay,\n },\n loop: {\n default: this.options.loop,\n },\n muted: {\n default: this.options.muted,\n },\n preload: {\n default: this.options.preload,\n },\n controlslist: {\n default: this.options.controlslist,\n },\n crossorigin: {\n default: this.options.crossorigin,\n },\n disableremoteplayback: {\n default: this.options.disableRemotePlayback,\n },\n }\n },\n\n parseHTML() {\n return [\n {\n tag: this.options.allowBase64 ? 'audio[src]' : 'audio[src]:not([src^=\"data:\"])',\n },\n ]\n },\n\n addCommands() {\n return {\n setAudio:\n options =>\n ({ commands }) => {\n if (!isValidAudioUrl(options.src, this.options.allowBase64)) {\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: AUDIO_URL_REGEX_GLOBAL,\n type: this.type,\n getAttributes: match => {\n return { src: match[0] }\n },\n }),\n ]\n },\n\n renderHTML({ HTMLAttributes }) {\n const sanitizedSrc = sanitizeAudioSrc(HTMLAttributes.src, this.options.allowBase64)\n\n const mergedAttributes = mergeAttributes(\n this.options.HTMLAttributes,\n {\n controls: this.options.controls,\n autoplay: this.options.autoplay,\n loop: this.options.loop,\n muted: this.options.muted,\n preload: this.options.preload,\n controlslist: this.options.controlslist,\n crossorigin: this.options.crossorigin,\n disableremoteplayback: this.options.disableRemotePlayback,\n },\n {\n ...HTMLAttributes,\n src: sanitizedSrc,\n },\n )\n\n const cleanedAttributes = Object.fromEntries(\n Object.entries(mergedAttributes).filter(([, value]) => value !== null && value !== undefined && value !== false),\n )\n\n return ['audio', cleanedAttributes]\n },\n\n ...createAtomBlockMarkdownSpec({\n nodeName: 'audio',\n allowedAttributes: [\n 'src',\n 'controls',\n 'autoplay',\n 'loop',\n 'muted',\n 'preload',\n 'controlslist',\n 'crossorigin',\n 'disableremoteplayback',\n ],\n }),\n})\n","/**\n * Regular expression to match URLs pointing to common audio file formats.\n * Matches http/https URLs ending in: mp3, wav, ogg, oga, flac, m4a, aac, opus, weba, webm.\n * Supports optional query strings. Case-insensitive, single match.\n */\nexport const AUDIO_URL_REGEX = /https?:\\/\\/[^\\s]+?\\.(?:mp3|wav|ogg|oga|flac|m4a|aac|opus|weba|webm)(?:\\?[^\\s]*)?/i\n\n/**\n * Global variant of AUDIO_URL_REGEX for matching multiple audio URLs in a string.\n * Use this when you need to find all audio URLs in a block of text.\n */\nexport const AUDIO_URL_REGEX_GLOBAL =\n /https?:\\/\\/[^\\s]+?\\.(?:mp3|wav|ogg|oga|flac|m4a|aac|opus|weba|webm)(?:\\?[^\\s]*)?/gi\n\n/**\n * Regular expression to match base64-encoded audio data URLs.\n */\nconst AUDIO_DATA_URL_REGEX = /^data:audio\\/[a-zA-Z0-9.+-]+;base64,/i\n\n/**\n * Validates whether a URL is safe for use in an audio element.\n * Allows http, https, protocol-relative, and relative paths by default.\n * Data URLs can be enabled via the allowBase64 flag.\n *\n * @param url - The URL string to validate\n * @param allowBase64 - Whether to allow base64 data URLs (default: false)\n * @returns True if the URL is valid and safe, false otherwise\n */\nexport const isValidAudioUrl = (url: string, allowBase64 = false) => {\n if (!url) {\n return false\n }\n\n if (allowBase64 && AUDIO_DATA_URL_REGEX.test(url)) {\n return true\n }\n\n if (url.startsWith('//')) {\n return true\n }\n\n if (url.startsWith('/') || url.startsWith('./') || url.startsWith('../')) {\n return true\n }\n\n try {\n const parsedUrl = new URL(url)\n\n return parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:'\n } catch {\n return false\n }\n}\n\n/**\n * Sanitizes an audio source URL by validating it against allowed protocols.\n * Returns null for invalid or potentially unsafe URLs.\n *\n * @param url - The URL string to sanitize (can be null or undefined)\n * @param allowBase64 - Whether to allow base64 data URLs (default: false)\n * @returns The original URL if valid, or null if invalid/unsafe\n */\nexport const sanitizeAudioSrc = (url: string | null | undefined, allowBase64 = false) => {\n if (!url) {\n return null\n }\n\n return isValidAudioUrl(url, allowBase64) ? url : null\n}\n","import { Audio } from './audio.js'\n\nexport * from './audio.js'\nexport * from './utils.js'\n\nexport default Audio\n"],"mappings":"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","names":[]}