zoukankan      html  css  js  c++  java
  • 简易富文本编辑器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));
  • 相关阅读:
    leetcode 309. Best Time to Buy and Sell Stock with Cooldown
    leetcode 714. Best Time to Buy and Sell Stock with Transaction Fee
    leetcode 32. Longest Valid Parentheses
    leetcode 224. Basic Calculator
    leetcode 540. Single Element in a Sorted Array
    leetcode 109. Convert Sorted List to Binary Search Tree
    leetcode 3. Longest Substring Without Repeating Characters
    leetcode 84. Largest Rectangle in Histogram
    leetcode 338. Counting Bits
    git教程之回到过去,版本对比
  • 原文地址:https://www.cnblogs.com/woleicom/p/5468874.html
Copyright © 2011-2022 走看看