@tiptap/extension-audio 3.31.2 → 3.31.4

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.
Files changed (2) hide show
  1. package/package.json +3 -3
  2. 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.31.2",
3
+ "version": "3.31.4",
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.31.2"
42
+ "@tiptap/core": "^3.31.4"
43
43
  },
44
44
  "peerDependencies": {
45
- "@tiptap/core": "3.31.2"
45
+ "@tiptap/core": "3.31.4"
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
+ })