@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 +3 -3
- package/dist/index.cjs +0 -208
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -140
- package/dist/index.d.ts +0 -140
- package/dist/index.js +0 -177
- package/dist/index.js.map +0 -1
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.
|
|
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.
|
|
34
|
+
"@tiptap/core": "^3.20.3"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@tiptap/core": "^3.20.
|
|
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
|
package/dist/index.cjs.map
DELETED
|
@@ -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":[]}
|