This repository has been archived on 2023-08-30. You can view files and clone it, but cannot push or open issues/pull-requests.
bytedo
/
wcui
Archived
1
0
Fork 0

layer & meditor这2个组件增加中英语言包;meditor增加node-webkit版本;优化marked解析混合html的文本

old
宇天 2018-08-14 01:50:10 +08:00
parent ab4ad90cd0
commit d7d653ff0b
8 changed files with 750 additions and 144 deletions

View File

@ -86,6 +86,14 @@
}
&:active {background:nth($cp, 2)}
}
.do-meditor__input {width:100%;height:40px;padding:0 10px;background:nth($cp, 1);border:2px solid transparent;border-radius:5px;font-size:13px;@include ts();color: nth($cd, 2);
&.area {height:120px;padding:5px 10px;resize:none;outline:none;}
&:focus {background:#fff;border-color:nth($cd, 2);}
&::-webkit-input-placeholder {color:nth($cp, 3);}
}
/* 关于编辑器模块*/
@ -128,7 +136,6 @@
section {width:100%;height: 40px;margin:10px 0;line-height:40px;
.txt {width:100%;height:40px;padding:0 10px;border:0;border-radius:5px;background:nth($cp, 1);color:nth($cd, 2);font-size:14px;}
.label {float: left;width:50%;}
.submit {float:right;width:30%;}
}
@ -137,16 +144,18 @@
.do-meditor-codeblock {width:480px;height:auto;
section {display:block;width:100%;height:auto;margin:10px 0;line-height:35px;
.label {float: left;width:80px;}
select {float:left;width:200px;height:35px;padding:0 30px 0 10px;border:0;border-radius:0;border-bottom:1px solid nth($cp, 3);background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAMAAABV0m3JAAAADFBMVEUAAAD///+Pj4+JiYkxcGihAAAABHRSTlMAABBwqVQF9wAAADNJREFUeNqlzjEOACAMw8DQ/v/PSE5FFhaEx5usdekBuzRVH0RtCqJYELUFrVjQigX/5jdvzgDh9izlMQAAAABJRU5ErkJggg==) no-repeat right 12px;color:nth($cd, 2);outline:none;appearance: none;@include ts;
&::-ms-expand {display:none;}
.select {position:relative;width:200px;height:35px;color:nth($cgr, 1);
&:focus {box-shadow:0 0 5px nth($ct, 1)}
}
textarea {width:100%;height:120px;padding:5px 10px;border:0;border-radius:5px;background:nth($cp, 1);font-size:14px;resize:none;outline:none;color:nth($cd, 2);
&:focus {box-shadow:0 0 5px nth($ct, 1)}
select {width:100%;height:100%;padding:5px 13px;line-height:1;background:nth($cp, 1);border-radius:5px;appearance:none;border:2px solid transparent;outline:none;color: nth($cd, 2);font-size:13px;
&:focus {background:#fff;border-color:nth($cd, 2);}
&::-ms-expand {display:none;}
&:disabled {border-color:transparent;background:#fff8ed;color:nth($cp, 3)}
}
.trigon {position:absolute;right:7px;top:0;width:15px;height:35px;padding:7px 0;font-size:12px;text-align:center;
i {float:left;width:15px;height:12px;line-height:12px;}
i:nth-child(2) {margin-top:-6px;}
}
}
.submit {float:right;width:80px;}
}

View File

@ -34,7 +34,6 @@
.section {display:block;width:100%;height:auto;margin:15px 0;line-height:35px;
.txt {width:100%;height:45px;padding:0 10px;border:0;border-radius:5px;background:nth($cp, 1);color:nth($cd, 2);font-size:14px;}
.submit {float:right;width:30%;height:45px;line-height:45px;}
}
@ -52,9 +51,8 @@
.upload-box {width:100%;height:auto;min-height:255px;padding-top:10px;
.thead {width:100%;height:35px;line-height:35px;background:nth($cp, 1);}
.thead .col {text-align:center;}
.col {float:left;height:30px;padding:0 5px;}
.col {overflow:hidden;float:left;height:30px;padding:0 5px;text-align:center;}
.col:nth-child(1) {width:50%}
.col:nth-child(2) {width:35%}
.col:nth-child(3) {width:15%}

View File

@ -12,6 +12,31 @@ import 'css/layer-normal.scss'
Anot.ui.layer = '1.0.0-normal'
const LANGUAGES = {
en: {
TITLE: 'Dialog',
YES_BTN: 'OK',
NO_BTN: 'Cancel',
ERROR: 'The layer instance is not exists',
NEED_CONTAINER: 'layer "tips" require a DOM object as container'
},
zh: {
TITLE: '提示',
YES_BTN: '确定',
NO_BTN: '取消',
ERROR: '要关闭的layer实例不存在',
NEED_CONTAINER: 'tips类型需要指定一个元素节点作为容器'
},
'zh-TW': {
TITLE: '提示',
YES_BTN: '確定',
NO_BTN: '取消',
ERROR: '要關閉的layer實例不存在',
NEED_CONTAINER: 'tips类型需要指定一個元素節點作爲容器'
}
}
LANGUAGES['zh-CN'] = LANGUAGES.zh
const lang = LANGUAGES[Anot.language || navigator.language || 'en']
let layerDom = {}
let layerObj = {}
let unique = null // 储存当前打开的1/2/3类型的弹窗
@ -24,13 +49,13 @@ let defconf = {
maskColor: null, // 遮罩背景色
radius: '0px', // 弹窗圆角半径
area: ['auto', 'auto'],
title: '提示', // 弹窗主标题(在工具栏上的)
title: lang.TITLE, // 弹窗主标题(在工具栏上的)
menubar: true, // 是否显示菜单栏
content: '', // 弹窗的内容
fixed: false, // 是否固定不可拖拽
shift: 'cc', // 弹窗出来的初始位置,用于出场动画
offset: [], // 弹窗出来后的坐标, 为数组,可有4个值,依次是 上右下左
btns: ['确定', '取消'] // 弹窗的2个按钮的文字
btns: [lang.YES_BTN, lang.NO_BTN] // 弹窗的2个按钮的文字
}
const $doc = Anot(document)
const uuid = function() {
@ -38,7 +63,7 @@ const uuid = function() {
}
const close = function(id) {
if (typeof id !== 'string' && typeof id !== 'number') {
return Anot.error('要关闭的layer实例不存在')
return Anot.error(lang.ERROR)
}
if (/^layerwrap\-/.test(id) || layerObj['layerwrap-' + id]) {
try {
@ -626,7 +651,7 @@ const _layer = {
},
tips(content, container, opt = {}) {
if (!(container instanceof HTMLElement)) {
return Anot.error('layer "tips" require a DOM object')
return Anot.error(lang.NEED_CONTAINER)
}
if (!opt.background) {
@ -669,7 +694,7 @@ const _layer = {
if (typeof opt === 'string') {
opt = 'layerwrap-' + opt
if (!layerObj[opt]) {
throw new Error('layer实例不存在')
throw new Error(lang.ERROR)
} else {
//只能显示一个实例
if (layerObj[opt].show) {
@ -813,7 +838,6 @@ Anot.directive('layer', {
tips.style.visibility = 'hidden'
}, 100)
})
// _layer.tips(val, this.element)
}
}
})

View File

@ -726,7 +726,9 @@ InlineLexer.prototype.output = function(src) {
// br
if ((cap = this.rules.br.exec(src))) {
src = src.substring(cap[0].length)
out += this.renderer.br()
if (!/<[\/]?([a-z0-9\-])+[^>]*>/.test(src)) {
out += this.renderer.br()
}
continue
}
@ -921,25 +923,24 @@ Renderer.prototype.listitem = function(text) {
}
Renderer.prototype.paragraph = function(text) {
text = text.replace(/<br>/g, '').replace(/<p><\/p>/g, '')
return '<p>' + text + '</p>'
}
Renderer.prototype.table = function(header, body) {
return (
'<table>\n' +
'<thead>\n' +
'<table>' +
'<thead>' +
header +
'</thead>\n' +
'<tbody>\n' +
'</thead>' +
'<tbody>' +
body +
'</tbody>\n' +
'</table>\n'
'</tbody>' +
'</table>'
)
}
Renderer.prototype.tablerow = function(content) {
return '<tr>\n' + content + '</tr>\n'
return '<tr>' + content + '</tr>'
}
Renderer.prototype.tablecell = function(content, flags) {
@ -947,7 +948,7 @@ Renderer.prototype.tablecell = function(content, flags) {
var tag = flags.align
? '<' + type + ' style="text-align:' + flags.align + '">'
: '<' + type + '>'
return tag + content + '</' + type + '>\n'
return tag + content + '</' + type + '>'
}
// span level renderer
@ -1016,7 +1017,7 @@ Renderer.prototype.image = function(href, title, text) {
}
Renderer.prototype.text = function(text) {
return text.trim()
return text
}
/**

View File

@ -0,0 +1,371 @@
/**
*
* @authors yutent (yutent@doui.cc)
* @date 2017-04-19 21:17:26
*
*/
'use strict'
import '../../layer/index'
import 'css/meditor__attach.scss'
const $doc = Anot(document)
const LANGUAGES = {
zh: {
IMAGE: {
REMOTE: '远程图片',
LOCAL: '本地上传',
MANAGE: '图片管理',
ALT: '图片描述',
ADDRESS: '图片地址'
},
FILE: {
REMOTE: '远程附件',
LOCAL: '本地上传',
MANAGE: '附件管理',
ALT: '附件描述',
ADDRESS: '附件地址'
},
BTN: '确定',
INSERT: '插入',
CHOOSE: '选择文件',
LIMIT: '上传大小限制:单文件最大 ',
SCREENSHOT: '截图',
COMPRESS: '截图处理中...',
TABLE: {
NAME: '文件名',
PROGRESS: '上传进度',
HANDLE: '操作'
},
ERROR: {
TYPE: '文件类型错误',
SIZE: '文件体积过大',
EMPTY: '描述和地址不能为空',
UNDEFINED: '在node-webkit中saveAttach回调必须定义'
}
},
en: {
IMAGE: {
REMOTE: 'Remote image',
LOCAL: 'Local image',
MANAGE: 'Manage',
ALT: 'Image alt text',
ADDRESS: 'Image address'
},
FILE: {
REMOTE: 'Remote file',
LOCAL: 'Local file',
MANAGE: 'Manage',
ALT: 'File alt text',
ADDRESS: 'File address'
},
BTN: 'OK',
INSERT: 'insert',
CHOOSE: 'Choose file',
LIMIT: 'Size of upload file limit to ',
SCREENSHOT: 'screenshot',
COMPRESS: 'Screenshot compressing...',
TABLE: {
NAME: 'name',
PROGRESS: 'progress',
HANDLE: 'handle'
},
ERROR: {
TYPE: 'Forbidden type',
SIZE: 'Too large',
EMPTY: 'Alt text and address can not be null',
UNDEFINED: 'Function saveAttach is not defined'
}
}
}
LANGUAGES['zh-CN'] = LANGUAGES.zh
LANGUAGES['zh-TW'] = LANGUAGES.zh
const lang = LANGUAGES[Anot.language || navigator.language || 'en']
const fixCont = function(vm, tool) {
let limit = false
if (vm.props.uploadSizeLimit) {
limit = (vm.props.uploadSizeLimit / (1024 * 1024)).toFixed(2)
}
return `
<dl class="do-meditor-attach">
<dt class="tab-box" :drag="do-layer" data-limit="window">
<span class="item" :class="active:tab === 1" :click="switchTab(1)">
${lang[tool].REMOTE}
</span>
<span class="item" :class="active:tab === 2" :click="switchTab(2)">
${lang[tool].LOCAL}
</span>
<span class="item" :class="active:tab === 3" :click="switchTab(3)">
${lang[tool].MANAGE}
</span>
</dt>
<dd class="cont-box">
<div class="remote" :visible="tab === 1">
<section class="section do-fn-cl">
<input
class="do-meditor__input"
:duplex="attachAlt"
placeholder="${lang[tool].ALT}" />
</section>
<section class="section do-fn-cl">
<input
class="do-meditor__input"
:duplex="attach"
placeholder="${lang[tool].ADDRESS}" />
</section>
<section class="section do-fn-cl">
<a
href="javascript:;"
class="do-meditor__button submit"
:click="confirm">${lang.BTN}</a>
</section>
</div>
<div class="local" :visible="tab === 2">
<div class="select-file">
<input ref="attach" multiple :change="change" type="file" class="hide" />
<span class="file" :click="select">${lang.CHOOSE}</span>
${limit ? `<span class="tips">(${lang.LIMIT + limit} MB)</span>` : ''}
</div>
<ul class="upload-box">
<li class="thead">
<span class="col">${lang.TABLE.NAME}</span>
<span class="col">${lang.TABLE.PROGRESS}</span>
<span class="col">${lang.TABLE.HANDLE}</span>
</li>
<li class="tbody">
<p :for="uploadQueue">
<span
class="col do-fn-ell"
:text="el.name"
:layer-tips="el.name"></span>
<span class="col" :html="el.progress"></span>
<span class="col"><a class="insert" :click="insert(el)">${
lang.INSERT
}</a></span>
</p>
</li>
</ul>
</div>
<div class="manager" :visible="tab === 3">
<ul class="list-box">
<li
class="item"
:for="attachList"
:layer-tips="el.name"
:click="insert(el)">
<span class="thumb" :html="el.thumb"></span>
<p class="name" :text="el.name"></p>
</li>
</ul>
</div>
</dd>
</dl>`
}
/**
* [uploadFile 文件上传]
* @param {[type]} vm [vm对象]
* @param {[type]} tool [image/file]
*/
function uploadFile(vm, tool) {
for (let it of this.files) {
let ext = it.name.slice(it.name.lastIndexOf('.'))
if (tool === 'IMAGE' && !/^\.(jpg|jpeg|png|gif|bmp|webp|ico)$/.test(ext)) {
this.uploadQueue.push({
name: it.name,
progress: '<span class="red">0%(' + lang.ERROR.TYPE + ')</span>',
url: ''
})
continue
}
if (vm.props.uploadSizeLimit && it.size > vm.props.uploadSizeLimit) {
this.uploadQueue.push({
name: it.name,
progress: '<span class="red">0%(' + lang.ERROR.SIZE + ')</span>',
url: ''
})
continue
}
let fixName = new Date().format('YmdHis') + ext
let attach = { name: it.name, fixName, progress: '100%', url: '' }
if (vm.props.saveAttach) {
vm.props
.saveAttach(attach, it)
.then(url => {
attach.url = url
this.uploadQueue.push(attach)
})
.catch(err => {
Anot.error(err)
})
} else {
layer.toast(lang.ERROR.UNDEFINED, 'error')
}
}
}
function uploadScreenshot(vm, blob) {
let name = new Date().format('YmdHis') + '.jpg'
let attach = { name, url: '' }
if (vm.props.saveAttach) {
vm.props
.saveAttach(attach, blob)
.then(url => {
vm.insert(`![${lang.SCREENSHOT}](${url})`)
})
.catch(err => {
Anot.error(err)
})
} else {
layer.toast(lang.ERROR.UNDEFINED, 'error')
}
}
function showDialog(elem, vm, tool) {
let offset = Anot(elem).offset()
layer.open({
type: 7,
menubar: false,
fixed: true,
maskClose: true,
offset: [offset.top + 35 - $doc.scrollTop()],
shift: {
top: offset.top - $doc.scrollTop()
},
tab: 2,
attach: '',
attachAlt: '',
uploadQueue: [], //当前上传的列表
attachList: [], //附件管理列表
switchTab(id) {
this.tab = id
if (id === 3) {
this.attachList.clear()
if (vm.props.getAttachList) {
vm.props
.getAttachList(tool)
.then(list => {
list.forEach(it => {
let ext = it.name.slice(it.name.lastIndexOf('.'))
it.isImage = /^\.(jpg|jpeg|png|gif|bmp|webp|ico)$/.test(ext)
it.thumb = it.isImage
? `<img src="${it.url}" />`
: `<em class="do-icon-txt"></em>`
})
return list
})
.then(list => {
list = list.filter(it => {
if (tool === 'IMAGE') {
return it.isImage
}
return true
})
this.attachList = list
})
}
}
},
select() {
let ev = document.createEvent('MouseEvent')
ev.initEvent('click', false, false)
this.$refs.attach.dispatchEvent(ev)
},
change(ev) {
this.files = ev.target.files
uploadFile.call(this, vm, tool)
},
insert: function(it) {
if (!it.url) {
return
}
let val = `\n${tool === 'IMAGE' ? '!' : ''}[${it.name}](${it.url})`
vm.insert(val)
},
confirm: function() {
if (!this.attach || !this.attachAlt) {
return layer.toast(lang.ERROR.EMPTY, 'error')
}
let val = `\n${tool === 'IMAGE' ? '!' : ''}[${this.attachAlt}](${
this.attach
})`
vm.insert(val)
this.close()
},
content: fixCont(vm, tool)
})
}
const plugin = {
__init__(ME) {
Object.assign(ME.vm.addon, {
attach(elem) {
showDialog(elem, this, 'FILE')
},
image(elem) {
showDialog(elem, this, 'IMAGE')
}
})
ME.vm.$refs.editor.addEventListener('paste', function(ev) {
ev.preventDefault()
let txt = ev.clipboardData.getData('text/plain')
//文本类型直接默认处理
if (txt) {
return
}
if (ev.clipboardData.items) {
let items = ev.clipboardData.items
let len = items.length
let blob = null
for (let it of items) {
if (it.type.indexOf('image') > -1) {
blob = it.getAsFile()
}
}
if (blob !== null) {
layer.toast(lang.COMPRESS)
// 压缩截图,避免文件过大
let reader = new FileReader()
reader.onload = function() {
let img = document.createElement('img')
let canvas = document.createElement('canvas')
img.onload = function() {
canvas.width = img.width
canvas.height = img.height
let ctx = canvas.getContext('2d')
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.drawImage(this, 0, 0, canvas.width, canvas.height)
canvas.toBlob(
obj => {
uploadScreenshot(ME.vm, obj)
},
'image/jpeg',
0.8
)
}
img.src = this.result
}
reader.readAsDataURL(blob)
}
}
})
}
}
export default plugin

View File

@ -11,10 +11,75 @@ import '../../layer/index'
import 'css/meditor__attach.scss'
const $doc = Anot(document)
const LANG = {
image: ['远程图片', '图片管理', '图片描述', '图片地址'],
file: ['远程附件', '附件管理', '附件描述', '附件地址']
const LANGUAGES = {
zh: {
IMAGE: {
REMOTE: '远程图片',
LOCAL: '本地上传',
MANAGE: '图片管理',
ALT: '图片描述',
ADDRESS: '图片地址'
},
FILE: {
REMOTE: '远程附件',
LOCAL: '本地上传',
MANAGE: '附件管理',
ALT: '附件描述',
ADDRESS: '附件地址'
},
BTN: '确定',
INSERT: '插入',
CHOOSE: '选择文件',
LIMIT: '上传大小限制:单文件最大 ',
SCREENSHOT: '截图',
COMPRESS: '截图处理中...',
TABLE: {
NAME: '文件名',
PROGRESS: '上传进度',
HANDLE: '操作'
},
ERROR: {
TYPE: '文件类型错误',
SIZE: '文件体积过大',
EMPTY: '描述和地址不能为空'
}
},
en: {
IMAGE: {
REMOTE: 'Remote image',
LOCAL: 'Local image',
MANAGE: 'Manage',
ALT: 'Image alt text',
ADDRESS: 'Image address'
},
FILE: {
REMOTE: 'Remote file',
LOCAL: 'Local file',
MANAGE: 'Manage',
ALT: 'File alt text',
ADDRESS: 'File address'
},
BTN: 'OK',
INSERT: 'insert',
CHOOSE: 'Choose file',
LIMIT: 'Size of upload file limit to ',
SCREENSHOT: 'screenshot',
COMPRESS: 'Screenshot compressing...',
TABLE: {
NAME: 'name',
PROGRESS: 'progress',
HANDLE: 'handle'
},
ERROR: {
TYPE: 'Forbidden type',
SIZE: 'Too large',
EMPTY: 'Alt text and address can not be null'
}
}
}
LANGUAGES['zh-CN'] = LANGUAGES.zh
LANGUAGES['zh-TW'] = LANGUAGES.zh
const lang = LANGUAGES[Anot.language || navigator.language || 'en']
class Uploader {
constructor(url) {
@ -77,49 +142,47 @@ const fixCont = function(vm, tool) {
<dl class="do-meditor-attach">
<dt class="tab-box" :drag="do-layer" data-limit="window">
<span class="item" :class="active:tab === 1" :click="switchTab(1)">
${LANG[tool][0]}
${lang[tool].REMOTE}
</span>
<span class="item" :class="active:tab === 2" :click="switchTab(2)">
${lang[tool].LOCAL}
</span>
<span class="item" :class="active:tab === 2" :click="switchTab(2)">本地上传</span>
<span class="item" :class="active:tab === 3" :click="switchTab(3)">
${LANG[tool][1]}
${lang[tool].MANAGE}
</span>
</dt>
<dd class="cont-box">
<div class="remote" :visible="tab === 1">
<section class="section do-fn-cl">
<input
class="txt"
class="do-meditor__input"
:duplex="attachAlt"
placeholder="${LANG[tool][2]}" />
placeholder="${lang[tool].ALT}" />
</section>
<section class="section do-fn-cl">
<input
class="txt"
class="do-meditor__input"
:duplex="attach"
placeholder="${LANG[tool][3]}" />
placeholder="${lang[tool].ADDRESS}" />
</section>
<section class="section do-fn-cl">
<a
href="javascript:;"
class="do-meditor__button submit"
:click="confirm">确定</a>
:click="confirm">${lang.BTN}</a>
</section>
</div>
<div class="local" :visible="tab === 2">
<div class="select-file">
<input ref="attach" multiple :change="change" type="file" class="hide" />
<span class="file" :click="select">选择文件</span>
${
limit
? `<span class="tips">(上传大小限制:单文件最大${limit} MB)</span>`
: ''
}
<span class="file" :click="select">${lang.CHOOSE}</span>
${limit ? `<span class="tips">(${lang.LIMIT + limit} MB)</span>` : ''}
</div>
<ul class="upload-box">
<li class="thead">
<span class="col">文件名</span>
<span class="col">上传进度</span>
<span class="col">操作</span>
<span class="col">${lang.TABLE.NAME}</span>
<span class="col">${lang.TABLE.PROGRESS}</span>
<span class="col">${lang.TABLE.HANDLE}</span>
</li>
<li class="tbody">
<p :for="uploadQueue">
@ -128,7 +191,9 @@ const fixCont = function(vm, tool) {
:text="el.name"
:layer-tips="el.name"></span>
<span class="col" :html="el.progress"></span>
<span class="col"><a class="insert" :click="insert(el)">插入</a></span>
<span class="col"><a class="insert" :click="insert(el)">${
lang.INSERT
}</a></span>
</p>
</li>
</ul>
@ -159,10 +224,10 @@ const fixCont = function(vm, tool) {
function uploadFile(vm, tool) {
for (let it of this.files) {
let ext = it.name.slice(it.name.lastIndexOf('.'))
if (tool === 'image' && !/^\.(jpg|jpeg|png|gif|bmp|webp|ico)$/.test(ext)) {
if (tool === 'IMAGE' && !/^\.(jpg|jpeg|png|gif|bmp|webp|ico)$/.test(ext)) {
this.uploadQueue.push({
name: it.name,
progress: '<span class="red">0%(文件类型错误)</span>',
progress: '<span class="red">0%(' + lang.ERROR.TYPE + ')</span>',
url: ''
})
continue
@ -170,7 +235,7 @@ function uploadFile(vm, tool) {
if (vm.props.uploadSizeLimit && it.size > vm.props.uploadSizeLimit) {
this.uploadQueue.push({
name: it.name,
progress: '<span class="red">0%(文件体积过大)</span>',
progress: '<span class="red">0%(' + lang.ERROR.SIZE + ')</span>',
url: ''
})
continue
@ -250,7 +315,7 @@ function uploadScreenshot(vm, blob) {
})
})
.then(url => {
vm.insert(`![截图](${url})`)
vm.insert(`![${lang.SCREENSHOT}](${url})`)
})
.catch(err => {
Anot.error(err)
@ -265,7 +330,7 @@ function uploadScreenshot(vm, blob) {
}
})
.then(url => {
vm.insert(`![截图](${url})`)
vm.insert(`![${lang.SCREENSHOT}](${url})`)
})
}
}
@ -306,7 +371,7 @@ function showDialog(elem, vm, tool) {
})
.then(list => {
list = list.filter(it => {
if (tool === 'image') {
if (tool === 'IMAGE') {
return it.isImage
}
return true
@ -329,14 +394,14 @@ function showDialog(elem, vm, tool) {
if (!it.url) {
return
}
let val = `\n${tool === 'image' ? '!' : ''}[${it.name}](${it.url})`
let val = `\n${tool === 'IMAGE' ? '!' : ''}[${it.name}](${it.url})`
vm.insert(val)
},
confirm: function() {
if (!this.attach || !this.attachAlt) {
return layer.toast('描述和地址不能为空', 'error')
return layer.toast(lang.ERROR.EMPTY, 'error')
}
let val = `\n${tool === 'image' ? '!' : ''}[${this.attachAlt}](${
let val = `\n${tool === 'IMAGE' ? '!' : ''}[${this.attachAlt}](${
this.attach
})`
@ -351,10 +416,10 @@ const plugin = {
__init__(ME) {
Object.assign(ME.vm.addon, {
attach(elem) {
showDialog(elem, this, 'file')
showDialog(elem, this, 'FILE')
},
image(elem) {
showDialog(elem, this, 'image')
showDialog(elem, this, 'IMAGE')
}
})
@ -379,7 +444,7 @@ const plugin = {
}
if (blob !== null) {
layer.toast('截图处理中...')
layer.toast(lang.COMPRESS)
// 压缩截图,避免文件过大
let reader = new FileReader()
reader.onload = function() {
@ -394,7 +459,7 @@ const plugin = {
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.drawImage(this, 0, 0, canvas.width, canvas.height)
// 目前 IE和Safari的toBlob方法还不支持图片质量的设定
// chrome, Firefox, 以及支持toBlob 设置图片质量
if (canvas.toBlob && (window.chrome || window.sidebar)) {
canvas.toBlob(
function(obj) {
@ -404,6 +469,8 @@ const plugin = {
0.8
)
} else {
// IE和Safari的toBlob方法还不支持图片质量的设定
// 需要先转base64再转回Blob
let base64 = canvas.toDataURL('image/jpeg', 0.8)
let buf = atob(base64.split(',')[1])
let intArr = new Uint8Array(buf.length)

View File

@ -21,12 +21,11 @@ function trim(str, sign) {
}
const $doc = Anot(document)
const addon = {
h1: function(elem) {
let that = this
let offset = Anot(elem).offset()
let wrap = this.selection(true) || '在此输入文本'
let wrap = this.selection(true) || Anot.ui.meditor.lang.PLACEHOLDER
layer.open({
type: 7,
menubar: false,
@ -49,23 +48,35 @@ const addon = {
},
content: `
<ul class="do-meditor-h1 do-fn-noselect">
<li :click="insert(1)" class="h1"><i class="do-meditor__icon icon-h1"></i></li>
<li :click="insert(2)" class="h2"><i class="do-meditor__icon icon-h2"></i></li>
<li :click="insert(3)" class="h3"><i class="do-meditor__icon icon-h3"></i></li>
<li :click="insert(4)" class="h4"><i class="do-meditor__icon icon-h4"></i></li>
<li :click="insert(5)" class="h5"><i class="do-meditor__icon icon-h5"></i></li>
<li :click="insert(6)" class="h6"><i class="do-meditor__icon icon-h6"></i></li>
<li :click="insert(1)" class="h1"><i class="do-meditor__icon icon-h1"></i>${
Anot.ui.meditor.lang.HEADERS.H1
}</li>
<li :click="insert(2)" class="h2"><i class="do-meditor__icon icon-h2"></i>${
Anot.ui.meditor.lang.HEADERS.H2
}</li>
<li :click="insert(3)" class="h3"><i class="do-meditor__icon icon-h3"></i>${
Anot.ui.meditor.lang.HEADERS.H3
}</li>
<li :click="insert(4)" class="h4"><i class="do-meditor__icon icon-h4"></i>${
Anot.ui.meditor.lang.HEADERS.H4
}</li>
<li :click="insert(5)" class="h5"><i class="do-meditor__icon icon-h5"></i>${
Anot.ui.meditor.lang.HEADERS.H5
}</li>
<li :click="insert(6)" class="h6"><i class="do-meditor__icon icon-h6"></i>${
Anot.ui.meditor.lang.HEADERS.H6
}</li>
</ul>`
})
},
quote: function(elem) {
let wrap = this.selection() || '在此输入文本'
let wrap = this.selection() || Anot.ui.meditor.lang.PLACEHOLDER
wrap = '> ' + wrap
this.insert(wrap, true)
},
bold: function(elem) {
let wrap = this.selection() || '在此输入文本'
let wrap = this.selection() || Anot.ui.meditor.lang.PLACEHOLDER
let wraped = trim(wrap, '\\*\\*')
wrap = wrap === wraped ? '**' + wrap + '**' : wraped
@ -73,7 +84,7 @@ const addon = {
this.insert(wrap, true)
},
italic: function(elem) {
let wrap = this.selection() || '在此输入文本'
let wrap = this.selection() || Anot.ui.meditor.lang.PLACEHOLDER
let wraped = trim(wrap, '_')
wrap = wrap === wraped ? '_' + wrap + '_' : wraped
@ -81,7 +92,7 @@ const addon = {
this.insert(wrap, true)
},
through: function(elem) {
let wrap = this.selection() || '在此输入文本'
let wrap = this.selection() || Anot.ui.meditor.lang.PLACEHOLDER
let wraped = trim(wrap, '~~')
wrap = wrap === wraped ? '~~' + wrap + '~~' : wraped
@ -89,13 +100,13 @@ const addon = {
this.insert(wrap, true)
},
unordered: function(elem) {
let wrap = this.selection() || '在此输入文本'
let wrap = this.selection() || Anot.ui.meditor.lang.PLACEHOLDER
wrap = '* ' + wrap
this.insert(wrap, false)
},
ordered: function(elem) {
let wrap = this.selection() || '在此输入文本'
let wrap = this.selection() || Anot.ui.meditor.lang.PLACEHOLDER
wrap = '1. ' + wrap
this.insert(wrap, false)
@ -118,7 +129,7 @@ const addon = {
linkTarget: 1,
insert: function() {
if (!this.link || !this.linkName) {
return layer.toast('链接文字和地址不能为空', 'error')
return layer.toast(Anot.ui.meditor.lang.LINK.ERROR, 'error')
}
let val = `[${this.linkName}](${this.link} ${
this.linkTarget === 1 ? ' "target=_blank"' : ''
@ -140,10 +151,14 @@ const addon = {
content: `
<div class="do-meditor-common">
<section>
<input class="txt" :duplex="linkName" placeholder="链接文字"/>
<input class="do-meditor__input" :duplex="linkName" placeholder="${
Anot.ui.meditor.lang.LINK.ALT
}"/>
</section>
<section>
<input class="txt" :duplex="link" placeholder="链接地址"/>
<input class="do-meditor__input" :duplex="link" placeholder="${
Anot.ui.meditor.lang.LINK.URL
}"/>
</section>
<section>
<label class="label">
@ -153,7 +168,7 @@ const addon = {
class="radio"
:duplex-number="linkTarget"
value="1"/>
新窗口打开
${Anot.ui.meditor.lang.TARGET.BLANK}
</label>
<label class="label">
<input
@ -162,14 +177,14 @@ const addon = {
class="radio"
:duplex-number="linkTarget"
value="2"/>
本窗口打开
${Anot.ui.meditor.lang.TARGET.SELF}
</label>
</section>
<section>
<a
href="javascript:;"
class="do-meditor__button submit"
:click="insert">确定</a>
:click="insert">${Anot.ui.meditor.lang.BTN.YES}</a>
</section>
</div>`
})
@ -183,7 +198,7 @@ const addon = {
layer.open({
type: 7,
title: '插入表情',
title: Anot.ui.meditor.lang.LAYER.FACE_TITLE,
fixed: true,
maskClose: true,
arr: [
@ -251,7 +266,9 @@ const addon = {
layer.open({
type: 7,
title: '0行 x 0列',
title: `0 ${Anot.ui.meditor.lang.TABLE.ROW} x 0 ${
Anot.ui.meditor.lang.TABLE.COLUMN
}`,
fixed: true,
maskClose: true,
offset: [
@ -279,6 +296,7 @@ const addon = {
success: function() {
let tb = this.$refs.table
let lastx, lasty
let { lang } = Anot.ui.meditor
Anot(tb).bind('mousemove', ev => {
if (ev.target.nodeName === 'SPAN') {
@ -289,7 +307,9 @@ const addon = {
}
lastx = x
lasty = y
this.title = y + 1 + '行 x ' + (x + 1) + '列'
this.title = `${y + 1} ${lang.TABLE.ROW} x ${x + 1} ${
lang.TABLE.COLUMN
}`
for (let i = 0; i <= 9; i++) {
for (let j = 0; j <= 9; j++) {
this.matrix[i][j].v = i <= y && j <= x ? 1 : 0
@ -300,7 +320,7 @@ const addon = {
Anot(tb).bind('mouseleave', ev => {
lastx = -1
lasty = -1
this.title = '0行 x 0列'
this.title = `0 ${lang.TABLE.ROW} x 0 ${lang.TABLE.COLUMN}`
for (let i = 0; i <= 9; i++) {
for (let j = 0; j <= 9; j++) {
this.matrix[i][j].v = 0
@ -312,7 +332,7 @@ const addon = {
let x = ev.target.dataset.x - 0 + 1
let y = ev.target.dataset.y - 0 + 1
let thead = `\n\n${'| 表头 '.repeat(x)}|\n`
let thead = `\n\n${('| ' + lang.TABLE.THEAD + ' ').repeat(x)}|\n`
let pipe = `${'| -- '.repeat(x)}|\n`
let tbody = ('| '.repeat(x) + '|\n').repeat(y)
@ -337,7 +357,7 @@ const addon = {
imgAlt: wrap,
insert: function() {
if (!this.img || !this.imgAlt) {
return layer.toast('链接文字和地址不能为空', 'error')
return layer.toast(Anot.ui.meditor.lang.LINK.ERROR, 'error')
}
let val = `![${this.imgAlt}](${this.img})`
@ -357,16 +377,20 @@ const addon = {
content: `
<div class="do-meditor-common">
<section>
<input class="txt" :duplex="imgAlt" placeholder="图片描述"/>
<input class="do-meditor__input" :duplex="imgAlt" placeholder="${
Anot.ui.meditor.lang.IMAGE.ALT
}"/>
</section>
<section>
<input class="txt" :duplex="img" placeholder="图片地址"/>
<input class="do-meditor__input" :duplex="img" placeholder="${
Anot.ui.meditor.lang.IMAGE.URL
}"/>
</section>
<section>
<a
href="javascript:;"
class="do-meditor__button submit"
:click="insert">确定</a>
:click="insert">${Anot.ui.meditor.lang.BTN.YES}</a>
</section>
</div>
`
@ -376,7 +400,7 @@ const addon = {
this.addon.link.call(this, elem)
},
inlinecode: function(elem) {
let wrap = this.selection() || '在此输入文本'
let wrap = this.selection() || Anot.ui.meditor.lang.PLACEHOLDER
let wraped = trim(wrap, '`')
wrap = wrap === wraped ? '`' + wrap + '`' : wraped
@ -387,7 +411,8 @@ const addon = {
let offset = Anot(elem).offset()
layer.open({
type: 7,
title: '添加代码块',
menubar: false,
fixed: true,
__lang__: [
{ id: 'asp' },
{ id: 'actionscript', name: 'ActionScript(3.0)/Flash/Flex' },
@ -422,7 +447,7 @@ const addon = {
{ id: 'typescript' },
{ id: 'xml' },
{ id: 'yaml' },
{ id: 'other', name: '其他语言' }
{ id: 'other', name: Anot.ui.meditor.lang.CODE.OTHER }
],
lang: 'javascript',
code: '',
@ -431,26 +456,33 @@ const addon = {
shift: { top: offset.top - $doc.scrollTop() },
insert: function() {
let val = `\n\`\`\`${this.lang}\n${this.code ||
'// 在此输入代码'}\n\`\`\`\n`
'// ' + Anot.ui.meditor.lang.PLACEHOLDER}\n\`\`\`\n`
that.insert(val, false)
this.close()
},
content: `
<div class="do-meditor-codeblock">
<section class="do-fn-cl">
<span class="label">语言类型</span>
<select :duplex="lang">
<option :for="__lang__" :attr-value="el.id">{{el.name || el.id}}</option>
</select>
<div class="select">
<select :duplex="lang">
<option :for="__lang__" :attr-value="el.id">{{el.name || el.id}}</option>
</select>
<span class="trigon">
<i class="do-icon-trigon-up"></i>
<i class="do-icon-trigon-down"></i>
</span>
</div>
</section>
<section>
<textarea :duplex="code" placeholder="在这里输入/粘贴代码"></textarea>
<textarea class="do-meditor__input area" :duplex="code" placeholder="${
Anot.ui.meditor.lang.PLACEHOLDER
}"></textarea>
</section>
<section class="do-fn-cl">
<a
href="javascript:;"
class="do-meditor__button submit"
:click="insert">确定</a>
:click="insert">${Anot.ui.meditor.lang.BTN.YES}</a>
</section>
</div>
`
@ -477,25 +509,21 @@ const addon = {
let offset = Anot(elem).offset()
layer.open({
type: 7,
title: '关于编辑器',
title: Anot.ui.meditor.lang.LAYER.ABOUT_TITLE,
maskClose: true,
offset: [offset.top + 35 - $doc.scrollTop()],
shift: { top: offset.top - $doc.scrollTop() },
content:
'<div class="do-meditor-about">' +
'<pre>' +
' __ __ _____ _ _ _\n' +
'| \\/ | ____|__| (_) |_ ___ _ __\n' +
"| |\\/| | _| / _` | | __/ _ \\| '__|\n" +
'| | | | |__| (_| | | || (_) | |\n' +
'|_| |_|_____\\__,_|_|\\__\\___/|_| ' +
'v' +
Anot.ui.meditor +
'</pre>' +
'<p>开源在线Markdown编辑器</p>' +
'<p><a target="_blank" href="https://doui.cc/product/meditor">https://doui.cc/product/meditor</a></p>' +
'<p>Copyright © 2017 Yutent, The MIT License.</p>' +
'</div>'
content: `<div class="do-meditor-about">
<pre>
__ __ _____ _ _ _
| \\/ | ____|__| (_) |_ ___ _ __
| |\\/| | _| / _\` | | __/ _ \\| '__|
| | | | |__| (_| | | || (_) | |
|_| |_|_____\\__,_|_|\\__\\___/|_| v${Anot.ui.meditor.version}</pre>
<p>${Anot.ui.meditor.lang.NAME}</p>
<p><a target="_blank" href="https://doui.cc/product/meditor">https://doui.cc/product/meditor</a></p>
<p>Copyright © 2017 Yutent, The MIT License.</p>
</div>`
})
}
}

View File

@ -12,6 +12,136 @@ import '../marked/index'
import addon from './addon/base'
import 'css/meditor.scss'
const LANGUAGES = {
en: {
NAME: 'Open Source Markdown Editor',
TOOLBAR: {
PIPE: '',
H1: 'Header',
QUOTE: 'Quote text',
BOLD: 'Font-bold',
ITALIC: 'Font-italic',
THROUGH: 'Font-through',
UNORDERED: 'Unordered list',
ORDERED: 'Ordered list',
LINK: 'Hyperlink',
HR: 'Line',
TIME: 'Insert current time',
FACE: 'Face',
TABLE: 'Insert table',
IMAGE: 'Upload Pictures',
FILE: 'Upload Files',
INLINECODE: 'Inline code',
BLOCKCODE: 'Block code',
PREVIEW: 'Preview',
FULLSCREEN: 'Fullscreen',
ABOUT: 'About MEditor'
},
HEADERS: {
H1: '#{1}',
H2: '#{2}',
H3: '#{3}',
H4: '#{4}',
H5: '#{5}',
H6: '#{6}'
},
PLACEHOLDER: 'Type here',
LINK: {
ALT: 'Link text',
URL: 'Link address',
ERROR: 'Link address and text can not be null'
},
IMAGE: {
ALT: 'Image alt text',
URL: 'Image address'
},
TARGET: {
BLANK: 'New window open',
SELF: 'Current window open'
},
BTN: {
YES: 'OK'
},
TABLE: {
ROW: 'row',
COLUMN: 'column',
THEAD: 'thead'
},
LAYER: {
FACE_TITLE: 'Insert Face',
ABOUT_TITLE: 'About MEditor'
},
CODE: {
OTHER: 'Other language'
}
},
zh: {
NAME: '开源在线Markdown编辑器',
TOOLBAR: {
PIPE: '',
H1: '标题',
QUOTE: '引用文本',
BOLD: '粗体',
ITALIC: '斜体',
THROUGH: '删除线',
UNORDERED: '无序列表',
ORDERED: '有序列表',
LINK: '超链接',
HR: '横线',
TIME: '插入当前时间',
FACE: '表情',
TABLE: '插入表格',
IMAGE: '插入图片',
FILE: '插入附件',
INLINECODE: '行内代码',
BLOCKCODE: '代码块',
PREVIEW: '预览',
FULLSCREEN: '全屏',
ABOUT: '关于编辑器'
},
HEADERS: {
H1: '一级标题',
H2: '二级标题',
H3: '三级标题',
H4: '四级标题',
H5: '五级标题',
H6: '六级标题'
},
PLACEHOLDER: '在此输入文本',
LINK: {
ALT: '链接文字',
URL: '链接地址',
ERROR: '链接文字和地址不能为空'
},
IMAGE: {
ALT: '图片描述',
URL: '图片地址'
},
TARGET: {
BLANK: '新窗口打开',
SELF: '本窗口打开'
},
BTN: {
YES: '确定'
},
TABLE: {
ROW: '行',
COLUMN: '列',
THEAD: '表头'
},
LAYER: {
FACE_TITLE: '插入表情',
ABOUT_TITLE: '关于编辑器'
},
CODE: {
OTHER: '其他语言'
}
}
}
LANGUAGES['zh-CN'] = LANGUAGES.zh
LANGUAGES['zh-TW'] = LANGUAGES.zh
const lang = LANGUAGES[Anot.language || navigator.language || 'en']
marked.setOptions({
highlight: function(code, lang) {
return Prism.highlight(code, Prism.languages[lang])
@ -28,31 +158,9 @@ if (!String.prototype.repeat) {
}
}
Anot.ui.meditor = '1.0.0'
Anot.ui.meditor = { version: '1.0.0', author: 'yutent', lang }
const log = console.log
// 工具栏title
const TOOLBAR = {
pipe: '',
h1: '标题',
quote: '引用文本',
bold: '粗体',
italic: '斜体',
through: '删除线',
unordered: '无序列表',
ordered: '有序列表',
link: '超链接',
hr: '横线',
time: '插入当前时间',
face: '表情',
table: '插入表格',
image: '插入图片',
file: '插入附件',
inlinecode: '行内代码',
blockcode: '代码块',
preview: '预览',
fullscreen: '全屏',
about: '关于编辑器'
}
const DEFAULT_TOOLBAR = [
'h1',
'quote',
@ -234,7 +342,7 @@ function tool(name) {
name = (name + '').trim().toLowerCase()
name = '|' === name ? 'pipe' : name
let title = TOOLBAR[name]
let title = lang.TOOLBAR[name.toUpperCase()]
let extra = ''
switch (name) {
case 'preview':