jedmund-svelte/src/lib/components/edra/extensions/audio/AudioExtension.ts

147 lines
2.9 KiB
TypeScript

import { Node, nodeInputRule } from '@tiptap/core'
import { Plugin, PluginKey } from '@tiptap/pm/state'
export interface AudioOptions {
HTMLAttributes: Record<string, unknown>
}
declare module '@tiptap/core' {
interface Commands<ReturnType> {
audio: {
/**
* Set a audio node
*/
setAudio: (src: string) => ReturnType
/**
* Toggle a audio
*/
toggleAudio: (src: string) => ReturnType
/**
* Remove a audio
*/
removeAudio: () => ReturnType
}
}
}
const AUDIO_INPUT_REGEX = /!\[(.+|:?)]\((\S+)(?:(?:\s+)["'](\S+)["'])?\)/
export const Audio = Node.create<AudioOptions>({
name: 'audio',
group: 'block',
draggable: true,
isolating: true,
atom: true,
addOptions() {
return {
HTMLAttributes: {}
}
},
addAttributes() {
return {
src: {
default: null,
parseHTML: (el) => (el as HTMLSpanElement).getAttribute('src'),
renderHTML: (attrs) => ({ src: attrs.src })
}
}
},
parseHTML() {
return [
{
tag: 'audio',
getAttrs: (el) => ({ src: (el as HTMLAudioElement).getAttribute('src') })
}
]
},
renderHTML({ HTMLAttributes }) {
return [
'audio',
{ controls: 'true', style: 'width: 100%;', ...HTMLAttributes },
['source', HTMLAttributes]
]
},
addCommands() {
return {
setAudio:
(src: string) =>
({ commands }) =>
commands.insertContent(
`<audio controls autoplay="false" style="width: 100%;" src="${src}"></audio>`
),
toggleAudio:
() =>
({ commands }) =>
commands.toggleNode(this.name, 'paragraph'),
removeAudio:
() =>
({ commands }) =>
commands.deleteNode(this.name)
}
},
addInputRules() {
return [
nodeInputRule({
find: AUDIO_INPUT_REGEX,
type: this.type,
getAttributes: (match) => {
const [, , src] = match
return { src }
}
})
]
},
addProseMirrorPlugins() {
return [
new Plugin({
key: new PluginKey('audioDropPlugin'),
props: {
handleDOMEvents: {
drop(view, event) {
const {
state: { schema, tr },
dispatch
} = view
const hasFiles =
event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files.length
if (!hasFiles) return false
const audios = Array.from(event.dataTransfer.files).filter((file) =>
/audio/i.test(file.type)
)
if (audios.length === 0) return false
event.preventDefault()
const coordinates = view.posAtCoords({ left: event.clientX, top: event.clientY })
audios.forEach((audio) => {
const reader = new FileReader()
reader.onload = (readerEvent) => {
const node = schema.nodes.audio.create({ src: readerEvent.target?.result })
if (coordinates && typeof coordinates.pos === 'number') {
const transaction = tr.insert(coordinates?.pos, node)
dispatch(transaction)
}
}
reader.readAsDataURL(audio)
})
return true
}
}
}
})
]
}
})