@tiptap/extension-audio 3.31.3 → 3.32.0
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/src/audio.test.ts +287 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiptap/extension-audio",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.32.0",
|
|
4
4
|
"description": "audio extension for tiptap",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"tiptap",
|
|
@@ -39,10 +39,10 @@
|
|
|
39
39
|
}
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@tiptap/core": "^3.
|
|
42
|
+
"@tiptap/core": "^3.32.0"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
|
-
"@tiptap/core": "3.
|
|
45
|
+
"@tiptap/core": "3.32.0"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"build": "vp pack"
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { Editor } from '@tiptap/core'
|
|
2
|
+
import { Audio } from './audio.js'
|
|
3
|
+
import Document from '@tiptap/extension-document'
|
|
4
|
+
import Paragraph from '@tiptap/extension-paragraph'
|
|
5
|
+
import Text from '@tiptap/extension-text'
|
|
6
|
+
import { describe, expect, it } from 'vite-plus/test'
|
|
7
|
+
|
|
8
|
+
describe('extension-audio', () => {
|
|
9
|
+
const editorElClass = 'tiptap'
|
|
10
|
+
let editor: Editor | null = null
|
|
11
|
+
|
|
12
|
+
const createEditorEl = () => {
|
|
13
|
+
const editorEl = document.createElement('div')
|
|
14
|
+
|
|
15
|
+
editorEl.classList.add(editorElClass)
|
|
16
|
+
document.body.appendChild(editorEl)
|
|
17
|
+
return editorEl
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const getEditorEl = () => document.querySelector(`.${editorElClass}`)
|
|
21
|
+
|
|
22
|
+
const destroyEditor = () => {
|
|
23
|
+
editor?.destroy()
|
|
24
|
+
editor = null
|
|
25
|
+
getEditorEl()?.remove()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
it('does not output html for javascript schema or invalid links', () => {
|
|
29
|
+
editor = new Editor({
|
|
30
|
+
element: createEditorEl(),
|
|
31
|
+
extensions: [Document, Text, Paragraph, Audio],
|
|
32
|
+
content: {
|
|
33
|
+
type: 'doc',
|
|
34
|
+
content: [
|
|
35
|
+
{
|
|
36
|
+
type: 'audio',
|
|
37
|
+
attrs: {
|
|
38
|
+
// We want to ensure script URLs are filtered out
|
|
39
|
+
// oxlint-disable-next-line no-script-url
|
|
40
|
+
src: 'javascript:alert(window.origin)//audio',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
// oxlint-disable-next-line no-script-url
|
|
48
|
+
expect(editor.getHTML()).not.toContain('javascript:alert')
|
|
49
|
+
|
|
50
|
+
destroyEditor()
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('allows base64 sources only when enabled', () => {
|
|
54
|
+
const dataUrl = 'data:audio/mp3;base64,aGVsbG8='
|
|
55
|
+
|
|
56
|
+
editor = new Editor({
|
|
57
|
+
element: createEditorEl(),
|
|
58
|
+
extensions: [Document, Text, Paragraph, Audio],
|
|
59
|
+
content: {
|
|
60
|
+
type: 'doc',
|
|
61
|
+
content: [
|
|
62
|
+
{
|
|
63
|
+
type: 'audio',
|
|
64
|
+
attrs: {
|
|
65
|
+
src: dataUrl,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
expect(editor.getHTML()).not.toContain(dataUrl)
|
|
73
|
+
|
|
74
|
+
destroyEditor()
|
|
75
|
+
|
|
76
|
+
editor = new Editor({
|
|
77
|
+
element: createEditorEl(),
|
|
78
|
+
extensions: [Document, Text, Paragraph, Audio.configure({ allowBase64: true })],
|
|
79
|
+
content: {
|
|
80
|
+
type: 'doc',
|
|
81
|
+
content: [
|
|
82
|
+
{
|
|
83
|
+
type: 'audio',
|
|
84
|
+
attrs: {
|
|
85
|
+
src: dataUrl,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
expect(editor.getHTML()).toContain(dataUrl)
|
|
93
|
+
|
|
94
|
+
destroyEditor()
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('respects audio attributes and options', () => {
|
|
98
|
+
editor = new Editor({
|
|
99
|
+
element: createEditorEl(),
|
|
100
|
+
extensions: [
|
|
101
|
+
Document,
|
|
102
|
+
Text,
|
|
103
|
+
Paragraph,
|
|
104
|
+
Audio.configure({
|
|
105
|
+
autoplay: true,
|
|
106
|
+
controls: false,
|
|
107
|
+
loop: true,
|
|
108
|
+
muted: true,
|
|
109
|
+
preload: 'auto',
|
|
110
|
+
controlslist: 'nodownload',
|
|
111
|
+
crossorigin: 'anonymous',
|
|
112
|
+
disableRemotePlayback: true,
|
|
113
|
+
}),
|
|
114
|
+
],
|
|
115
|
+
content: {
|
|
116
|
+
type: 'doc',
|
|
117
|
+
content: [
|
|
118
|
+
{
|
|
119
|
+
type: 'audio',
|
|
120
|
+
attrs: {
|
|
121
|
+
src: 'https://assets.tiptap.dev/sounds/loop.mp3',
|
|
122
|
+
controls: true,
|
|
123
|
+
preload: 'none',
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
const html = editor.getHTML()
|
|
131
|
+
|
|
132
|
+
expect(html).toContain('<audio')
|
|
133
|
+
expect(html).toContain('src="https://assets.tiptap.dev/sounds/loop.mp3"')
|
|
134
|
+
expect(html).toContain('autoplay')
|
|
135
|
+
expect(html).toContain('loop')
|
|
136
|
+
expect(html).toContain('muted')
|
|
137
|
+
expect(html).toContain('controlslist="nodownload"')
|
|
138
|
+
expect(html).toContain('crossorigin="anonymous"')
|
|
139
|
+
expect(html).toContain('disableremoteplayback')
|
|
140
|
+
expect(html).toContain('preload="none"')
|
|
141
|
+
expect(html).toContain('controls')
|
|
142
|
+
|
|
143
|
+
destroyEditor()
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('does not insert invalid audio sources via command', () => {
|
|
147
|
+
editor = new Editor({
|
|
148
|
+
element: createEditorEl(),
|
|
149
|
+
extensions: [Document, Text, Paragraph, Audio],
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
const succeeded = editor.commands.setAudio({
|
|
153
|
+
// We want to ensure script URLs are filtered out
|
|
154
|
+
// oxlint-disable-next-line no-script-url
|
|
155
|
+
src: 'javascript:alert(1)',
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
expect(succeeded).toBe(false)
|
|
159
|
+
// oxlint-disable-next-line no-script-url
|
|
160
|
+
expect(editor.getHTML()).not.toContain('javascript:alert')
|
|
161
|
+
|
|
162
|
+
const validInsert = editor.commands.setAudio({
|
|
163
|
+
src: 'https://assets.tiptap.dev/sounds/loop.mp3',
|
|
164
|
+
loop: true,
|
|
165
|
+
muted: true,
|
|
166
|
+
disableremoteplayback: true,
|
|
167
|
+
controls: false,
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
expect(validInsert).toBe(true)
|
|
171
|
+
|
|
172
|
+
const html = editor.getHTML()
|
|
173
|
+
|
|
174
|
+
expect(html).toContain('src="https://assets.tiptap.dev/sounds/loop.mp3"')
|
|
175
|
+
expect(html).toContain('loop')
|
|
176
|
+
expect(html).toContain('muted')
|
|
177
|
+
expect(html).toContain('disableremoteplayback')
|
|
178
|
+
expect(html).not.toContain('controls')
|
|
179
|
+
|
|
180
|
+
destroyEditor()
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('creates audio node when pasting a valid audio URL', () => {
|
|
184
|
+
editor = new Editor({
|
|
185
|
+
element: createEditorEl(),
|
|
186
|
+
extensions: [Document, Text, Paragraph, Audio],
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
const audioUrl = 'https://assets.tiptap.dev/sounds/loop.mp3'
|
|
190
|
+
|
|
191
|
+
// Simulate paste by inserting plain text content that matches the paste rule regex
|
|
192
|
+
editor.view.pasteText(audioUrl)
|
|
193
|
+
|
|
194
|
+
const json = editor.getJSON()
|
|
195
|
+
const audioNode = json.content?.find(node => node.type === 'audio')
|
|
196
|
+
|
|
197
|
+
expect(audioNode).toBeDefined()
|
|
198
|
+
expect(audioNode?.attrs?.src).toBe(audioUrl)
|
|
199
|
+
|
|
200
|
+
destroyEditor()
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
it('does not create audio node when paste handler is disabled', () => {
|
|
204
|
+
editor = new Editor({
|
|
205
|
+
element: createEditorEl(),
|
|
206
|
+
extensions: [Document, Text, Paragraph, Audio.configure({ addPasteHandler: false })],
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
const audioUrl = 'https://assets.tiptap.dev/sounds/loop.mp3'
|
|
210
|
+
|
|
211
|
+
editor.commands.insertContent(audioUrl)
|
|
212
|
+
|
|
213
|
+
const json = editor.getJSON()
|
|
214
|
+
const audioNode = json.content?.find(node => node.type === 'audio')
|
|
215
|
+
|
|
216
|
+
expect(audioNode).toBeUndefined()
|
|
217
|
+
|
|
218
|
+
destroyEditor()
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it('renders audio node as inline when inline option is enabled', () => {
|
|
222
|
+
editor = new Editor({
|
|
223
|
+
element: createEditorEl(),
|
|
224
|
+
extensions: [Document, Text, Paragraph, Audio.configure({ inline: true })],
|
|
225
|
+
content: {
|
|
226
|
+
type: 'doc',
|
|
227
|
+
content: [
|
|
228
|
+
{
|
|
229
|
+
type: 'paragraph',
|
|
230
|
+
content: [
|
|
231
|
+
{
|
|
232
|
+
type: 'text',
|
|
233
|
+
text: 'Audio: ',
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
type: 'audio',
|
|
237
|
+
attrs: {
|
|
238
|
+
src: 'https://assets.tiptap.dev/sounds/loop.mp3',
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
},
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
const html = editor.getHTML()
|
|
248
|
+
|
|
249
|
+
expect(html).toContain('<p>Audio: <audio')
|
|
250
|
+
expect(html).toContain('src="https://assets.tiptap.dev/sounds/loop.mp3"')
|
|
251
|
+
|
|
252
|
+
destroyEditor()
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
it('renders audio node as block by default', () => {
|
|
256
|
+
editor = new Editor({
|
|
257
|
+
element: createEditorEl(),
|
|
258
|
+
extensions: [Document, Text, Paragraph, Audio],
|
|
259
|
+
content: {
|
|
260
|
+
type: 'doc',
|
|
261
|
+
content: [
|
|
262
|
+
{
|
|
263
|
+
type: 'paragraph',
|
|
264
|
+
content: [
|
|
265
|
+
{
|
|
266
|
+
type: 'text',
|
|
267
|
+
text: 'Some text',
|
|
268
|
+
},
|
|
269
|
+
],
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
type: 'audio',
|
|
273
|
+
attrs: {
|
|
274
|
+
src: 'https://assets.tiptap.dev/sounds/loop.mp3',
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
],
|
|
278
|
+
},
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
const html = editor.getHTML()
|
|
282
|
+
|
|
283
|
+
expect(html).toContain('<p>Some text</p><audio')
|
|
284
|
+
|
|
285
|
+
destroyEditor()
|
|
286
|
+
})
|
|
287
|
+
})
|