Skip to content

Commit

Permalink
Merge pull request #23 from li-xiaoqing/dev
Browse files Browse the repository at this point in the history
feat: 新增视频上传插件
  • Loading branch information
li-xiaoqing authored Mar 24, 2020
2 parents d283d9e + 9473644 commit b5a1bbd
Show file tree
Hide file tree
Showing 14 changed files with 397 additions and 147 deletions.
7 changes: 6 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "perfect-markdown",
"version": "1.0.15",
"version": "1.0.19",
"main": "src/index.js",
"files": [
"lib",
Expand Down Expand Up @@ -37,6 +37,7 @@
"markdown-it-sup": "^1.0.0",
"markdown-it-task-lists": "^2.1.1",
"markdown-it-toc": "^1.1.0",
"markdown-it-video": "^0.6.3",
"v-tooltip": "^2.0.2",
"vue-router": "^3.0.1",
"vuex": "^3.0.1"
Expand Down
176 changes: 90 additions & 86 deletions src/assets/fonts/iconfont.css

Large diffs are not rendered by default.

Binary file modified src/assets/fonts/iconfont.eot
Binary file not shown.
87 changes: 45 additions & 42 deletions src/assets/fonts/iconfont.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/assets/fonts/iconfont.ttf
Binary file not shown.
Binary file modified src/assets/fonts/iconfont.woff
Binary file not shown.
Binary file modified src/assets/fonts/iconfont.woff2
Binary file not shown.
63 changes: 62 additions & 1 deletion src/components/toolbar/toolbar-left.vue
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,24 @@
</ul>
</transition>
</span>
<span v-tooltip.top-center="'视频'" class="menu" @click="clickHandler('vide', 'menu')" @mouseenter="showVideoMenu" @mouseleave="hideVideoMenu">
<i class="iconfont icon-video"></i>
<transition name="fade">
<ul
v-show="videoMenuShow"
:class="['icon-menu']"
@mouseenter="showVideoMenu"
@mouseleave="hideVideoMenu"
>
<li @click="addVideFromLink">
来自网络
</li>
<li>
<input type="file" accept="video/mp4,audio/mp4" @change="e => addVideoFromLocal(e)"/>本地上传
</li>
</ul>
</transition>
</span>
<span v-tooltip.top-center="'附件'" @click="clickHandler('file', 'menu')" @mouseenter="showFileMenu" @mouseleave="hideFileMenu">
<i class="iconfont icon-attachment"></i>
<transition name="fade">
Expand Down Expand Up @@ -101,6 +119,17 @@
</div>
</div>
</div>
<div v-show="videoPopShow" class="video-pop">
<div class="dialog">
<div class="input-box">
<input placeholder="视频链接" v-model="videoUrl" type="text">
</div>
<div class="btn-box">
<div @click="closePop('videoPopShow')">取消</div>
<div class="confirm" @click="clickHandler('video', 'insert')">确定</div>
</div>
</div>
</div>
<div v-show="linkPopShow" class="link-pop">
<div class="dialog">
<div class="input-box">
Expand Down Expand Up @@ -140,20 +169,23 @@ export default {
data() {
return {
imageMenuShow: false,
videoMenuShow: false,
fileMenuShow: false,
titleMenuShow: false,
menuShowTimer: null,
videoShowTimer: null,
linkPopShow: false,
imagePopShow: false,
videoPopShow: false,
filePopShow: false,
fileName: '【附件】',
fileUrl: '',
imageName: '',
imageUrl: '',
videoUrl: '',
linkName: '',
linkUrl: '',
imgIndex: 0
}
},
props: {
Expand Down Expand Up @@ -198,6 +230,8 @@ export default {
payload = { name: this.fileName, url: this.fileUrl }
} else if (icon === 'link') {
payload = { name: this.linkName, url: this.linkUrl }
} else if (icon === 'video') {
payload = { url: this.videoUrl }
} else {
// default
}
Expand All @@ -216,6 +250,15 @@ export default {
this.imageMenuShow = false
}, 100)
},
showVideoMenu() {
clearTimeout(this.videoShowTimer)
this.videoMenuShow = true
},
hideVideoMenu() {
this.videoShowTimer = setTimeout(() => {
this.videoMenuShow = false
}, 100)
},
showFileMenu() {
clearTimeout(this.fileMenuShowTimer)
this.fileMenuShow = true
Expand All @@ -237,6 +280,9 @@ export default {
addImgFromLink() {
this.imagePopShow = true
},
addVideFromLink() {
this.videoPopShow = true
},
addFileFromLink() {
this.filePopShow = true
},
Expand All @@ -251,10 +297,24 @@ export default {
e.target.value = '' // input初始化
},
addVideoFromLocal(e) {
const files = e.target.files
console.log(files)
if (files.length > 0) {
[].forEach.call(files, (item, index) => {
this.videoAddHandler(item)
})
}
e.target.value = '' // input初始化
},
imgAddHandler(file, multiple) {
this.imgIndex++
this.$emit('addImg', this.imgIndex, file, multiple)
},
videoAddHandler(file, multiple) {
this.$emit('addVideo', file, multiple)
},
addFileFromLocal(e) {
// 文件上传目前支持单个
const file = e.target.files[0]
Expand Down Expand Up @@ -324,6 +384,7 @@ export default {
}
}
.image-pop,
.video-pop,
.link-pop,
.file-pop {
position: fixed;
Expand Down
11 changes: 0 additions & 11 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,6 @@ import App from './App.vue'
import router from './router'
import store from '@/store'
import md from '@/utils/md'
import { VTooltip, VPopover, VClosePopover } from 'v-tooltip'

import './assets/less/reset.less'
import './assets/less/github-markdown.css' // fork github-markdown
import './assets/fonts/iconfont.css'
import './assets/less/tooltip.less'

Vue.directive('tooltip', VTooltip)
Vue.directive('close-popover', VClosePopover)
Vue.component('v-popover', VPopover)

Vue.use({
install(Vue, options) {
Vue.prototype.$md = md
Expand Down
109 changes: 109 additions & 0 deletions src/plugins/markdown-it-video/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/**
* fork from markdown-it-video
* https://github.com/CenterForOpenScience/markdown-it-video/blob/master/index.js
*/

// Process @[video](videoUrl)

function videoParser(url) {
return url
}

const EMBED_REGEX = /@\[([a-zA-Z].+)]\([\s]*(.*?)[\s]*[)]/im

function videoEmbed(md, options) {
function videoReturn(state, silent) {
let serviceEnd
let serviceStart
let token
let videoUrl
let theState = state
const oldPos = state.pos

if (state.src.charCodeAt(oldPos) !== 0x40/* @ */ ||
state.src.charCodeAt(oldPos + 1) !== 0x5B/* [ */) {
return false
}

const match = EMBED_REGEX.exec(state.src.slice(state.pos, state.src.length))

if (!match || match.length < 3) {
return false
}

const service = match[1]
videoUrl = match[2]
const serviceLower = service.toLowerCase()
if (serviceLower === 'video') {
videoUrl = videoParser(videoUrl)
} else if (!options[serviceLower]) {
return false
}

// If the videoUrl field is empty, regex currently make it the close parenthesis.
if (videoUrl === ')') {
videoUrl = ''
}

serviceStart = oldPos + 2
serviceEnd = md.helpers.parseLinkLabel(state, oldPos + 1, false)

//
// We found the end of the link, and know for a fact it's a valid link;
// so all that's left to do is to call tokenizer.
//
if (!silent) {
theState.pos = serviceStart
theState.service = theState.src.slice(serviceStart, serviceEnd)
const newState = new theState.md.inline.State(service, theState.md, theState.env, [])
newState.md.inline.tokenize(newState)

token = theState.push('video', '')
token.videoUrl = videoUrl
token.service = service
token.url = match[2]
token.level = theState.level
}

theState.pos += theState.src.indexOf(')', theState.pos)
return true
}

return videoReturn
}

function videoUrl(service, videoUrl, url, options) {
return service
}

function tokenizeVideo(md, options) {
function tokenizeReturn(tokens, idx) {
const videoUrl = md.utils.escapeHtml(tokens[idx].videoUrl)
const service = md.utils.escapeHtml(tokens[idx].service).toLowerCase()
return videoUrl === '' ? ''
: `<video class="video" controls width=${options[service].width} height=${options[service].height} src=${videoUrl}></vide>`
}

return tokenizeReturn
}

const defaults = {
url: videoUrl,
video: { width: '100%', height: '100%' }
}

export default function videoPlugin(md, options) {
let theOptions = options
const theMd = md
if (theOptions) {
Object.keys(defaults).forEach(function checkForKeys(key) {
if (typeof theOptions[key] === 'undefined') {
theOptions[key] = defaults[key]
}
})
} else {
theOptions = defaults
}
theMd.renderer.rules.video = tokenizeVideo(theMd, theOptions)
theMd.inline.ruler.before('emphasis', 'video', videoEmbed(theMd, theOptions))
}
43 changes: 43 additions & 0 deletions src/utils/insert.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ export function insertContentAtCaret(dom, icon, payload, $vue) {
case 'image':
imageInsert(dom, payload, $vue)
break
case 'video':
videoInsert(dom, payload, $vue)
break
default:
txtInsert(dom, icon, payload, $vue)
break
Expand Down Expand Up @@ -57,6 +60,46 @@ function fileInsert(dom, payload, $vue) {
}
}

function videoInsert(dom, payload, $vue) {
const textArea = dom()
textArea.focus()
let content = textArea.value // current value
if ('selectionStart' in textArea) {
const start = textArea.selectionStart
const end = textArea.selectionEnd
let newStart = 0
let newEnd = 0

if (start === end) {
// insert
content = content.substring(0, start) + `@[video](${payload.url})` + content.substring(end, content.length)
// foucus url by url
if (isLocal(payload.url)) {
newStart = newEnd = content.length
} else {
newStart = start + 1 + 1 + 5 + 1 + 1
newEnd = newStart + `${payload.url}`.length
}
} else { // selected
// select insert, ignore the input url
content = content.substring(0, start) + `@[video](${content.substring(start, end)})` + content.substring(end, content.length)
if (isLocal(payload.url)) {
newStart = newEnd = content.length
} else {
newStart = start + 1 + 1 + 5 + 1 + 1
newEnd = newStart + end - start
}
}
$vue.setTextareaContent(content)
$vue.$nextTick(() => {
textArea.setSelectionRange(newStart, newEnd)
textArea.focus()
})
} else {
console.warn('the browser version is too low')
}
}

function isLocal(url) {
return typeof url === 'number'
}
Expand Down
8 changes: 6 additions & 2 deletions src/utils/md.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import katex from 'markdown-it-katex' // todos: dynamic import
// local图片插件
import miip from '../plugins/markdown-it-images'

import video from '../plugins/markdown-it-video'

// todos: inject merge
const plugins = {
emoji: emoji,
Expand All @@ -47,7 +49,8 @@ const plugins = {
mdhl: mdhl,
katex: katex,
miip: miip,
attrs
attrs,
video
}

const config = {
Expand Down Expand Up @@ -90,7 +93,8 @@ const installConfig = {
mdhl: true,
katex: false,
miip: true,
attrs: true
attrs: true,
video: true
}
// a link target
const defaultRender = md.renderer.rules.link_open || function (tokens, idx, options, env, self) {
Expand Down
Loading

0 comments on commit b5a1bbd

Please sign in to comment.