Web实战之Markdown编辑器


前言

由于想在自己的页面里嵌入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>

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,383评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,522评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,852评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,621评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,741评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,929评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,076评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,803评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,265评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,582评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,716评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,395评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,039评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,027评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,488评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,612评论 2 350

推荐阅读更多精彩内容