5

这次项目中需要用到编辑器插件,于是上网查了一下。由于需要的编辑器功能比较简单,不需要太多复杂功能,所以选择了一款特别轻量的summernote插件,而且后台操作也很简单。
官网:http://summernote.org/
github地址:https://github.com/summernote...

先来看一下官网的截图

clipboard.png

麻雀虽小五脏俱全。完全可以满足编辑器的需要。

按照官网链接下载下来的是

clipboard.png

我们需要使用的是在dist文件夹内

clipboard.png

其中font主要是编辑器内的图标显示,lang是各种语言,css则是样式。我们主要来看一下summernote.js。

summernote.js

定义

 $.fn.extend({
    summernote: function () {
      var type = $.type(list.head(arguments));
      var isExternalAPICalled = type === 'string';
      var hasInitOptions = type === 'object';

      var options = hasInitOptions ? list.head(arguments) : {};

      options = $.extend({}, $.summernote.options, options);
      options.langInfo = $.extend(true, {}, $.summernote.lang['en-US'], $.summernote.lang[options.lang]);

      this.each(function (idx, note) {
        var $note = $(note);
        if (!$note.data('summernote')) {
          var context = new Context($note, options);
          $note.data('summernote', context);
          $note.data('summernote').triggerEvent('init', context.layoutInfo);
        }
      });

      var $note = this.first();
      if ($note.length) {
        var context = $note.data('summernote');
        if (isExternalAPICalled) {
          return context.invoke.apply(context, list.from(arguments));
        } else if (options.focus) {
          context.invoke('editor.focus');
        }
      }

      return this;
    }
  });

这就是初始化summernote时执行的函数。

$.extend(object) 可以理解为JQuery 添加一个静态方法。
$.fn.extend(object) 可以理解为JQuery实例添加一个方法。

默认的options如下

options: {
  modules: {
    'editor': Editor,
    'clipboard': Clipboard,
    'dropzone': Dropzone,
    'codeview': Codeview,
    'statusbar': Statusbar,
    'fullscreen': Fullscreen,
    'handle': Handle,
    // FIXME: HintPopover must be front of autolink
    //  - Script error about range when Enter key is pressed on hint popover
    'hintPopover': HintPopover,
    'autoLink': AutoLink,
    'autoSync': AutoSync,
    'placeholder': Placeholder,
    'buttons': Buttons,
    'toolbar': Toolbar,
    'linkDialog': LinkDialog,
    'linkPopover': LinkPopover,
    'imageDialog': ImageDialog,
    'imagePopover': ImagePopover,
    'videoDialog': VideoDialog,
    'helpDialog': HelpDialog,
    'airPopover': AirPopover
  },

  buttons: {},
  
  lang: 'zh-CN',

  // toolbar工具栏默认
  toolbar: [
    ['style', ['style']],
    ['font', ['bold', 'underline', 'clear']],
    ['fontname', ['fontname']],
    ['color', ['color']],
    ['para', ['ul', 'ol', 'paragraph']],
    ['table', ['table']],
    ['insert', ['link', 'picture', 'video']],
    ['view', ['fullscreen', 'codeview', 'help']]
  ],

  // popover
  popover: {
    image: [
      ['imagesize', ['imageSize100', 'imageSize50', 'imageSize25']],
      ['float', ['floatLeft', 'floatRight', 'floatNone']],
      ['remove', ['removeMedia']]
    ],
    link: [
      ['link', ['linkDialogShow', 'unlink']]
    ],
    air: [
      ['color', ['color']],
      ['font', ['bold', 'underline', 'clear']],
      ['para', ['ul', 'paragraph']],
      ['table', ['table']],
      ['insert', ['link', 'picture']]
    ]
  },

  // air mode: inline editor
  airMode: false,

  width: null,
  height: null,

  focus: false,
  tabSize: 4,
  styleWithSpan: false,
  shortcuts: true,
  textareaAutoSync: true,
  direction: null,

  styleTags: ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'],

  fontNames: [
    'Arial', 'Arial Black', 'Comic Sans MS', 'Courier New',
    'Helvetica Neue', 'Helvetica', 'Impact', 'Lucida Grande',
    'Tahoma', 'Times New Roman', 'Verdana'
  ],

  fontSizes: ['8', '9', '10', '11', '12', '14', '18', '24', '36'],

  // pallete colors(n x n)
  colors: [
    ['#000000', '#424242', '#636363', '#9C9C94', '#CEC6CE', '#EFEFEF', '#F7F7F7', '#FFFFFF'],
    ['#FF0000', '#FF9C00', '#FFFF00', '#00FF00', '#00FFFF', '#0000FF', '#9C00FF', '#FF00FF'],
    ['#F7C6CE', '#FFE7CE', '#FFEFC6', '#D6EFD6', '#CEDEE7', '#CEE7F7', '#D6D6E7', '#E7D6DE'],
    ['#E79C9C', '#FFC69C', '#FFE79C', '#B5D6A5', '#A5C6CE', '#9CC6EF', '#B5A5D6', '#D6A5BD'],
    ['#E76363', '#F7AD6B', '#FFD663', '#94BD7B', '#73A5AD', '#6BADDE', '#8C7BC6', '#C67BA5'],
    ['#CE0000', '#E79439', '#EFC631', '#6BA54A', '#4A7B8C', '#3984C6', '#634AA5', '#A54A7B'],
    ['#9C0000', '#B56308', '#BD9400', '#397B21', '#104A5A', '#085294', '#311873', '#731842'],
    ['#630000', '#7B3900', '#846300', '#295218', '#083139', '#003163', '#21104A', '#4A1031']
  ],

  lineHeights: ['1.0', '1.2', '1.4', '1.5', '1.6', '1.8', '2.0', '3.0'],

  tableClassName: 'table table-bordered',

  insertTableMaxSize: {
    col: 10,
    row: 10
  },

  dialogsInBody: false,
  dialogsFade: false,

  maximumImageFileSize: null,

  callbacks: {
    onInit: null,//初始化回调函数
    onFocus: null,//聚集
    onBlur: null,//失去焦点
    onEnter: null,//回车键的回调函数
    onKeyup: null,
    onKeydown: null,
    onSubmit: null,//提交时回调函数
    onImageUpload: null,//这就是上传图片的回调函数
    onImageUploadError: null//上传图片出错
  },

  codemirror: {
    mode: 'text/html',
    htmlMode: true,
    lineNumbers: true
  },

  keyMap: {
    pc: {
      'ENTER': 'insertParagraph',
      'CTRL+Z': 'undo',
      'CTRL+Y': 'redo',
      'TAB': 'tab',
      'SHIFT+TAB': 'untab',
      'CTRL+B': 'bold',
      'CTRL+I': 'italic',
      'CTRL+U': 'underline',
      'CTRL+SHIFT+S': 'strikethrough',
      'CTRL+BACKSLASH': 'removeFormat',
      'CTRL+SHIFT+L': 'justifyLeft',
      'CTRL+SHIFT+E': 'justifyCenter',
      'CTRL+SHIFT+R': 'justifyRight',
      'CTRL+SHIFT+J': 'justifyFull',
      'CTRL+SHIFT+NUM7': 'insertUnorderedList',
      'CTRL+SHIFT+NUM8': 'insertOrderedList',
      'CTRL+LEFTBRACKET': 'outdent',
      'CTRL+RIGHTBRACKET': 'indent',
      'CTRL+NUM0': 'formatPara',
      'CTRL+NUM1': 'formatH1',
      'CTRL+NUM2': 'formatH2',
      'CTRL+NUM3': 'formatH3',
      'CTRL+NUM4': 'formatH4',
      'CTRL+NUM5': 'formatH5',
      'CTRL+NUM6': 'formatH6',
      'CTRL+ENTER': 'insertHorizontalRule',
      'CTRL+K': 'linkDialog.show'
    },

    mac: {
      'ENTER': 'insertParagraph',
      'CMD+Z': 'undo',
      'CMD+SHIFT+Z': 'redo',
      'TAB': 'tab',
      'SHIFT+TAB': 'untab',
      'CMD+B': 'bold',
      'CMD+I': 'italic',
      'CMD+U': 'underline',
      'CMD+SHIFT+S': 'strikethrough',
      'CMD+BACKSLASH': 'removeFormat',
      'CMD+SHIFT+L': 'justifyLeft',
      'CMD+SHIFT+E': 'justifyCenter',
      'CMD+SHIFT+R': 'justifyRight',
      'CMD+SHIFT+J': 'justifyFull',
      'CMD+SHIFT+NUM7': 'insertUnorderedList',
      'CMD+SHIFT+NUM8': 'insertOrderedList',
      'CMD+LEFTBRACKET': 'outdent',
      'CMD+RIGHTBRACKET': 'indent',
      'CMD+NUM0': 'formatPara',
      'CMD+NUM1': 'formatH1',
      'CMD+NUM2': 'formatH2',
      'CMD+NUM3': 'formatH3',
      'CMD+NUM4': 'formatH4',
      'CMD+NUM5': 'formatH5',
      'CMD+NUM6': 'formatH6',
      'CMD+ENTER': 'insertHorizontalRule',
      'CMD+K': 'linkDialog.show'
    }
  },
  icons: {
    'align': 'icon-align',
    'alignCenter': 'icon-align-center',
    'alignJustify': 'icon-align-justify',
    'alignLeft': 'icon-align-left',
    'alignRight': 'icon-align-right',
    'indent': 'icon-indent-right',
    'outdent': 'icon-indent-left',
    'arrowsAlt': 'icon-resize-full',
    'bold': 'icon-bold',
    'caret': 'icon-caret-down',
    'circle': 'icon-circle',
    'close': 'icon-close',
    'code': 'icon-code',
    'eraser': 'icon-eraser',
    'font': 'icon-font',
    'frame': 'icon-frame',
    'italic': 'icon-italic',
    'link': 'icon-link',
    'unlink': 'icon-chain-broken',
    'magic': 'icon-magic',
    'menuCheck': 'icon-check',
    'minus': 'icon-minus',
    'orderedlist': 'icon-list-ol',
    'pencil': 'icon-pencil',
    'picture': 'icon-picture',
    'question': 'icon-question',
    'redo': 'icon-redo',
    'square': 'icon-square',
    'strikethrough': 'icon-strikethrough',
    'subscript': 'icon-subscript',
    'superscript': 'icon-superscript',
    'table': 'icon-table',
    'textHeight': 'icon-text-height',
    'trash': 'icon-trash',
    'underline': 'icon-underline',
    'undo': 'icon-undo',
    'unorderedlist': 'icon-list-ul',
    'video': 'icon-facetime-video'
  }
}

关于编辑器需要的工具栏toolbar具体属性可查看官网summernote-toolbar属性

更改工具栏图标

由于项目中我是直接使用fontawesome,所以我没有再引入summernote.font,直接在options中的icons中修改。但比较麻烦,不知道有什么更好的方法,求指导。

关于图片上传、提交、按键等回调函数也是在options中,详见callbacks部分

初始化一个编辑器很简单。只需要定义

<div class="summernote" id="myid"></div>

 $(function () {
    $('.summernote').summernote();
    //或者
    $('#myid').summernote();
});

设置placeholder:

$('.summernote').summernote({
        placeholder:'请输入文章内容',
        ...
    });

设置toolbar,

$('.summernote').summernote({
        toolbar:[
            ['style',['bold','italic','underline','clear']],
            ['fontsize',['fontsize']],
            ['para',['ul','ol','paragraph']],
            ['color',['color']]
        ],
        ...

    });

更改图片上传的方式

需要提及的是,summernote默认的图片上传方式为base64方式。若需要修改,看以下代码。
【一定要注意:onImageUpload方法修改时要放在callbacks内配置,否则没用】

$('#myid').summernote({
    callbacks:{
        onImageUpload: function(files, editor, $editable) {
            UploadFiles(files,insertImg);
        }
    },
    ...
});

function insertImg(){
    for(i in imageUrl){
        $('.summernote').summernote('editor.insertImage',imageUrl[i]);
    }
}


function UploadFiles(files,func){
//这里files是因为我设置了可上传多张图片,所以需要依次添加到formData中
    var formData = new FormData();
    for(f in files){
        formData.append("file", files[f]);
    }

    $.ajax({
        data: formData,
        type: "POST",
        url: "/uploadMultipleFile",
        cache: false,
        contentType: false,
        processData: false,
        success: function(imageUrl) {
            func(imageUrl);
     
        },
        error: function() {
            console.log("uploadError");
        }
    })
}

我们项目的后台是用spring+springMVC实现的。后台图片上传代码如下:

@RequestMapping(value = "/uploadMultipleFile", method = RequestMethod.POST, produces = "application/json;charset=utf8")
@ResponseBody
public String uploadMultipleFileHandler(@RequestParam("file") MultipartFile[] files, HttpServletRequest request) throws IOException {
    return     UploadUtil.uploadImage(request.getServletContext().getRealPath("/"), files);
}


//UploadUtil.java中uploadImage方法如下
 public static String uploadImage(String serverPath, MultipartFile[] files) {
    try {
        String uploadPath = serverPath + getImageRelativePath();
        String images = "{}";
        //如果不存在目录,创建一个目录
        isDirectory(uploadPath);
        if (files != null && files.length > 0) {
            for (int i = 0; i < files.length; i++) {
                MultipartFile file = files[i];
                //save file
                if (!file.isEmpty()) {
                    String savePath = getImageRelativePath() + file.getOriginalFilename();//数据库保存的图片路径
                    images = JSONUtil.addProperty(images, String.valueOf(i), savePath);
                    save(file, uploadPath);
                }
            }
        }
        return images;
    } catch (Exception e) {
        e.printStackTrace();
        return "{}";
    }
}

设置编辑器中的值:

$('#myid').summernote('code',content);

需要注意的是,content是html代码,可能存在引号嵌套的问题导致报错,记得将引号进行转义。
后台处理-java代码:

content = content.replaceAll("'","\\\\'");
content = content.replaceAll("\"", "\\\\\"");

获取编辑器中的值:

var content = $('.summernote').summernote('code');

上传附件

这次项目需要使用附件,但发现summernote貌似没有附件功能,于是自己研究了一下代码,根据项目的需求,在link链接部分进行了修改。
效果如下:

clipboard.png

首先,我们先看link按钮所绑定的事件。

context.memo('button.link', function () {
        return ui.button({
          contents: ui.icon(options.icons.link),
          tooltip: lang.link.link,
          click: context.createInvokeHandler('linkDialog.show')
        }).render();
      });

由上面的代码可以发现click事件为:linkDialog.show,那么我们再来看一下linkDialog

var LinkDialog = function (context) {
    ...
    this.initialize = function () {//初始化
        ...
        var body = '<div class="form-group">' +
               '<label>' + lang.link.textToDisplay + '</label>' +
               '<input class="note-link-text form-control" type="text" />' +
             '</div>' +
              '<div class="form-group">' +
              '<label>' + lang.link.attachment + '</label>' +
              '<input class="note-link-attachment form-control" type="file" />' +
              '</div>' +
             '<div class="form-group">' +
               '<label>' + lang.link.url + '</label>' +
               '<input class="note-link-url form-control" type="text" value="http://" />' +
             '</div>' +
             (!options.disableLinkTarget ?
               '<div class="checkbox">' +
                 '<label>' + '<input type="checkbox" checked> ' + lang.link.openInNewWindow + '</label>' +
               '</div>' : ''
             );
        var footer = '<button href="#" class="btn btn-primary note-link-btn disabled" disabled>' + lang.link.insert + '</button>';
      
    }
}

可以看到,点击链接按钮出现的弹框样式就在LinkDialoginitialize方法中的body,所以我在中间添加了一个input上传附件的部分。

'<div class="form-group">' +
'<label>' + lang.link.attachment + '</label>' +
'<input class="note-link-attachment form-control" type="file" />' +
'</div>' +

那么,我们需要在lang.link属性中,新增一个attachment附件属性。

clipboard.png

除此之外,在中文的转换部分summernote-zh-CN.min.js中,添加linkattachment: '添加附件'

好了,那么我们接下来需要处理的问题是上传文件后的处理。

this.showLinkDialog = function (linkInfo) {
    return $.Deferred(function (deferred) {
        ...
        //上传文件的输入框
        $linkAttachment = self.$dialog.find('.note-link-attachment'),
        
        ui.onDialogShown(self.$dialog, function () {
            ...
            //对于输入框的事件绑定
            $linkAttachment.on('change', function() {
                UploadFiles($linkAttachment.val(),function(url){
                    $linkUrl.val(url);//将上传后的URL赋值到linkUrl的输入框中
                });
            });
        }
    }
}

UploadFiles与上述修改上传图片的形式一样。

如果这篇文章对您有帮助,欢迎点赞。如果有疏漏,欢迎指正。
本文地址:http://lsxj615.com/2016/08/10...


lsxj
1k 声望92 粉丝

因为热爱,所以执着。