前言
由于想在自己的页面里嵌入markdown编辑器,搜了不少现成的,感觉都不是很满意:
- EpicEditor:太丑,代码不高亮,没有工具栏
- Bootstrap-Markdown:不能多行代码,没有实时预览
- PageDown:不能多行代码,使用不方便
- zempto(没记错的话):太重,是一个框架
所以萌生了自己写一个的想法
文档和Demo,快点我
提供的功能
- 工具栏
- 多行代码与高亮
- 快捷键
- 两种模式——实时效果和按键预览
- 两种调用方法--手动添加类名和异步加载
- Tab缩进
- 没有全屏编辑和自动保存
依赖的第三方工具
- highlight.js
- marked.js
- jquery
- bootstrap(基本主题需要,并非必要)
代码
/**
*
* Created by suemi on 14-12-5.
*/
var SuMarkdown=function(option){
var load=function(mark){
var get=function(dst){
var result={};
$(dst).each(function(i,e){
result.select=(
// Mozilla, Webkit
('selectionStart' in e && function()
{
var l = e.selectionEnd - e.selectionStart;
return {
start: e.selectionStart,
end: e.selectionEnd,
length: l,
text: e.value.substr(e.selectionStart, l)
};
}) ||
// Internet Explorer
(document.selection && function()
{
e.focus();
var r = document.selection.createRange();
if (r === null)
{
return {
start: 0,
end: e.value.length,
length: 0
};
}
var re = e.createTextRange();
var rc = re.duplicate();
re.moveToBookmark(r.getBookmark());
rc.setEndPoint('EndToStart', re);
return {
start: rc.text.length,
end: rc.text.length + r.text.length,
length: r.text.length,
text: r.text
};
}) ||
// Not supported
function()
{
return null;
}
)();
if(result.select) {
result.container=$(this);
return false;
}
else return true;
});
return result;
};
var replace= function (target,text,option){
var tmp;
$(target).each(function(i,e){
tmp=(
// Mozilla, Webkit
('selectionStart' in e && function()
{
var start = e.selectionStart;
e.value = e.value.substr(0, e.selectionStart) + text + e.value.substr(e.selectionEnd, e.value.length);
if (option === true || option === undefined)
{
e.selectionStart = start;
e.selectionEnd = start + text.length;
}
else
{
e.selectionStart = e.selectionEnd = start + text.length;
}
return $(e);
}) ||
// Internet Explorer
(document.selection && function()
{
e.focus();
document.selection.createRange().text = text;
return $(e);
}) ||
// Not supported
function()
{
e.value += text;
return $(e);
}
)();
if(tmp) return false;
else return true;
});
return tmp;
};
var methods={
bold:function(){
var reg=/^\*{2}[^\0]*\*{2}$/m;
var target=get($('textarea',mark));
var dst;
if(!reg.test(target.select.text))
dst=replace(target.container,'**'+target.select.text+'**');
else dst=replace(target.container,target.select.text.split('**')[1]);
return dst;
},
italic:function(){
var reg=/^_[^\0]*_$/m;
var target=get($('textarea',mark));
var dst;
if(!reg.test(target.select.text))
dst=replace(target.container,'_'+target.select.text+'_');
else dst=replace(target.container,target.select.text.split('_')[1]);
return dst;
},
head:function(){
var reg=/^#{1,6}[^\0]*/m;
var target=get($('textarea',mark));
var dst;
if(!reg.test(target.select.text))
dst=replace(target.container,'###'+target.select.text);
else dst=replace(target.container,target.select.text.split(/#{1,6}/)[1]);
return dst;
},
link:function(){
var reg=/^\[[^\0]*\]\([^\0]*\)$/m;
var target=get($('textarea',mark));
var dst;
if(!reg.test(target.select.text)) {
var url=prompt('Enter your URL :');
if(!url) return target.container;
if(target.select.text=='') target.select.text="Enter your link description here:";
dst = replace(target.container, '[ ' + target.select.text + ' ](' + url + ')');
}
else dst=replace(target.container,target.select.text.split('[')[1].split(']')[0]);
return dst;
},
img:function(){
var reg=/^!\[[^\0]*\]\([^\0]*\)$/m;
var target=get($('textarea',mark));
var dst;
if(!reg.test(target.select.text)) {
var url=prompt('Enter your Image URL :');
if(!url) return target.container;
if(target.select.text=='') target.select.text="Enter your image description here:";
dst = replace(target.container, '![ ' + target.select.text + ' ](' + url + ')');
}
else dst=replace(target.container,target.select.text.split('[')[1].split(']')[0]);
return dst;
},
list:function(){
var target=get($('textarea',mark));
if(target.select.text=='') target.select.text='list text here';
var dst=replace(target.container,'- '+target.select.text);
return dst;
},
orderlist:function(){
var target=get($('textarea',mark));
if(target.select.text=='') target.select.text='list text here';
var dst=replace(target.container,'1. '+target.select.text);
return dst;
},
code:function(){
var reg=/^`[^\0]*`$/m;
var target=get($('textarea',mark));
var dst;
if(target.select.text=='') target.select.text='list text here';
if(!reg.test(target.select.text))
dst=replace(target.container,'`'+target.select.text+'`');
else dst=replace(target.container,target.select.text.split('`')[1]);
return dst;
},
block:function(){
var target=get($('textarea',mark));
if(target.select.text=='') target.select.text='quote here';
var dst=replace(target.container,'> '+target.select.text);
return dst;
},
tab:function(){
var target=get($('textarea',mark));
var dst=replace(target.container,' ' +target.select.text.split('\n').join('\n '),false);
return dst;
},
preview:function(event){
var blank=$('.suPreview',mark);
var state=blank.css('display');
var target=get($('textarea',mark));
if(state=='none'){
blank.html(marked(target.container.val()));
$('pre code',blank).each(function(i,block){
hljs.highlightBlock(block);
});
$('.suEditor',mark).css('display','none');
blank.css('display','block');
}
else{
blank.css('display','none');
$('.suEditor',mark).css('display','block');
target.container.focus();
}
}
};
//set the hotkey
$('textarea',mark).attr('data-state','0').on('keyup',function(event){
if(event.keyCode==17) $(this).attr('data-state','0');
});
$('textarea:input',mark).on('keydown',function(event){
var dst,tmp;
switch(event.keyCode){
case 9:
event.preventDefault();
dst=methods.tab();
break;
case 17:
$(this).attr('data-state','1');
break;
case 66:
tmp=$(this).attr('data-state');
if(tmp=='1') {
methods.bold();
event.preventDefault();
}
break;
case 73:
tmp=$(this).attr('data-state');
if(tmp=='1') {
methods.italic();
event.preventDefault();
}
break;
case 71:
tmp=$(this).attr('data-state');
if(tmp=='1') {
methods.img();
event.preventDefault();
}
break;
case 72:
tmp=$(this).attr('data-state');
if(tmp=='1') {
methods.head();
event.preventDefault();
}
break;
case 75:
tmp=$(this).attr('data-state');
if(tmp=='1') {
methods.code();
event.preventDefault();
}
break;
case 76:
tmp=$(this).attr('data-state');
if(tmp=='1') {
methods.link();
event.preventDefault();
}
break;
case 79:
tmp=$(this).attr('data-state');
if(tmp=='1') {
methods.orderlist();
event.preventDefault();
}
break;
case 81:
tmp=$(this).attr('data-state');
if(tmp=='1') {
methods.block();
event.preventDefault();
}
break;
case 85:
tmp=$(this).attr('data-state');
if(tmp=='1') {
methods.list();
event.preventDefault();
}
break;
default : break;
}
});
$('.su-toolbar > *',mark).each(function(i,element){
var str=$(this).attr('class');
str=str.split(/\s+/g);
for(var i in str){
if(str[i].substring(0,8)=='su-tool-'){
var method=str[i].substring(8);
if(method=='preview') break;
$(this).on('click',function(event){
event.preventDefault();
var dst=methods[method]();
if(option.preview)
$('.suPreview',mark).html(marked(dst.val()));
dst.focus();
});
}
}
return true;
});
if(!option.clean){
$('.suPreview',mark).css({
"width":"50%",
"float":"left",
'overflow': 'auto',
'padding': '0 20px',
});
$('.suEditor',mark).css({
"width":"50%",
"float":"left",
"display":"block"
});
$('textarea',mark).css({
"tab-size":"4",
"padding":"20px",
"resize":"none",
"overflow":"auto"
});
$('textarea:focus',mark).css('background','#fff');
}
if(option.preview){
$('.su-tool-preview',mark).attr('disabled','true');
$('.suEditor textarea',mark).on('keyup',function(){
$('.suPreview',mark).html(marked($(this).val()));
$('pre code',mark).each(function(i,block){
hljs.highlightBlock(block);
});
});
}
else{
$('.suPreview',mark).css({
'width':"100%",
'display':'none',
"border-left":"solid 1px",
"border-color": $('textarea',mark).css('border-color')
});
$('.suEditor',mark).css({width:"100%"});
$('.su-tool-preview',mark).on('click',function(){
methods.preview();
});
}
if(option.css) mark.css(option.css);
if(option.textCss) $('.textarea',mark).css(option.textCss);
if(option.previewCss) $('.suPreview',mark).css(option.previewCss);
if(option.textHeight){
$('textarea',mark).css('height',option.textHeight);
$('.suPreview',mark).css('height',option.textHeight);
}
};
if(!option.target) option.target=$('.suMarkdown');
if(!option.insert){
var mark=option.target;
console.log($('textarea',mark).css('height'));
load(option.target);
}
else{
$.get(option.baseUrl,function(data){
mark=$("<div></div>");
mark.html(data);
load(mark);
$(option.target).append(mark);
},'html');
}
};
使用时
$(function(){
var option={
target:'.suMarkdown',
preview:true,
};
SuMarkdown(option);
});
为了便于理解,附上基本模板的代码
<style>
.su-toolbar{
width:100%;
height:45px;
display: block;
background: #f5f5f5;
padding: 5px;
border: solid 1px;
border-color: #cccccc;
}
.su-toolbar .tool-block{
cursor: pointer;
display: block;
width:35px;
margin:0 1%;
height:35px;
float:left;
padding: 5px;
}
.su-toolbar .tool-block *{
left:20%;
top:20%;
}
.su-toolbar .tool-block:hover{
background: #00ffff;
}
.su-toolbar button{
margin-top:5px;
}
.suEditor{
width:50%;
float:left;
display: block;
}
.suEditor textarea{
width:100%;
height: 400px;
background: #f5f5f5;
tab-size: 4;
border:solid 1px;
border-top: none;
border-color: #cccccc;
padding: 20px;
resize: none;
}
.suEditor textarea:focus{
background: #fff;
border-color:#cccccc ;
outline: none;
}
.suPreview{
width:50%;
left:50%;
float:left;
background: #f5f5f5;
height: 400px;
display: block;
overflow: auto;
padding: 0 20px;
border-right: solid 1px;
border-bottom: solid 1px;
border-color: #cccccc;
}
</style>
<div class="su-toolbar">
<div class="tool-block su-tool-bold" title="加粗(Ctrl+B)" data-placement="top" data-toggle="tooltip">
<span class="glyphicon glyphicon-bold"></span>
</div>
<div class="tool-block su-tool-italic" title="斜体(Ctrl+I)" data-placement="top" data-toggle="tooltip">
<span class="glyphicon glyphicon-italic"></span>
</div>
<div class="tool-block su-tool-head" title="标题(Ctrl+H)" data-placement="top" data-toggle="tooltip">
<span class="glyphicon glyphicon-header"></span>
</div>
<div class="tool-block su-tool-link" title="链接(Ctrl+L)" data-placement="top" data-toggle="tooltip">
<span class="glyphicon glyphicon-link"></span>
</div>
<div class="tool-block su-tool-img" title="图片(Ctrl+G)" data-placement="top" data-toggle="tooltip">
<span class="glyphicon glyphicon-picture"></span>
</div>
<div class="tool-block su-tool-list" title="无序列表(Ctrl+U)" data-placement="top" data-toggle="tooltip">
<span class="glyphicon glyphicon-list"></span>
</div>
<div class="tool-block su-tool-orderlist" title="有序列表(Ctrl+O)" data-placement="top" data-toggle="tooltip">
<span class="glyphicon glyphicon-th-list"></span>
</div>
<div class="tool-block su-tool-code" title="单行代码(Ctrl+K)" data-placement="top" data-toggle="tooltip">
<span class="glyphicon glyphicon-asterisk"></span>
</div>
<div class="tool-block su-tool-block" title="区块(Ctrl+Q)" data-placement="top" data-toggle="tooltip">
<span class="glyphicon glyphicon-comment"></span>
</div>
<button class="btn btn-sm btn-primary pull-right su-tool-preview">
<span class="glyphicon glyphicon-search"></span>
Preview
</button>
</div>
<div class="suEditor">
<textarea ></textarea>
</div>
<div class="suPreview">
</div>