简易富文本编辑器bootstrap-wysiwyg源码注释

好久没写随笔了,因为最近比较忙,小公司基本都是一个前端干所有属于和部分不属于前端的事情,所以就没空弄了,即使想分享,也因为没有时间和精力就搁置了。

这周周六日休息,正好时间比较充裕(ps:目前处在单休中。。。),就分析了以下bootstrap-wysiwyg的源码,虽然只有200多行,但是本人js水平还是欠佳,所以大概用了一天的时间(ps:可怜我偶尔的双休还是变成了单休),对bootstrap-wysiwyg的源码进行了解读和部分的注释,在这里放出来,给需要的人,毕竟不是人人都喜欢百度的编辑器的(ps:我就是因为不喜欢百度编辑器的样式和一大堆文件的原因才会去看bootstrap-wysiwyg的源码的),这样,理解了内部构造也好扩展和修改,因为最近的项目图片不能转码提交,而是要上传到淘宝的oss上才返回地址显示在富文本编辑器中,这也是促使我对bootstrap-wysiwyg源码的解读,好了,废话太多了,简易注释的源码在下面:

  ps:里面我添加了对富文本编辑器中点击图片的监听事件(这个还是很有用的,比如修改图片大小),或者读到这篇文章的也可以去自己定义接口

    同时我也放在了github上,地址是:

    https://github.com/woleicom/bootstrap-wysiwyg-notes

    如果github被墙,国内可以访问oschina,地址是:

    https://git.oschina.net/woleicom/bootstrap-wysiwyg-notes

    喜欢的就给星吧,哈哈

  1 /* http://github.com/mindmup/bootstrap-wysiwyg */
  2 /*global jQuery, $, FileReader*/
  3 /*jslint browser:true*/
  4 (function ($) {
  5     'use strict';
  6     /*转码图片*/
  7     var readFileIntoDataUrl = function (fileInfo) {
  8         var loader = $.Deferred(),  //jq延迟对象
  9             fReader = new FileReader();
 10         fReader.onload = function (e) {
 11             loader.resolve(e.target.result);
 12         };
 13         fReader.onerror = loader.reject; //拒绝
 14         fReader.onprogress = loader.notify;
 15         fReader.readAsDataURL(fileInfo); //转码图片
 16         return loader.promise();  //返回promise
 17     };
 18     /*清空内容*/
 19     $.fn.cleanHtml = function () {
 20         var html = $(this).html();
 21         return html && html.replace(/(<br>|s|<div><br></div>|&nbsp;)*$/, '');
 22     };
 23     $.fn.wysiwyg = function (userOptions) {
 24         var editor = this,  //设置ui-jq='设置的插件别名的dom元素'(此句注释可忽略,是针对我的项目结构写的)
 25             selectedRange,
 26             options,
 27             toolbarBtnSelector,
 28             //更新工具栏
 29             updateToolbar = function () {
 30                 if (options.activeToolbarClass) {
 31                     $(options.toolbarSelector).find(toolbarBtnSelector).each(function () {
 32                         var command = $(this).data(options.commandRole);
 33                         //判断光标所在位置以确定命令的状态,为真则显示为激活
 34                         if (document.queryCommandState(command)) {
 35                             $(this).addClass(options.activeToolbarClass);
 36                         } else {
 37                             $(this).removeClass(options.activeToolbarClass);
 38                         }
 39                     });
 40                 }
 41             },
 42             //插入内容
 43             execCommand = function (commandWithArgs, valueArg) {
 44                 var commandArr = commandWithArgs.split(' '),
 45                     command = commandArr.shift(),
 46                     args = commandArr.join(' ') + (valueArg || '');
 47                 document.execCommand(command, 0, args);
 48                 updateToolbar();
 49             },
 50             //用jquery.hotkeys.js插件监听键盘
 51             bindHotkeys = function (hotKeys) {
 52                 $.each(hotKeys, function (hotkey, command) {
 53                     editor.keydown(hotkey, function (e) {
 54                         if (editor.attr('contenteditable') && editor.is(':visible')) {
 55                             e.preventDefault();
 56                             e.stopPropagation();
 57                             execCommand(command);
 58                         }
 59                     }).keyup(hotkey, function (e) {
 60                         if (editor.attr('contenteditable') && editor.is(':visible')) {
 61                             e.preventDefault();
 62                             e.stopPropagation();
 63                         }
 64                     });
 65                 });
 66             },
 67             //获取当前range对象
 68             getCurrentRange = function () {
 69                 var sel = window.getSelection();
 70                 if (sel.getRangeAt && sel.rangeCount) {
 71                     return sel.getRangeAt(0); //从当前selection对象中获得一个range对象。
 72                 }
 73             },
 74             //保存
 75             saveSelection = function () {
 76                 selectedRange = getCurrentRange();
 77             },
 78             //恢复
 79             restoreSelection = function () {
 80                 var selection = window.getSelection(); //获取当前既获区,selection是对当前激活选中区(即高亮文本)进行操作
 81                 if (selectedRange) {
 82                     try {
 83                         //移除selection中所有的range对象,执行后anchorNode、focusNode被设置为null,不存在任何被选中的内容。
 84                         selection.removeAllRanges();
 85                     } catch (ex) {
 86                         document.body.createTextRange().select();
 87                         document.selection.empty();
 88                     }
 89                     //将range添加到selection当中,所以一个selection中可以有多个range。
 90                     //注意Chrome不允许同时存在多个range,它的处理方式和Firefox有些不同。
 91                     selection.addRange(selectedRange);
 92                 }
 93             },
 94             //插入文件(这里指图片)
 95             insertFiles = function (files) {
 96                 editor.focus();
 97                 //遍历插入(应为可以多文件插入)
 98                 $.each(files, function (idx, fileInfo) {
 99                     //只可插入图片文件
100                     if (/^image//.test(fileInfo.type)) {
101                         //转码图片
102                         $.when(readFileIntoDataUrl(fileInfo))
103                             .done(function (dataUrl) {
104                             execCommand('insertimage', dataUrl); //插入图片dom及src属性值
105                         })
106                             .fail(function (e) {
107                             options.fileUploadError("file-reader", e);
108                         });
109                     } else {
110                         //非图片文件会调用config的错误函数
111                         options.fileUploadError("unsupported-file-type", fileInfo.type);
112                     }
113                 });
114             },
115             //TODO 暂不了解用意
116             markSelection = function (input, color) {
117                 restoreSelection();
118                 //确定命令是否被支持,返回true或false
119                 if (document.queryCommandSupported('hiliteColor')) {
120                     document.execCommand('hiliteColor', 0, color || 'transparent');
121                 }
122                 saveSelection();
123                 input.data(options.selectionMarker, color);
124             },
125             //绑定工具栏相应工具事件
126             bindToolbar = function (toolbar, options) {
127                 //给所有工具栏上的控件绑定点击事件
128                 toolbar.find(toolbarBtnSelector).click(function () {
129                     restoreSelection();
130                     editor.focus();  //获取焦点
131                     //设置相应配置的工具execCommand
132                     execCommand($(this).data(options.commandRole));
133                     //保存
134                     saveSelection();
135                 });
136                 //对[data-toggle=dropdown]进行单独绑定点击事件处理  字体大小
137                 toolbar.find('[data-toggle=dropdown]').click(restoreSelection);
138                 //对input控件进行单独处理,webkitspeechchange为语音事件
139                 toolbar.find('input[type=text][data-' + options.commandRole + ']').on('webkitspeechchange change', function () {
140                     var newValue = this.value; //获取input 的value
141                     this.value = '';  //清空value防止冲突
142                     restoreSelection();
143                     if (newValue) {
144                         editor.focus();//获取焦点
145                         //设置相应配置的工具execCommand
146                         execCommand($(this).data(options.commandRole), newValue);
147                     }
148                     saveSelection();
149                 }).on('focus', function () { //获取焦点
150                     var input = $(this);
151                     if (!input.data(options.selectionMarker)) {
152                         markSelection(input, options.selectionColor);
153                         input.focus();
154                     }
155                 }).on('blur', function () { //失去焦点
156                     var input = $(this);
157                     if (input.data(options.selectionMarker)) {
158                         markSelection(input, false);
159                     }
160                 });
161                 toolbar.find('input[type=file][data-' + options.commandRole + ']').change(function () {
162                     restoreSelection();
163                     if (this.type === 'file' && this.files && this.files.length > 0) {
164                         insertFiles(this.files);
165                     }
166                     saveSelection();
167                     this.value = '';
168                 });
169             },
170             //初始化拖放事件
171             initFileDrops = function () {
172                 editor.on('dragenter dragover', false)
173                     .on('drop', function (e) {
174                         var dataTransfer = e.originalEvent.dataTransfer;
175                         e.stopPropagation();
176                         e.preventDefault();
177                         if (dataTransfer && dataTransfer.files && dataTransfer.files.length > 0) {
178                             insertFiles(dataTransfer.files);
179                         }
180                     });
181             };
182         //合并传入的配置对象userOptions和默认的配置对象config
183         options = $.extend({}, $.fn.wysiwyg.defaults, userOptions);
184         //设置查找字符串:a[data-edit] button[data-edit] input[type=button][data-edit]
185         toolbarBtnSelector = 'a[data-' + options.commandRole + '],button[data-' + options.commandRole + '],input[type=button][data-' + options.commandRole + ']';
186         //设置热键 容器有[data-role=editor-toolbar]属性的dom元素
187         bindHotkeys(options.hotKeys);
188         //是否允许拖放 允许则配置拖放
189         if (options.dragAndDropImages) {initFileDrops();}
190         //配置工具栏
191         bindToolbar($(options.toolbarSelector), options);
192         //设置编辑区域为可编辑状态并绑定事件mouseup keyup mouseout
193         editor.attr('contenteditable', true)
194             .on('mouseup keyup mouseout', function () {
195                 saveSelection();
196                 updateToolbar();
197             });
198         //编辑区域绑定图片点击事件
199         //TODO 这是我自己添加的,因为有时要对图片进行一些操作
200         editor.on('mousedown','img', function (e) {
201             e.preventDefault();
202         }).on('click', 'img', function (e) {
203             var $img = $(e.currentTarget);
204             console.log($img);
205             e.preventDefault();
206             e.stopPropagation();
207         });
208         //window绑定touchend事件
209         $(window).bind('touchend', function (e) {
210             var isInside = (editor.is(e.target) || editor.has(e.target).length > 0),
211                 currentRange = getCurrentRange(),
212                 clear = currentRange && (currentRange.startContainer === currentRange.endContainer && currentRange.startOffset === currentRange.endOffset);
213             if (!clear || isInside) {
214                 saveSelection();
215                 updateToolbar();
216             }
217         });
218         return this;
219     };
220     //配置参数
221     $.fn.wysiwyg.defaults = {
222         hotKeys: {      //热键 应用hotkeys.js jquery插件
223             'ctrl+b meta+b': 'bold',
224             'ctrl+i meta+i': 'italic',
225             'ctrl+u meta+u': 'underline',
226             'ctrl+z meta+z': 'undo',
227             'ctrl+y meta+y meta+shift+z': 'redo',
228             'ctrl+l meta+l': 'justifyleft',
229             'ctrl+r meta+r': 'justifyright',
230             'ctrl+e meta+e': 'justifycenter',
231             'ctrl+j meta+j': 'justifyfull',
232             'shift+tab': 'outdent',
233             'tab': 'indent'
234         },
235         toolbarSelector: '[data-role=editor-toolbar]',
236         commandRole: 'edit',
237         activeToolbarClass: 'btn-info',
238         selectionMarker: 'edit-focus-marker',
239         selectionColor: 'darkgrey',
240         dragAndDropImages: true,  //是否支持拖放,默认为支持
241         fileUploadError: function (reason, detail) { console.log("File upload error", reason, detail); }
242     };
243 }(window.jQuery));
原文地址:https://www.cnblogs.com/woleicom/p/5468874.html