Merge pull request #2724 from wangeditor-team/feature-uploadVideo
上传视频功能
This commit is contained in:
commit
f4525bc72f
|
@ -0,0 +1,128 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>wangEditor example</title>
|
||||
<script src="//cdn.jsdelivr.net/npm/xgplayer@2.9.6/browser/index.js" type="text/javascript"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>
|
||||
wangEditor demo
|
||||
</p>
|
||||
<div id="div1">
|
||||
<p>欢迎使用 <b>wangEditor</b> 富文本编辑器</p>
|
||||
<p>测试视频</p>
|
||||
<p>
|
||||
<video src="/server/upload-files/测试1-w9b.mp4" controls="controls" style="max-width:100%"></video>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<script src="../dist/wangEditor.js"></script>
|
||||
<script>
|
||||
// 改为使用var声明,才能在window对象上获取到编辑器实例,方便e2e测试
|
||||
var E = window.wangEditor
|
||||
var editor = new E('#div1')
|
||||
|
||||
// 上传地址
|
||||
editor.config.uploadVideoServer = '/api/upload-video';
|
||||
|
||||
// 自定义插入视频
|
||||
// editor.config.customInsertVideo = (url) => {
|
||||
// console.log('自定义插入视频---->', url);
|
||||
// // 演示西瓜视频
|
||||
// editor.cmd.do('insertHTML', `<p>
|
||||
// <p contenteditable = false id="mse" style="max-width:100%"></p>
|
||||
// </p>`)
|
||||
// let player = new Player({
|
||||
// id: 'mse',
|
||||
// url: url
|
||||
// });
|
||||
// }
|
||||
|
||||
// // 自定义上传
|
||||
// editor.config.customUploadVideo = (files, insertVideoFn) => {
|
||||
// console.log('自定义上传---->', files);
|
||||
// insertVideoFn('/server/upload-files/测试1-w9b.mp4');
|
||||
// }
|
||||
|
||||
// 显示“插入视频”
|
||||
editor.config.showLinkVideo = true;
|
||||
|
||||
// accept
|
||||
editor.config.uploadVideoAccept = ['mp4'];
|
||||
|
||||
// 上传视频的最大体积,默认 1024M
|
||||
editor.config.uploadVideoMaxSize = 1 * 1024 * 1024 * 1024;
|
||||
|
||||
// 自定义上传视频的名称
|
||||
editor.config.uploadVideoName = '';
|
||||
|
||||
// 上传视频自定义参数
|
||||
editor.config.uploadVideoParams = {
|
||||
name: 'v2',
|
||||
};
|
||||
|
||||
// 自定义参数拼接到 url 中
|
||||
editor.config.uploadVideoParamsWithUrl = true;
|
||||
|
||||
// 上传视频自定义 header
|
||||
editor.config.uploadVideoHeaders = {};
|
||||
|
||||
// 钩子函数
|
||||
editor.config.uploadVideoHooks = {
|
||||
// 上传视频之前
|
||||
before: function (xhr) {
|
||||
console.log(xhr)
|
||||
|
||||
// 可阻止视频上传
|
||||
// return {
|
||||
// prevent: true,
|
||||
// msg: '需要提示给用户的错误信息'
|
||||
// }
|
||||
},
|
||||
// 视频上传并返回了结果,视频插入已成功
|
||||
success: function (xhr) {
|
||||
console.log('success', xhr)
|
||||
},
|
||||
// 视频上传并返回了结果,但视频插入时出错了
|
||||
fail: function (xhr, editor, resData) {
|
||||
console.log('fail', resData)
|
||||
},
|
||||
// 上传视频出错,一般为 http 请求的错误
|
||||
error: function (xhr, editor, resData) {
|
||||
console.log('error', xhr, resData)
|
||||
},
|
||||
// 上传视频超时
|
||||
timeout: function (xhr) {
|
||||
console.log('timeout')
|
||||
},
|
||||
// 视频上传并返回了结果,想要自己把视频插入到编辑器中
|
||||
// 例如服务器端返回的不是 { errno: 0, data: {url: ....} } 这种格式,可使用 customInsert
|
||||
customInsert: function (insertVideoFn, result) {
|
||||
// result 即服务端返回的接口
|
||||
console.log('customInsert', result)
|
||||
|
||||
// insertVideoFn 可把视频插入到编辑器,传入视频 src ,执行函数即可
|
||||
insertVideoFn(result.data.url)
|
||||
}
|
||||
};
|
||||
|
||||
// 上传视频超时时间 ms 默认5分钟
|
||||
editor.config.uploadVideoTimeout = 1000 * 60 * 5;
|
||||
|
||||
// 跨域带 cookie
|
||||
editor.config.withVideoCredentials = false;
|
||||
|
||||
// 自定义alert
|
||||
editor.config.customAlert = (s) => {
|
||||
console.log('customAlert: ' + s)
|
||||
}
|
||||
|
||||
editor.create()
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,99 @@
|
|||
/**
|
||||
* @description 保存上传的文件
|
||||
* @author wangfupeng
|
||||
*/
|
||||
|
||||
const os = require('os')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const formidable = require('formidable')
|
||||
const { objForEach } = require('../util')
|
||||
const FILE_FOLDER = 'upload-files'
|
||||
const isWindows = os.type().toLowerCase().indexOf('windows') >= 0
|
||||
const TMP_FOLDER = 'upload-files-tmp'
|
||||
|
||||
/**
|
||||
* 获取随机数
|
||||
*/
|
||||
function getRandom() {
|
||||
return Math.random().toString(36).slice(-3)
|
||||
}
|
||||
|
||||
/**
|
||||
* 给文件名加后缀,如 a.png 转换为 a-123123.png
|
||||
* @param {string} fileName 文件名
|
||||
*/
|
||||
function genRandomFileName(fileName = '') {
|
||||
// 如 fileName === 'a.123.png'
|
||||
|
||||
const r = getRandom()
|
||||
if (!fileName) return r
|
||||
|
||||
const length = fileName.length // 9
|
||||
const pointLastIndexOf = fileName.lastIndexOf('.') // 5
|
||||
if (pointLastIndexOf < 0) return `${fileName}-${r}`
|
||||
|
||||
const fileNameWithOutExt = fileName.slice(0, pointLastIndexOf) // "a.123"
|
||||
const ext = fileName.slice(pointLastIndexOf + 1, length) // "png"
|
||||
return `${fileNameWithOutExt}-${r}.${ext}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存上传的文件
|
||||
* @param {Object} req request
|
||||
*/
|
||||
function saveFiles(req) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const videoLinks = []
|
||||
const form = formidable({ multiples: true })
|
||||
|
||||
// windows 系统,处理 rename 报错
|
||||
if (isWindows) {
|
||||
const tmpPath = path.resolve(__dirname, '..', '..', TMP_FOLDER) // 在根目录下
|
||||
if (!fs.existsSync(tmpPath)) {
|
||||
fs.mkdirSync(tmpPath)
|
||||
}
|
||||
form.uploadDir = TMP_FOLDER
|
||||
}
|
||||
|
||||
form.parse(req, function (err, fields, files) {
|
||||
if (err) {
|
||||
reject('formidable, form.parse err', err.stack)
|
||||
}
|
||||
// 存储视频的文件夹
|
||||
const storePath = path.resolve(__dirname, '..', FILE_FOLDER)
|
||||
if (!fs.existsSync(storePath)) {
|
||||
fs.mkdirSync(storePath)
|
||||
}
|
||||
|
||||
// 遍历所有上传来的视频
|
||||
objForEach(files, (name, file) => {
|
||||
console.log('name...', name)
|
||||
|
||||
// 视频临时位置
|
||||
const tempFilePath = file.path
|
||||
// 视频名称和路径
|
||||
const fileName = genRandomFileName(name) // 为文件名增加一个随机数,防止同名文件覆盖
|
||||
console.log('fileName...', fileName)
|
||||
const fullFileName = path.join(storePath, fileName)
|
||||
console.log('fullFileName...', fullFileName)
|
||||
// 将临时文件保存为正式文件
|
||||
fs.renameSync(tempFilePath, fullFileName)
|
||||
// 存储链接
|
||||
const url = `/server/${FILE_FOLDER}/` + fileName
|
||||
videoLinks.push(url)
|
||||
})
|
||||
console.log('videoLinks...', videoLinks)
|
||||
|
||||
// 返回结果
|
||||
resolve({
|
||||
errno: 0,
|
||||
data: {
|
||||
url: videoLinks[0]
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = saveFiles
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
const router = require('koa-router')()
|
||||
const saveFiles = require('./controller/save-file')
|
||||
const saveVideoFile = require('./controller/saveVideo-file')
|
||||
|
||||
router.prefix('/api')
|
||||
|
||||
|
@ -19,4 +20,10 @@ router.post('/upload-img', async function (ctx, next) {
|
|||
ctx.body = data
|
||||
})
|
||||
|
||||
// 上传视频
|
||||
router.post('/upload-video', async function (ctx, next) {
|
||||
const data = await saveVideoFile(ctx.req)
|
||||
ctx.body = data
|
||||
})
|
||||
|
||||
module.exports = router
|
||||
|
|
|
@ -141,8 +141,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
/* 上传图片的 panel 定制样式 */
|
||||
.w-e-up-img-container {
|
||||
/* 上传图片、上传视频的 panel 定制样式 */
|
||||
.w-e-up-img-container, .w-e-up-video-container {
|
||||
text-align: center;
|
||||
|
||||
.w-e-up-btn {
|
||||
|
|
|
@ -12,7 +12,7 @@ import imageConfig, { UploadImageHooksType } from './image'
|
|||
import textConfig from './text'
|
||||
import langConfig from './lang'
|
||||
import historyConfig from './history'
|
||||
import videoConfig from './video'
|
||||
import videoConfig, { UploadVideoHooksType } from './video'
|
||||
|
||||
// 字典类型
|
||||
export type DicType = {
|
||||
|
@ -76,6 +76,20 @@ export type ConfigType = {
|
|||
|
||||
onlineVideoCheck: Function
|
||||
onlineVideoCallback: Function
|
||||
|
||||
showLinkVideo: Boolean
|
||||
uploadVideoAccept: string[]
|
||||
uploadVideoServer: string
|
||||
uploadVideoMaxSize: number
|
||||
uploadVideoName: string
|
||||
uploadVideoParams: DicType
|
||||
uploadVideoParamsWithUrl: boolean
|
||||
uploadVideoHeaders: DicType
|
||||
uploadVideoHooks: UploadVideoHooksType
|
||||
uploadVideoTimeout: number
|
||||
withVideoCredentials: boolean
|
||||
customUploadVideo: Function | null
|
||||
customInsertVideo: Function | null
|
||||
}
|
||||
|
||||
export type Resource = {
|
||||
|
|
|
@ -86,6 +86,7 @@ export default {
|
|||
},
|
||||
video: {
|
||||
插入视频: '插入视频',
|
||||
上传视频: '上传视频',
|
||||
},
|
||||
table: {
|
||||
行: '行',
|
||||
|
@ -126,6 +127,15 @@ export default {
|
|||
请替换为支持的图片类型: '请替换为支持的图片类型',
|
||||
您插入的网络图片无法识别: '您插入的网络图片无法识别',
|
||||
您刚才插入的图片链接未通过编辑器校验: '您刚才插入的图片链接未通过编辑器校验',
|
||||
插入视频错误: '插入视频错误',
|
||||
视频链接: '视频链接',
|
||||
不是视频: '不是视频',
|
||||
视频验证未通过: '视频验证未通过',
|
||||
个视频: '个视频',
|
||||
上传视频超时: '上传视频超时',
|
||||
上传视频错误: '上传视频错误',
|
||||
上传视频失败: '上传视频失败',
|
||||
上传视频返回结果错误: '上传视频返回结果错误',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -214,6 +224,7 @@ export default {
|
|||
},
|
||||
video: {
|
||||
插入视频: 'insert video',
|
||||
上传视频: 'upload local video',
|
||||
},
|
||||
table: {
|
||||
行: 'rows',
|
||||
|
@ -255,6 +266,15 @@ export default {
|
|||
您插入的网络图片无法识别: 'the network picture you inserted is not recognized',
|
||||
您刚才插入的图片链接未通过编辑器校验:
|
||||
'the image link you just inserted did not pass the editor verification',
|
||||
插入视频错误: 'insert video error',
|
||||
视频链接: 'video link',
|
||||
不是视频: 'is not video',
|
||||
视频验证未通过: 'video validate failed',
|
||||
个视频: 'videos',
|
||||
上传视频超时: 'upload video timeout',
|
||||
上传视频错误: 'upload video error',
|
||||
上传视频失败: 'upload video failed',
|
||||
上传视频返回结果错误: 'upload video return results error',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -3,7 +3,26 @@
|
|||
* @author hutianhao
|
||||
*/
|
||||
|
||||
import Editor from '../editor/index'
|
||||
import { EMPTY_FN } from '../utils/const'
|
||||
import { ResType } from '../menus/video/upload-video'
|
||||
|
||||
export type UploadVideoHooksType = {
|
||||
before?: (
|
||||
xhr: XMLHttpRequest,
|
||||
editor: Editor,
|
||||
files: File[]
|
||||
) => { prevent: boolean; msg: string } | void
|
||||
success?: (xhr: XMLHttpRequest, editor: Editor, result: ResType) => void
|
||||
fail?: (xhr: XMLHttpRequest, editor: Editor, err: ResType | string) => void
|
||||
error?: (xhr: XMLHttpRequest, editor: Editor) => void
|
||||
timeout?: (xhr: XMLHttpRequest, editor: Editor) => void
|
||||
customInsert?: (
|
||||
inserVideo: (this: Editor, src: string) => void,
|
||||
result: ResType,
|
||||
editor: Editor
|
||||
) => void
|
||||
}
|
||||
|
||||
export default {
|
||||
// 插入网络视频前的回调函数
|
||||
|
@ -13,4 +32,46 @@ export default {
|
|||
|
||||
// 插入网络视频成功之后的回调函数
|
||||
onlineVideoCallback: EMPTY_FN,
|
||||
|
||||
// 显示“插入视频”
|
||||
showLinkVideo: true,
|
||||
|
||||
// accept
|
||||
uploadVideoAccept: ['mp4'],
|
||||
|
||||
// 服务端地址
|
||||
uploadVideoServer: '',
|
||||
|
||||
// 上传视频的最大体积,默认 1024M
|
||||
uploadVideoMaxSize: 1 * 1024 * 1024 * 1024,
|
||||
|
||||
// 一次最多上传多少个视频
|
||||
// uploadVideoMaxLength: 2,
|
||||
|
||||
// 自定义上传视频的名称
|
||||
uploadVideoName: '',
|
||||
|
||||
// 上传视频自定义参数
|
||||
uploadVideoParams: {},
|
||||
|
||||
// 自定义参数拼接到 url 中
|
||||
uploadVideoParamsWithUrl: false,
|
||||
|
||||
// 上传视频自定义 header
|
||||
uploadVideoHeaders: {},
|
||||
|
||||
// 钩子函数
|
||||
uploadVideoHooks: {},
|
||||
|
||||
// 上传视频超时时间 ms 默认2个小时
|
||||
uploadVideoTimeout: 1000 * 60 * 60 * 2,
|
||||
|
||||
// 跨域带 cookie
|
||||
withVideoCredentials: false,
|
||||
|
||||
// 自定义上传
|
||||
customUploadVideo: null,
|
||||
|
||||
// 自定义插入视频
|
||||
customInsertVideo: null,
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* @description 绑定视频的事件
|
||||
* @author lichunlin
|
||||
*/
|
||||
|
||||
import Editor from '../../../editor/index'
|
||||
import bindTooltipVideo from './tooltip-event'
|
||||
|
||||
/**
|
||||
* 绑定事件
|
||||
* @param editor 编辑器实例
|
||||
*/
|
||||
function bindEvent(editor: Editor): void {
|
||||
//Tooltip
|
||||
bindTooltipVideo(editor)
|
||||
}
|
||||
|
||||
export default bindEvent
|
|
@ -0,0 +1,114 @@
|
|||
/**
|
||||
* @description tooltip 事件
|
||||
* @author lichunlin
|
||||
*/
|
||||
|
||||
import $, { DomElement } from '../../../utils/dom-core'
|
||||
import Tooltip, { TooltipConfType } from '../../menu-constructors/Tooltip'
|
||||
import Editor from '../../../editor/index'
|
||||
|
||||
/**
|
||||
* 生成 Tooltip 的显示隐藏函数
|
||||
*/
|
||||
export function createShowHideFn(editor: Editor) {
|
||||
let tooltip: Tooltip | null
|
||||
const t = (text: string, prefix: string = ''): string => {
|
||||
return editor.i18next.t(prefix + text)
|
||||
}
|
||||
/**
|
||||
* 显示 tooltip
|
||||
* @param $node 链接元素
|
||||
*/
|
||||
function showVideoTooltip($node: DomElement) {
|
||||
const conf: TooltipConfType = [
|
||||
{
|
||||
$elem: $("<span class='w-e-icon-trash-o'></span>"),
|
||||
onClick: (editor: Editor, $node: DomElement) => {
|
||||
// 选中video元素
|
||||
editor.selection.createRangeByElem($node)
|
||||
editor.selection.restoreSelection()
|
||||
editor.cmd.do('delete')
|
||||
// 返回 true,表示执行完之后,隐藏 tooltip。否则不隐藏。
|
||||
return true
|
||||
},
|
||||
},
|
||||
{
|
||||
$elem: $('<span>100%</span>'),
|
||||
onClick: (editor: Editor, $node: DomElement) => {
|
||||
$node.attr('width', '100%')
|
||||
$node.removeAttr('height')
|
||||
// 返回 true,表示执行完之后,隐藏 tooltip。否则不隐藏。
|
||||
return true
|
||||
},
|
||||
},
|
||||
{
|
||||
$elem: $('<span>50%</span>'),
|
||||
onClick: (editor: Editor, $node: DomElement) => {
|
||||
$node.attr('width', '50%')
|
||||
$node.removeAttr('height')
|
||||
// 返回 true,表示执行完之后,隐藏 tooltip。否则不隐藏。
|
||||
return true
|
||||
},
|
||||
},
|
||||
{
|
||||
$elem: $('<span>30%</span>'),
|
||||
onClick: (editor: Editor, $node: DomElement) => {
|
||||
$node.attr('width', '30%')
|
||||
$node.removeAttr('height')
|
||||
// 返回 true,表示执行完之后,隐藏 tooltip。否则不隐藏。
|
||||
return true
|
||||
},
|
||||
},
|
||||
{
|
||||
$elem: $(`<span>${t('重置')}</span>`),
|
||||
onClick: (editor: Editor, $node: DomElement) => {
|
||||
$node.removeAttr('width')
|
||||
$node.removeAttr('height')
|
||||
|
||||
// 返回 true,表示执行完之后,隐藏 tooltip。否则不隐藏。
|
||||
return true
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
tooltip = new Tooltip(editor, $node, conf)
|
||||
tooltip.create()
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏 tooltip
|
||||
*/
|
||||
function hideVideoTooltip() {
|
||||
// 移除 tooltip
|
||||
if (tooltip) {
|
||||
tooltip.remove()
|
||||
tooltip = null
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
showVideoTooltip,
|
||||
hideVideoTooltip,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定 tooltip 事件
|
||||
* @param editor 编辑器实例
|
||||
*/
|
||||
export default function bindTooltipEvent(editor: Editor) {
|
||||
const { showVideoTooltip, hideVideoTooltip } = createShowHideFn(editor)
|
||||
|
||||
// 点击视频元素是,显示 tooltip
|
||||
editor.txt.eventHooks.videoClickEvents.push(showVideoTooltip)
|
||||
|
||||
// 点击其他地方,或者滚动时,隐藏 tooltip
|
||||
editor.txt.eventHooks.clickEvents.push(hideVideoTooltip)
|
||||
editor.txt.eventHooks.keyupEvents.push(hideVideoTooltip)
|
||||
editor.txt.eventHooks.toolbarClickEvents.push(hideVideoTooltip)
|
||||
editor.txt.eventHooks.menuClickEvents.push(hideVideoTooltip)
|
||||
editor.txt.eventHooks.textScrollEvents.push(hideVideoTooltip)
|
||||
|
||||
// change 时隐藏
|
||||
editor.txt.eventHooks.changeEvents.push(hideVideoTooltip)
|
||||
}
|
|
@ -4,16 +4,22 @@
|
|||
*/
|
||||
|
||||
import Editor from '../../editor/index'
|
||||
import { PanelConf } from '../menu-constructors/Panel'
|
||||
import { PanelConf, PanelTabConf } from '../menu-constructors/Panel'
|
||||
import { getRandom } from '../../utils/util'
|
||||
import $ from '../../utils/dom-core'
|
||||
import UploadVideo from './upload-video'
|
||||
import { videoRegex } from '../../utils/const'
|
||||
|
||||
export default function (editor: Editor, video: string): PanelConf {
|
||||
const config = editor.config
|
||||
const uploadVideo = new UploadVideo(editor)
|
||||
|
||||
// panel 中需要用到的id
|
||||
const inputIFrameId = getRandom('input-iframe')
|
||||
const btnOkId = getRandom('btn-ok')
|
||||
const i18nPrefix = 'menus.panelMenus.video.'
|
||||
const inputUploadId = getRandom('input-upload')
|
||||
const btnStartId = getRandom('btn-local-ok')
|
||||
const t = (text: string, prefix: string = i18nPrefix): string => {
|
||||
return editor.i18next.t(prefix + text)
|
||||
}
|
||||
|
@ -64,54 +70,118 @@ export default function (editor: Editor, video: string): PanelConf {
|
|||
return false
|
||||
}
|
||||
|
||||
const conf = {
|
||||
// tabs配置
|
||||
// const fileMultipleAttr = config.uploadVideoMaxLength === 1 ? '' : 'multiple="multiple"'
|
||||
const tabsConf: PanelTabConf[] = [
|
||||
{
|
||||
// tab 的标题
|
||||
title: editor.i18next.t('menus.panelMenus.video.上传视频'),
|
||||
tpl: `<div class="w-e-up-video-container">
|
||||
<div id="${btnStartId}" class="w-e-up-btn">
|
||||
<i class="w-e-icon-upload2"></i>
|
||||
</div>
|
||||
<div style="display:none;">
|
||||
<input id="${inputUploadId}" type="file" accept="video/*"/>
|
||||
</div>
|
||||
</div>`,
|
||||
events: [
|
||||
// 触发选择视频
|
||||
{
|
||||
selector: '#' + btnStartId,
|
||||
type: 'click',
|
||||
fn: () => {
|
||||
const $file = $('#' + inputUploadId)
|
||||
const fileElem = $file.elems[0]
|
||||
if (fileElem) {
|
||||
fileElem.click()
|
||||
} else {
|
||||
// 返回 true 可关闭 panel
|
||||
return true
|
||||
}
|
||||
},
|
||||
},
|
||||
// 选择视频完毕
|
||||
{
|
||||
selector: '#' + inputUploadId,
|
||||
type: 'change',
|
||||
fn: () => {
|
||||
const $file = $('#' + inputUploadId)
|
||||
const fileElem = $file.elems[0]
|
||||
if (!fileElem) {
|
||||
// 返回 true 可关闭 panel
|
||||
return true
|
||||
}
|
||||
|
||||
// 获取选中的 file 对象列表
|
||||
const fileList = (fileElem as any).files
|
||||
if (fileList.length) {
|
||||
uploadVideo.uploadVideo(fileList)
|
||||
}
|
||||
|
||||
// 返回 true 可关闭 panel
|
||||
return true
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
// tab 的标题
|
||||
title: editor.i18next.t('menus.panelMenus.video.插入视频'),
|
||||
// 模板
|
||||
tpl: `<div>
|
||||
<input
|
||||
id="${inputIFrameId}"
|
||||
type="text"
|
||||
class="block"
|
||||
placeholder="${editor.i18next.t('如')}:<iframe src=... ></iframe>"/>
|
||||
</td>
|
||||
<div class="w-e-button-container">
|
||||
<button type="button" id="${btnOkId}" class="right">
|
||||
${editor.i18next.t('插入')}
|
||||
</button>
|
||||
</div>
|
||||
</div>`,
|
||||
// 事件绑定
|
||||
events: [
|
||||
// 插入视频
|
||||
{
|
||||
selector: '#' + btnOkId,
|
||||
type: 'click',
|
||||
fn: () => {
|
||||
// 执行插入视频
|
||||
const $video = $('#' + inputIFrameId)
|
||||
let video = $video.val().trim()
|
||||
|
||||
// 视频为空,则不插入
|
||||
if (!video) return
|
||||
// 对当前用户插入的内容进行判断,插入为空,或者返回false,都停止插入
|
||||
if (!checkOnlineVideo(video)) return
|
||||
|
||||
insertVideo(video)
|
||||
|
||||
// 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭
|
||||
return true
|
||||
},
|
||||
},
|
||||
],
|
||||
}, // tab end
|
||||
]
|
||||
|
||||
const conf: PanelConf = {
|
||||
width: 300,
|
||||
height: 0,
|
||||
|
||||
// panel 中可包含多个 tab
|
||||
tabs: [
|
||||
{
|
||||
// tab 的标题
|
||||
title: editor.i18next.t('menus.panelMenus.video.插入视频'),
|
||||
// 模板
|
||||
tpl: `<div>
|
||||
<input
|
||||
id="${inputIFrameId}"
|
||||
type="text"
|
||||
class="block"
|
||||
placeholder="${editor.i18next.t('如')}:<iframe src=... ></iframe>"/>
|
||||
</td>
|
||||
<div class="w-e-button-container">
|
||||
<button type="button" id="${btnOkId}" class="right">
|
||||
${editor.i18next.t('插入')}
|
||||
</button>
|
||||
</div>
|
||||
</div>`,
|
||||
// 事件绑定
|
||||
events: [
|
||||
// 插入视频
|
||||
{
|
||||
selector: '#' + btnOkId,
|
||||
type: 'click',
|
||||
fn: () => {
|
||||
// 执行插入视频
|
||||
const $video = $('#' + inputIFrameId)
|
||||
let video = $video.val().trim()
|
||||
tabs: [], // tabs end
|
||||
}
|
||||
|
||||
// 视频为空,则不插入
|
||||
if (!video) return
|
||||
// 对当前用户插入的内容进行判断,插入为空,或者返回false,都停止插入
|
||||
if (!checkOnlineVideo(video)) return
|
||||
|
||||
insertVideo(video)
|
||||
|
||||
// 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭
|
||||
return true
|
||||
},
|
||||
},
|
||||
],
|
||||
}, // tab end
|
||||
], // tabs end
|
||||
// 显示“上传视频”
|
||||
if (window.FileReader && (config.uploadVideoServer || config.customUploadVideo)) {
|
||||
conf.tabs.push(tabsConf[0])
|
||||
}
|
||||
// 显示“插入视频”
|
||||
if (config.showLinkVideo) {
|
||||
conf.tabs.push(tabsConf[1])
|
||||
}
|
||||
|
||||
return conf
|
||||
|
|
|
@ -9,6 +9,7 @@ import Editor from '../../editor/index'
|
|||
import PanelMenu from '../menu-constructors/PanelMenu'
|
||||
import { MenuActive } from '../menu-constructors/Menu'
|
||||
import createPanelConf from './create-panel-conf'
|
||||
import bindEvent from './bind-event/index'
|
||||
|
||||
class Video extends PanelMenu implements MenuActive {
|
||||
constructor(editor: Editor) {
|
||||
|
@ -18,6 +19,9 @@ class Video extends PanelMenu implements MenuActive {
|
|||
</div>`
|
||||
)
|
||||
super($elem, editor)
|
||||
|
||||
// 绑定事件 tootip
|
||||
bindEvent(editor)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,279 @@
|
|||
/**
|
||||
* @description 上传视频
|
||||
* @author lichunlin
|
||||
*/
|
||||
|
||||
import Editor from '../../editor/index'
|
||||
import { arrForEach, forEach } from '../../utils/util'
|
||||
import post from '../../editor/upload/upload-core'
|
||||
import Progress from '../../editor/upload/progress'
|
||||
|
||||
type ResData = {
|
||||
url: string
|
||||
}
|
||||
|
||||
// 后台返回的格式
|
||||
export type ResType = {
|
||||
errno: number | string
|
||||
data: ResData
|
||||
}
|
||||
|
||||
class UploadVideo {
|
||||
private editor: Editor
|
||||
|
||||
constructor(editor: Editor) {
|
||||
this.editor = editor
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传视频
|
||||
* @param files 文件列表
|
||||
*/
|
||||
public uploadVideo(files: FileList | File[]): void {
|
||||
if (!files.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const editor = this.editor
|
||||
const config = editor.config
|
||||
|
||||
// ------------------------------ i18next ------------------------------
|
||||
|
||||
const i18nPrefix = 'validate.'
|
||||
const t = (text: string): string => {
|
||||
return editor.i18next.t(i18nPrefix + text)
|
||||
}
|
||||
|
||||
// ------------------------------ 获取配置信息 ------------------------------
|
||||
// 服务端地址
|
||||
let uploadVideoServer = config.uploadVideoServer
|
||||
// 上传视频的最大体积,默认 1024M
|
||||
const maxSize = config.uploadVideoMaxSize
|
||||
const uploadVideoMaxSize = maxSize / 1024 / 1024
|
||||
// 一次最多上传多少个视频
|
||||
// const uploadVideoMaxLength = config.uploadVideoMaxLength
|
||||
// 自定义上传视频的名称
|
||||
const uploadVideoName = config.uploadVideoName
|
||||
// 上传视频自定义参数
|
||||
const uploadVideoParams = config.uploadVideoParams
|
||||
// 自定义参数拼接到 url 中
|
||||
const uploadVideoParamsWithUrl = config.uploadVideoParamsWithUrl
|
||||
// 上传视频自定义 header
|
||||
const uploadVideoHeaders = config.uploadVideoHeaders
|
||||
// 钩子函数
|
||||
const uploadVideoHooks = config.uploadVideoHooks
|
||||
// 上传视频超时时间 ms 默认2个小时
|
||||
const uploadVideoTimeout = config.uploadVideoTimeout
|
||||
// 跨域带 cookie
|
||||
const withVideoCredentials = config.withVideoCredentials
|
||||
// 自定义上传
|
||||
const customUploadVideo = config.customUploadVideo
|
||||
// 格式校验
|
||||
const uploadVideoAccept = config.uploadVideoAccept
|
||||
|
||||
// ------------------------------ 验证文件信息 ------------------------------
|
||||
const resultFiles: File[] = []
|
||||
const errInfos: string[] = []
|
||||
arrForEach(files, file => {
|
||||
const name = file.name
|
||||
const size = file.size / 1024 / 1024
|
||||
|
||||
// chrome 低版本 name === undefined
|
||||
if (!name || !size) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!(uploadVideoAccept instanceof Array)) {
|
||||
// 格式不是数组
|
||||
errInfos.push(`【${uploadVideoAccept}】${t('uploadVideoAccept 不是Array')}`)
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
!uploadVideoAccept.some(
|
||||
item => item === name.split('.')[name.split('.').length - 1]
|
||||
)
|
||||
) {
|
||||
// 后缀名不合法,不是视频
|
||||
errInfos.push(`【${name}】${t('不是视频')}`)
|
||||
return
|
||||
}
|
||||
|
||||
if (uploadVideoMaxSize < size) {
|
||||
// 上传视频过大
|
||||
errInfos.push(`【${name}】${t('大于')} ${uploadVideoMaxSize}M`)
|
||||
return
|
||||
}
|
||||
//验证通过的加入结果列表
|
||||
resultFiles.push(file)
|
||||
})
|
||||
// 抛出验证信息
|
||||
if (errInfos.length) {
|
||||
config.customAlert(`${t('视频验证未通过')}: \n` + errInfos.join('\n'), 'warning')
|
||||
return
|
||||
}
|
||||
// 如果过滤后文件列表为空直接返回
|
||||
if (resultFiles.length === 0) {
|
||||
config.customAlert(t('传入的文件不合法'), 'warning')
|
||||
return
|
||||
}
|
||||
|
||||
// ------------------------------ 自定义上传 ------------------------------
|
||||
if (customUploadVideo && typeof customUploadVideo === 'function') {
|
||||
customUploadVideo(resultFiles, this.insertVideo.bind(this))
|
||||
return
|
||||
}
|
||||
|
||||
// 添加视频数据
|
||||
const formData = new FormData()
|
||||
resultFiles.forEach((file: File, index: number) => {
|
||||
let name = uploadVideoName || file.name
|
||||
if (resultFiles.length > 1) {
|
||||
// 多个文件时,filename 不能重复
|
||||
name = name + (index + 1)
|
||||
}
|
||||
formData.append(name, file)
|
||||
})
|
||||
|
||||
// ------------------------------ 上传视频 ------------------------------
|
||||
|
||||
//添加自定义参数 基于有服务端地址的情况下
|
||||
if (uploadVideoServer) {
|
||||
// 添加自定义参数
|
||||
const uploadVideoServerArr = uploadVideoServer.split('#')
|
||||
uploadVideoServer = uploadVideoServerArr[0]
|
||||
const uploadVideoServerHash = uploadVideoServerArr[1] || ''
|
||||
forEach(uploadVideoParams, (key: string, val: string) => {
|
||||
// 因使用者反应,自定义参数不能默认 encode ,由 v3.1.1 版本开始注释掉
|
||||
// val = encodeURIComponent(val)
|
||||
|
||||
// 第一,将参数拼接到 url 中
|
||||
if (uploadVideoParamsWithUrl) {
|
||||
if (uploadVideoServer.indexOf('?') > 0) {
|
||||
uploadVideoServer += '&'
|
||||
} else {
|
||||
uploadVideoServer += '?'
|
||||
}
|
||||
uploadVideoServer = uploadVideoServer + key + '=' + val
|
||||
}
|
||||
|
||||
// 第二,将参数添加到 formData 中
|
||||
formData.append(key, val)
|
||||
})
|
||||
if (uploadVideoServerHash) {
|
||||
uploadVideoServer += '#' + uploadVideoServerHash
|
||||
}
|
||||
|
||||
// 开始上传
|
||||
const xhr = post(uploadVideoServer, {
|
||||
timeout: uploadVideoTimeout,
|
||||
formData,
|
||||
headers: uploadVideoHeaders,
|
||||
withCredentials: !!withVideoCredentials,
|
||||
beforeSend: xhr => {
|
||||
if (uploadVideoHooks.before)
|
||||
return uploadVideoHooks.before(xhr, editor, resultFiles)
|
||||
},
|
||||
onTimeout: xhr => {
|
||||
config.customAlert(t('上传视频超时'), 'error')
|
||||
if (uploadVideoHooks.timeout) uploadVideoHooks.timeout(xhr, editor)
|
||||
},
|
||||
onProgress: (percent, e) => {
|
||||
const progressBar = new Progress(editor)
|
||||
if (e.lengthComputable) {
|
||||
percent = e.loaded / e.total
|
||||
progressBar.show(percent)
|
||||
}
|
||||
},
|
||||
onError: xhr => {
|
||||
config.customAlert(
|
||||
t('上传视频错误'),
|
||||
'error',
|
||||
`${t('上传视频错误')},${t('服务器返回状态')}: ${xhr.status}`
|
||||
)
|
||||
if (uploadVideoHooks.error) uploadVideoHooks.error(xhr, editor)
|
||||
},
|
||||
onFail: (xhr, resultStr) => {
|
||||
config.customAlert(
|
||||
t('上传视频失败'),
|
||||
'error',
|
||||
t('上传视频返回结果错误') + `,${t('返回结果')}: ` + resultStr
|
||||
)
|
||||
if (uploadVideoHooks.fail) uploadVideoHooks.fail(xhr, editor, resultStr)
|
||||
},
|
||||
onSuccess: (xhr, result: ResType) => {
|
||||
if (uploadVideoHooks.customInsert) {
|
||||
// 自定义插入视频
|
||||
uploadVideoHooks.customInsert(this.insertVideo.bind(this), result, editor)
|
||||
return
|
||||
}
|
||||
if (result.errno != '0') {
|
||||
// 返回格式不对,应该为 { errno: 0, data: [...] }
|
||||
config.customAlert(
|
||||
t('上传视频失败'),
|
||||
'error',
|
||||
`${t('上传视频返回结果错误')},${t('返回结果')} errno=${result.errno}`
|
||||
)
|
||||
if (uploadVideoHooks.fail) uploadVideoHooks.fail(xhr, editor, result)
|
||||
return
|
||||
}
|
||||
|
||||
// 成功,插入视频
|
||||
const data = result.data
|
||||
|
||||
this.insertVideo(data.url)
|
||||
|
||||
// 钩子函数
|
||||
if (uploadVideoHooks.success) uploadVideoHooks.success(xhr, editor, result)
|
||||
},
|
||||
})
|
||||
if (typeof xhr === 'string') {
|
||||
// 上传被阻止
|
||||
config.customAlert(xhr, 'error')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 往编辑器区域插入视频
|
||||
* @param url 视频访问地址
|
||||
*/
|
||||
public insertVideo(url: string): void {
|
||||
const editor = this.editor
|
||||
const config = editor.config
|
||||
|
||||
const i18nPrefix = 'validate.'
|
||||
const t = (text: string, prefix: string = i18nPrefix): string => {
|
||||
return editor.i18next.t(prefix + text)
|
||||
}
|
||||
|
||||
// 判断用户是否自定义插入视频
|
||||
if (!config.customInsertVideo) {
|
||||
editor.cmd.do(
|
||||
'insertHTML',
|
||||
`<p><video src="${url}" controls="controls" style="max-width:100%"></video></p><p><br></p>`
|
||||
)
|
||||
} else {
|
||||
config.customInsertVideo(url)
|
||||
return
|
||||
}
|
||||
|
||||
// 加载视频
|
||||
let video: any = document.createElement('video')
|
||||
video.onload = () => {
|
||||
video = null
|
||||
}
|
||||
video.onerror = () => {
|
||||
config.customAlert(
|
||||
t('插入视频错误'),
|
||||
'error',
|
||||
`wangEditor: ${t('插入视频错误')},${t('视频链接')} "${url}",${t('下载链接失败')}`
|
||||
)
|
||||
video = null
|
||||
}
|
||||
video.onabort = () => (video = null)
|
||||
video.src = url
|
||||
}
|
||||
}
|
||||
|
||||
export default UploadVideo
|
|
@ -54,6 +54,8 @@ type TextEventHooks = {
|
|||
dropListMenuHoverEvents: (() => void)[]
|
||||
/** 点击分割线时 */
|
||||
splitLineEvents: ((e: DomElement) => void)[]
|
||||
/** 视频点击事件 */
|
||||
videoClickEvents: ((e: DomElement) => void)[]
|
||||
}
|
||||
|
||||
class Text {
|
||||
|
@ -85,6 +87,7 @@ class Text {
|
|||
menuClickEvents: [],
|
||||
dropListMenuHoverEvents: [],
|
||||
splitLineEvents: [],
|
||||
videoClickEvents: [],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -558,6 +561,27 @@ class Text {
|
|||
const enterDownEvents = eventHooks.enterDownEvents
|
||||
enterDownEvents.forEach(fn => fn(e))
|
||||
})
|
||||
|
||||
// 视频 click
|
||||
$textElem.on('click', (e: Event) => {
|
||||
// 存储视频
|
||||
let $video: DomElement | null = null
|
||||
|
||||
const target = e.target as HTMLElement
|
||||
const $target = $(target)
|
||||
|
||||
//处理视频点击 简单的video 标签
|
||||
if ($target.getNodeName() === 'VIDEO') {
|
||||
// 当前点击的就是视频
|
||||
e.stopPropagation()
|
||||
$video = $target
|
||||
}
|
||||
|
||||
if (!$video) return // 没有点击视频,则返回
|
||||
|
||||
const videoClickEvents = eventHooks.videoClickEvents
|
||||
videoClickEvents.forEach(fn => fn($video as DomElement))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/**
|
||||
* @description Video menu tooltip-event
|
||||
* @author lichunlin
|
||||
*/
|
||||
|
||||
import createEditor from '../../../helpers/create-editor'
|
||||
import $ from '../../../../src/utils/dom-core'
|
||||
import bindTooltipEvent, * as tooltipEvent from '../../../../src/menus/video/bind-event/tooltip-event'
|
||||
|
||||
describe('Video menu tooltip-event', () => {
|
||||
test('绑定 tooltip-event 事件', () => {
|
||||
const editor = createEditor(document, 'div1')
|
||||
|
||||
bindTooltipEvent(editor)
|
||||
|
||||
expect(editor.txt.eventHooks.videoClickEvents.length).toBeGreaterThanOrEqual(1)
|
||||
})
|
||||
|
||||
test('调用 createShowHideFn 函数返回显示和隐藏tooltip方法', () => {
|
||||
const editor = createEditor(document, 'div2')
|
||||
const fns = tooltipEvent.createShowHideFn(editor)
|
||||
|
||||
expect(fns.showVideoTooltip instanceof Function).toBeTruthy()
|
||||
expect(fns.hideVideoTooltip instanceof Function).toBeTruthy()
|
||||
})
|
||||
|
||||
test('绑定 tooltip-event 事件,执行视频点击事件会展示tooltip', () => {
|
||||
const editor = createEditor(document, 'div4')
|
||||
|
||||
bindTooltipEvent(editor)
|
||||
|
||||
const videoClickEvents = editor.txt.eventHooks.videoClickEvents
|
||||
|
||||
videoClickEvents.forEach(fn => {
|
||||
fn($(editor.$textElem))
|
||||
})
|
||||
|
||||
expect($('.w-e-tooltip').elems[0]).not.toBeUndefined()
|
||||
expect($('.w-e-tooltip').elems[0].childNodes.length).toBe(5)
|
||||
})
|
||||
|
||||
test('绑定 tooltip-event 事件,执行视频之外的其它点击事件会隐藏tooltip', () => {
|
||||
const editor = createEditor(document, 'div5')
|
||||
|
||||
bindTooltipEvent(editor)
|
||||
|
||||
const videoClickEvents = editor.txt.eventHooks.videoClickEvents
|
||||
const clickEvents = editor.txt.eventHooks.clickEvents
|
||||
|
||||
videoClickEvents.forEach(fn => {
|
||||
fn($('<div></div>'))
|
||||
})
|
||||
|
||||
clickEvents.forEach(fn => {
|
||||
// @ts-ignore
|
||||
fn()
|
||||
})
|
||||
|
||||
expect($('#div5 .w-e-tooltip').elems[0]).toBeUndefined()
|
||||
})
|
||||
})
|
|
@ -0,0 +1,455 @@
|
|||
/**
|
||||
* @description upload-video test
|
||||
* @author lichunlin
|
||||
*/
|
||||
import createEditor from '../../../helpers/create-editor'
|
||||
import mockCmdFn from '../../../helpers/command-mock'
|
||||
import mockFile from '../../../helpers/mock-file'
|
||||
import mockXHR from '../../../helpers/mock-xhr'
|
||||
import Editor from '../../../../src/editor'
|
||||
import UploadVideo from '../../../../src/menus/video/upload-video'
|
||||
|
||||
let editor: Editor
|
||||
let id = 1
|
||||
|
||||
const videoUrl =
|
||||
'https://stream7.iqilu.com/10339/upload_transcode/202002/18/20200218114723HDu3hhxqIT.mp4'
|
||||
const uploadVideoServer = 'http://localhost:8881/api/upload-video'
|
||||
|
||||
const defaultRes = {
|
||||
status: 200,
|
||||
res: JSON.stringify({ data: { url: '' }, errno: 0 }),
|
||||
}
|
||||
|
||||
const mockXHRHttpRequest = (res: any = defaultRes) => {
|
||||
const mockXHRObject = mockXHR(res)
|
||||
|
||||
const mockObject = jest.fn().mockImplementation(() => mockXHRObject)
|
||||
|
||||
// @ts-ignore
|
||||
window.XMLHttpRequest = mockObject
|
||||
|
||||
return mockXHRObject
|
||||
}
|
||||
|
||||
const createUploadVideoInstance = (config: any) => {
|
||||
const editor = createEditor(document, `div${id++}`, '', config)
|
||||
const uploadVideo = new UploadVideo(editor)
|
||||
return uploadVideo
|
||||
}
|
||||
|
||||
const mockSupportCommand = () => {
|
||||
mockCmdFn(document)
|
||||
document.queryCommandSupported = jest.fn(() => true)
|
||||
}
|
||||
|
||||
const deaultFiles = [{ name: '测试.mp4', size: 512, mimeType: 'video/mp4' }]
|
||||
const createMockFilse = (fileList: any[] = deaultFiles) => {
|
||||
const files = fileList.map(file => mockFile(file))
|
||||
return files.filter(Boolean)
|
||||
}
|
||||
|
||||
describe('upload video', () => {
|
||||
beforeEach(() => {
|
||||
editor = createEditor(document, `div${id++}`)
|
||||
})
|
||||
|
||||
test('能够初始化基本的UploadVideo类', () => {
|
||||
const uploadVideo = new UploadVideo(editor)
|
||||
|
||||
expect(uploadVideo.insertVideo instanceof Function).toBeTruthy()
|
||||
expect(uploadVideo.insertVideo instanceof Function).toBeTruthy()
|
||||
})
|
||||
|
||||
test('调用 insertVideo 可以网编辑器里插入视频', () => {
|
||||
const uploadVideo = new UploadVideo(editor)
|
||||
|
||||
mockSupportCommand()
|
||||
|
||||
uploadVideo.insertVideo(videoUrl)
|
||||
|
||||
expect(document.execCommand).toBeCalledWith(
|
||||
'insertHTML',
|
||||
false,
|
||||
`<p><video src="${videoUrl}" controls="controls" style="max-width:100%"></video></p><p><br></p>`
|
||||
)
|
||||
})
|
||||
|
||||
test('调用 insertVideo 可以网编辑器里插入视频,可以监听插入视频回调', () => {
|
||||
const callback = jest.fn()
|
||||
|
||||
const uploadVideo = createUploadVideoInstance({
|
||||
linkVideoCallback: callback,
|
||||
})
|
||||
|
||||
mockSupportCommand()
|
||||
|
||||
uploadVideo.insertVideo(videoUrl)
|
||||
|
||||
expect(document.execCommand).toBeCalledWith(
|
||||
'insertHTML',
|
||||
false,
|
||||
`<p><video src="${videoUrl}" controls="controls" style="max-width:100%"></video></p><p><br></p>`
|
||||
)
|
||||
})
|
||||
|
||||
test('调用 uploadVideo 上传视频', done => {
|
||||
expect.assertions(1)
|
||||
|
||||
const jestFn = jest.fn()
|
||||
|
||||
const upload = createUploadVideoInstance({
|
||||
uploadVideoServer,
|
||||
uploadVideoHooks: {
|
||||
success: jestFn,
|
||||
},
|
||||
})
|
||||
|
||||
const files = createMockFilse()
|
||||
const mockXHRObject = mockXHRHttpRequest()
|
||||
|
||||
upload.uploadVideo(files)
|
||||
|
||||
mockXHRObject.onreadystatechange()
|
||||
|
||||
setTimeout(() => {
|
||||
expect(jestFn).toBeCalled()
|
||||
done()
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
test('调用 uploadVideo 上传视频,如果传入的文件为空直接返回', () => {
|
||||
const upload = new UploadVideo(editor)
|
||||
|
||||
const res = upload.uploadVideo([])
|
||||
|
||||
expect(res).toBeUndefined()
|
||||
})
|
||||
|
||||
test('调用 uploadVideo 上传视频,如果没有配置customUploadVideo, 则必须配置 uploadVideoServer ', () => {
|
||||
const upload = new UploadVideo(editor)
|
||||
const files = createMockFilse()
|
||||
|
||||
const res = upload.uploadVideo(files)
|
||||
|
||||
expect(res).toBeUndefined()
|
||||
})
|
||||
|
||||
test('调用 uploadVideo 上传视频,如果文件没有名字或者size为,则会被过滤掉', () => {
|
||||
const fn = jest.fn()
|
||||
|
||||
const upload = createUploadVideoInstance({
|
||||
uploadVideoServer,
|
||||
customAlert: fn,
|
||||
})
|
||||
|
||||
const files = createMockFilse([{ name: '', size: 0, mimeType: 'video/mp4' }])
|
||||
|
||||
const res = upload.uploadVideo(files)
|
||||
|
||||
expect(res).toBeUndefined()
|
||||
expect(fn).toBeCalledWith('传入的文件不合法', 'warning')
|
||||
})
|
||||
|
||||
test('调用 uploadVideo 上传视频,如果文件非视频,则返回并提示错误信息', () => {
|
||||
const fn = jest.fn()
|
||||
|
||||
const upload = createUploadVideoInstance({
|
||||
uploadVideoServer,
|
||||
customAlert: fn,
|
||||
})
|
||||
|
||||
const files = createMockFilse([{ name: 'test.txt', size: 200, mimeType: 'text/plain' }])
|
||||
|
||||
const res = upload.uploadVideo(files)
|
||||
|
||||
expect(res).toBeUndefined()
|
||||
expect(fn).toBeCalledWith('视频验证未通过: \n【test.txt】不是视频', 'warning')
|
||||
})
|
||||
|
||||
test('调用 uploadVideo 上传视频,如果文件体积大小超过配置的大小,则返回并提示错误信息', () => {
|
||||
const fn = jest.fn()
|
||||
|
||||
const upload = createUploadVideoInstance({
|
||||
uploadVideoServer,
|
||||
uploadVideoMaxSize: 5 * 1024 * 1024,
|
||||
customAlert: fn,
|
||||
})
|
||||
|
||||
const files = createMockFilse([
|
||||
{ name: 'test.mp4', size: 6 * 1024 * 1024, mimeType: 'video/mp4' },
|
||||
])
|
||||
|
||||
const res = upload.uploadVideo(files)
|
||||
|
||||
expect(res).toBeUndefined()
|
||||
expect(fn).toBeCalledWith(`视频验证未通过: \n【test.mp4】大于 5M`, 'warning')
|
||||
})
|
||||
|
||||
test('调用 uploadVideo 上传视频,如果配置了 customUploadVideo 选项,则调用customUploadVideo上传', () => {
|
||||
const fn = jest.fn()
|
||||
|
||||
const upload = createUploadVideoInstance({
|
||||
uploadVideoServer,
|
||||
customUploadVideo: fn,
|
||||
})
|
||||
|
||||
const files = createMockFilse()
|
||||
|
||||
const res = upload.uploadVideo(files)
|
||||
|
||||
expect(res).toBeUndefined()
|
||||
expect(fn).toBeCalled()
|
||||
})
|
||||
|
||||
test('调用 uploadVideo 上传视频,如果可以配置uploadVideoParamsWithUrl添加query参数', done => {
|
||||
expect.assertions(1)
|
||||
|
||||
const fn = jest.fn()
|
||||
|
||||
const upload = createUploadVideoInstance({
|
||||
uploadVideoServer,
|
||||
uploadVideoParams: {
|
||||
a: 'a',
|
||||
b: 'b',
|
||||
},
|
||||
uploadVideoParamsWithUrl: true,
|
||||
uploadVideoHooks: {
|
||||
success: fn,
|
||||
},
|
||||
})
|
||||
|
||||
const files = createMockFilse()
|
||||
|
||||
const mockXHRObject = mockXHRHttpRequest()
|
||||
|
||||
upload.uploadVideo(files)
|
||||
|
||||
mockXHRObject.onreadystatechange()
|
||||
|
||||
setTimeout(() => {
|
||||
expect(fn).toBeCalled()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
test('调用 uploadVideo 上传视频,uploadVideoServer支持hash参数拼接', done => {
|
||||
expect.assertions(1)
|
||||
|
||||
const fn = jest.fn()
|
||||
|
||||
const upload = createUploadVideoInstance({
|
||||
uploadVideoServer,
|
||||
uploadVideoParams: {
|
||||
a: 'a',
|
||||
b: 'b',
|
||||
},
|
||||
uploadVideoParamsWithUrl: true,
|
||||
uploadVideoHooks: {
|
||||
success: fn,
|
||||
},
|
||||
})
|
||||
|
||||
const files = createMockFilse([
|
||||
{ name: 'test1.mp4', size: 2048, mimeType: 'video/mp4' },
|
||||
{ name: 'test2.mp4', size: 2048, mimeType: 'video/mp4' },
|
||||
])
|
||||
|
||||
const mockXHRObject = mockXHRHttpRequest()
|
||||
|
||||
upload.uploadVideo(files)
|
||||
|
||||
mockXHRObject.onreadystatechange()
|
||||
|
||||
setTimeout(() => {
|
||||
expect(fn).toBeCalled()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
test('调用 uploadVideo 上传视频失败,会有错误提示,并支持配置onError hook', done => {
|
||||
expect.assertions(2)
|
||||
|
||||
const fn = jest.fn()
|
||||
const alertFn = jest.fn()
|
||||
|
||||
const upload = createUploadVideoInstance({
|
||||
uploadVideoServer,
|
||||
uploadVideoHooks: {
|
||||
error: fn,
|
||||
},
|
||||
customAlert: alertFn,
|
||||
})
|
||||
const files = createMockFilse()
|
||||
|
||||
const mockXHRObject = mockXHRHttpRequest({ status: 500 })
|
||||
|
||||
upload.uploadVideo(files)
|
||||
|
||||
mockXHRObject.onreadystatechange()
|
||||
|
||||
setTimeout(() => {
|
||||
expect(fn).toBeCalled()
|
||||
expect(alertFn).toBeCalledWith(
|
||||
'上传视频错误',
|
||||
'error',
|
||||
'上传视频错误,服务器返回状态: 500'
|
||||
)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
test('调用 uploadVideo 上传视频后数据返回不正常,会有错误提示,并支持配置onFail hook', done => {
|
||||
expect.assertions(2)
|
||||
|
||||
const fn = jest.fn()
|
||||
const alertFn = jest.fn()
|
||||
|
||||
const upload = createUploadVideoInstance({
|
||||
uploadVideoServer,
|
||||
uploadVideoHooks: {
|
||||
fail: fn,
|
||||
},
|
||||
customAlert: alertFn,
|
||||
})
|
||||
const files = createMockFilse()
|
||||
|
||||
const mockXHRObject = mockXHRHttpRequest({
|
||||
status: 200,
|
||||
res: '{test: 123}',
|
||||
})
|
||||
|
||||
upload.uploadVideo(files)
|
||||
|
||||
mockXHRObject.onreadystatechange()
|
||||
|
||||
setTimeout(() => {
|
||||
expect(fn).toBeCalled()
|
||||
expect(alertFn).toBeCalledWith(
|
||||
'上传视频失败',
|
||||
'error',
|
||||
'上传视频返回结果错误,返回结果: {test: 123}'
|
||||
)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
test('调用 uploadVideo 上传视频成功后,支持自定义插入视频函数', done => {
|
||||
expect.assertions(1)
|
||||
|
||||
const insertFn = jest.fn()
|
||||
|
||||
const upload = createUploadVideoInstance({
|
||||
uploadVideoServer,
|
||||
uploadVideoHooks: {
|
||||
customInsert: insertFn,
|
||||
},
|
||||
})
|
||||
|
||||
const files = createMockFilse()
|
||||
|
||||
const mockXHRObject = mockXHRHttpRequest()
|
||||
|
||||
upload.uploadVideo(files)
|
||||
|
||||
mockXHRObject.onreadystatechange()
|
||||
|
||||
setTimeout(() => {
|
||||
expect(insertFn).toBeCalled()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
test('调用 uploadVideo 上传被阻止,会有错误提示', done => {
|
||||
expect.assertions(2)
|
||||
|
||||
const beforFn = jest.fn(() => ({ prevent: true, msg: '阻止发送请求' }))
|
||||
const alertFn = jest.fn()
|
||||
|
||||
const upload = createUploadVideoInstance({
|
||||
uploadVideoServer,
|
||||
uploadVideoHooks: {
|
||||
before: beforFn,
|
||||
},
|
||||
customAlert: alertFn,
|
||||
})
|
||||
|
||||
const files = createMockFilse()
|
||||
|
||||
const mockXHRObject = mockXHRHttpRequest()
|
||||
|
||||
upload.uploadVideo(files)
|
||||
|
||||
mockXHRObject.onreadystatechange()
|
||||
|
||||
setTimeout(() => {
|
||||
expect(beforFn).toBeCalled()
|
||||
expect(alertFn).toBeCalledWith('阻止发送请求', 'error')
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
test('调用 uploadVideo 上传返回的错误码不符合条件会有错误提示,并触发fail回调', done => {
|
||||
expect.assertions(2)
|
||||
|
||||
const failFn = jest.fn()
|
||||
const alertFn = jest.fn()
|
||||
|
||||
const upload = createUploadVideoInstance({
|
||||
uploadVideoServer,
|
||||
uploadVideoHooks: {
|
||||
fail: failFn,
|
||||
},
|
||||
customAlert: alertFn,
|
||||
})
|
||||
|
||||
const files = createMockFilse()
|
||||
|
||||
const mockXHRObject = mockXHRHttpRequest({
|
||||
status: 200,
|
||||
res: { test: 123, errno: -1 },
|
||||
})
|
||||
|
||||
upload.uploadVideo(files)
|
||||
|
||||
mockXHRObject.onreadystatechange()
|
||||
|
||||
setTimeout(() => {
|
||||
expect(failFn).toBeCalled()
|
||||
expect(alertFn).toBeCalledWith(
|
||||
'上传视频失败',
|
||||
'error',
|
||||
'上传视频返回结果错误,返回结果 errno=-1'
|
||||
)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
test('调用 uploadVideo 上传超时会触发超时回调', done => {
|
||||
expect.assertions(2)
|
||||
|
||||
const timeoutFn = jest.fn()
|
||||
const alertFn = jest.fn()
|
||||
|
||||
const upload = createUploadVideoInstance({
|
||||
uploadVideoServer,
|
||||
uploadVideoHooks: {
|
||||
timeout: timeoutFn,
|
||||
},
|
||||
customAlert: alertFn,
|
||||
})
|
||||
|
||||
const files = createMockFilse()
|
||||
const mockXHRObject = mockXHRHttpRequest()
|
||||
|
||||
upload.uploadVideo(files)
|
||||
|
||||
mockXHRObject.ontimeout()
|
||||
|
||||
setTimeout(() => {
|
||||
expect(timeoutFn).toBeCalled()
|
||||
expect(alertFn).toBeCalledWith('上传视频超时', 'error')
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue