Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Video Component #726

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
64 changes: 64 additions & 0 deletions examples/Components/Routes/Videos/index.vue
@@ -0,0 +1,64 @@
<template>
<div class="editor">
<editor-menu-bar :editor="editor" v-slot="{ commands }">
<div class="menubar">
<button class="menubar__button" @click="showVideoPrompt(commands.video)">
<icon name="image" />
</button>
</div>
</editor-menu-bar>

<editor-content class="editor__content" :editor="editor" />
</div>
</template>

<script>
import Icon from 'Components/Icon'
import { Editor, EditorContent, EditorMenuBar } from 'tiptap'
import {
HardBreak,
Heading,
Video,
Bold,
Code,
Italic,
} from 'tiptap-extensions'

export default {
components: {
Icon,
EditorContent,
EditorMenuBar,
},
data() {
return {
editor: new Editor({
extensions: [
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new Video(),
new Bold(),
new Code(),
new Italic(),
],
content: `
<h2>
Videos
</h2>
<p>
This is basic example of implementing videos. Try to drop new videos here. Reordering also works.
</p>
`,
}),
}
},
methods: {
showVideoPrompt(command) {
const src = prompt('Enter the url of your video here')
if (src !== null) {
command({ src })
}
},
},
}
</script>
7 changes: 7 additions & 0 deletions examples/main.js
Expand Up @@ -46,6 +46,13 @@ const routes = [
githubUrl: 'https://github.com/scrumpy/tiptap/tree/master/examples/Components/Routes/Images',
},
},
{
path: '/videos',
component: () => import('Components/Routes/Videos'),
meta: {
githubUrl: 'https://github.com/scrumpy/tiptap/tree/master/examples/Components/Routes/Images',
},
},
{
path: '/hiding-menu-bar',
component: () => import('Components/Routes/HidingMenuBar'),
Expand Down
1 change: 1 addition & 0 deletions packages/tiptap-extensions/src/index.js
Expand Up @@ -6,6 +6,7 @@ export { default as HardBreak } from './nodes/HardBreak'
export { default as Heading } from './nodes/Heading'
export { default as HorizontalRule } from './nodes/HorizontalRule'
export { default as Image } from './nodes/Image'
export { default as Video } from './nodes/Video'
export { default as ListItem } from './nodes/ListItem'
export { default as Mention } from './nodes/Mention'
export { default as OrderedList } from './nodes/OrderedList'
Expand Down
118 changes: 118 additions & 0 deletions packages/tiptap-extensions/src/nodes/Video.js
@@ -0,0 +1,118 @@
import {
Node,
Plugin
} from 'tiptap'
import {
nodeInputRule
} from 'tiptap-commands'

const VIDEO_INPUT_REGEX = /!\[(.+|:?)]\((\S+)(?:(?:\s+)["'](\S+)["'])?\)/

export default class Video extends Node {

get name() {
return 'video'
}

get schema() {
return {
inline: true,
attrs: {
src: {},
},
group: 'inline',
draggable: true,
parseDOM: [{
tag: 'video',
getAttrs: dom => ({
src: dom.getAttribute('src'),
}),
},],
toDOM: node => ['video', {
'controls': true,
'style': 'width: 100%',
},
['source', node.attrs]
],
}
}

commands({
type
}) {
return attrs => (state, dispatch) => {
const {
selection
} = state
const position = selection.$cursor ? selection.$cursor.pos : selection.$to.pos
const node = type.create(attrs)
const transaction = state.tr.insert(position, node)
dispatch(transaction)
}
}

inputRules({
type
}) {
return [
nodeInputRule(VIDEO_INPUT_REGEX, type, match => {
const [, src,] = match
return {
src,
}
}),
]
}

get plugins() {
return [
new Plugin({
props: {
handleDOMEvents: {
drop(view, event) {
const hasFiles = event.dataTransfer &&
event.dataTransfer.files &&
event.dataTransfer.files.length

if (!hasFiles) {
return
}

const videos = Array
.from(event.dataTransfer.files)
.filter(file => (/video/i).test(file.type))

if (videos.length === 0) {
return
}

event.preventDefault()

const {
schema
} = view.state
const coordinates = view.posAtCoords({
left: event.clientX,
top: event.clientY
})

videos.forEach(video => {
const reader = new FileReader()

reader.onload = readerEvent => {
const node = schema.nodes.video.create({
src: readerEvent.target.result,
})
const transaction = view.state.tr.insert(coordinates.pos, node)
view.dispatch(transaction)
}
reader.readAsDataURL(video)
})
},
},
},
}),
]
}

}