// 扩展后续的js模块 (function (window) { // 注意:3个下划线 window.___E_mod = function (fn) { var E = window.___E; if (E == null) { // 说明一开始的验证没有通过,直接返回即可 return; } // 执行传入的 fn 函数 fn(E, window.Zepto); }; })(window); // 检测各个支持项是否存在 (function (window) { var $ = window.Zepto, E = window.___E, gitlink = 'github.com/wangfupeng1988/wangEditor-mobile'; // 验证是否重复引用 if(E != null) { if (E.gitlink === gitlink) { // 重复引用,提示,并退出 alert('不得重复引用wangEditor的js文件'); return; } if (!E.gitlink || E.gitlink !== gitlink) { // 属性名已被占用 alert('window.___E 属性名已经被其他程序占用。wangEditor-mobile引用失败'); return; } } // 验证 zepto 是否引用 if ($ == null) { alert('wangEditor-mobile依赖于zepto,请先引用zepto.js'); return; } // 验证浏览器对range的支持 if (!document.createRange || typeof document.createRange !== 'function') { alert('当前浏览器不支持document.createRange,不能生成富文本编辑器'); return; } })(window); // 构造函数 (function (window, $){ // 标记 var gitlink = 'github.com/wangfupeng1988/wangEditor-mobile'; // 定义构造函数 var E = function (textareaId) { var self = this; self.$body = $('body'); // textarea var $textarea = $('#' + textareaId); self.$textarea = $textarea; // 记录每一个tap事件的时间,防止短时间内重复tap self.checkTapTime = function (e, info) { //E.log('checkTapTime', info); var type = e.type.toLowerCase(); var currentElem; var $currentElem; var result = true; if (type.indexOf('tap') < 0) { // 只针对 tap,其他的不管 return result; } if (e) { // 传入 event 对象,则为每个event对象分配事件 currentElem = e.currentTarget || e.target; $currentElem = $(currentElem); } else { // 未传入,则都用body $currentElem = self.$body; } if ($currentElem.data('tapTime') == null) { // 第一次,直接通过 $currentElem.data('tapTime', Date.now().toString()); result = true; } else { if (Date.now() - parseInt($currentElem.data('tapTime')) < 100) { // 如果当前时间和上一次tapTime相差 **ms 之内, // 则视为无效,并阻止冒泡和默认行为 e.preventDefault(); e.stopPropagation(); result = false; } else { // 否则就继续并更新tapTime $currentElem.data('tapTime', Date.now().toString()); result = true; } } return result; }; // ---------接下来即初始化各个组件配置---------- // 初始化编辑器对象的默认配置 self.initDefaultConfig(); // 初始化编辑区域的配置 self.addTxt(); // 初始化菜单配置 self.addMenus(); }; // 原型 alias E.fn = E.prototype; // 做一个标记,用来判断是否重复引用 E.fn.gitlink = gitlink; // console.log 函数 E.log = function (a, b) { if (window.console) { if (b) { console.log(a, b); } else { console.log(a); } } else { alert(a); if (b) { alert(b); } } }; // console.warn 函数 E.warn = function (a, b) { if (window.console) { if (b) { console.warn(a, b); } else { console.warn(a); } } else { alert(a); if (b) { alert(b); } } }; // 暴露全局函数 window.___E = E; })(window, window.Zepto); // 自定义alert window.___E_mod(function (E, $) { }); // userAgent window.___E_mod(function (E, $) { var agent = window.navigator.userAgent.toLowerCase(); E.isAndroid = agent.indexOf('android') > 0; E.isUC = agent.indexOf('ucbrowser') > 0; E.agent = agent; }); // 初始化静态配置文件 window.___E_mod(function (E, $) { // 全局配置 E.config = { // 菜单栏中的 color 按钮点击时的颜色值(即css中的颜色值) menuColorValue: 'red', // 菜单栏中的 quote 按钮点击时的样式 menuQuoteStyle: { 'display': 'block', 'border-left': '5px solid #d0e5f2', 'padding': '4px 0 4px 10px', 'background-color': '#f1f1f1', 'margin': '4px 0' }, // 菜单配置 menus: [ 'head', 'bold', 'color', 'quote', 'list', 'img', 'happy', 'check' ], // 表情图标配置 happy: [ 'http://wangeditor.github.io/expressions/1.gif', 'http://wangeditor.github.io/expressions/2.gif', 'http://wangeditor.github.io/expressions/3.gif', 'http://wangeditor.github.io/expressions/4.gif', 'http://wangeditor.github.io/expressions/5.gif', 'http://wangeditor.github.io/expressions/6.gif', 'http://wangeditor.github.io/expressions/7.gif', 'http://wangeditor.github.io/expressions/8.gif', 'http://wangeditor.github.io/expressions/9.gif', 'http://wangeditor.github.io/expressions/10.gif', 'http://wangeditor.github.io/expressions/11.gif', 'http://wangeditor.github.io/expressions/12.gif', 'http://wangeditor.github.io/expressions/13.gif', 'http://wangeditor.github.io/expressions/14.gif', 'http://wangeditor.github.io/expressions/15.gif', 'http://wangeditor.github.io/expressions/16.gif', 'http://wangeditor.github.io/expressions/17.gif', 'http://wangeditor.github.io/expressions/18.gif', 'http://wangeditor.github.io/expressions/19.gif', 'http://wangeditor.github.io/expressions/20.gif' ], // 上传图片 uploadImgUrl: '', // 上传文件的超时时间(默认 10s) uploadTimeout: 10 * 1000, // 测试地址(在测试地址,编辑器会主动输出一些console.log信息) testHostname: 'localhost', // 上传图片时,如果浏览器不支持预览图片,则用以下图片代替 loadingImg: 'http://images2015.cnblogs.com/blog/138012/201512/138012-20151208194759027-506651939.gif', }; }); // 初始化对象配置 window.___E_mod(function (E, $) { E.fn.initDefaultConfig = function () { var self = this; var globalConfig = E.config; // 从全局配置拷贝到对象配置 var objConfig = $.extend({}, globalConfig); // 赋值到对象中 self.config = objConfig; }; }); // 上传图片 window.___E_mod(function (E, $) { var isAndroid = E.isAndroid; var isUC = E.isUC; }); // 初始化编辑区域的数据对象 window.___E_mod(function (E, $) { E.fn.addTxt = function () { var self = this; var $textarea = self.$textarea; var val = $.trim($textarea.val()); // 编辑区域(将textarea的值,直接复制过来) var $txt = $( '
' + val + '
' ); // modal container var $modalContainer = $('
'); // 记录到对象中 self.$txt = $txt; self.$modalContainer = $modalContainer; // 最后插入一个空行 self.insertEmpltyLink(); }; }); // 增加自带的菜单数据对象 window.___E_mod(function (E, $) { E.fn.addMenus = function () { var self = this; // ------------- menus container var $menuContainer = $('
'); var $menuItemContainer = $('
'); var $menuContainerTip = $('
'); // 三角形 // 增加小三角 tip $menuContainer.append($menuContainerTip); // 菜单项的容器 $menuContainer.append($menuItemContainer); // 添加到数据对象 self.$menuContainer = $menuContainer; self.$menuItemContainer = $menuItemContainer; // ------------- menus 数据集合 ------------- self.menus = {}; self.addMenuBold('bold'); self.addMenuHead('head'); self.addMenuColor('color'); self.addMenuQuote('quote'); self.addMenuList('list'); self.addMenuCheck('check'); self.addMenuHappy('happy'); self.addMenuImg('img'); }; }); // bold window.___E_mod(function (E, $) { E.fn.addMenuBold = function (menuId) { var self = this; var menus = self.menus; menus[menuId] = { // 是否处于选中状态 selected: false, // 触发器 $trigger: $('
'), // 包裹触发器的容器 $wrap: $('
'), // 绑定触发器事件 bindEvent: function (editor) { var menuData = this; menuData.$trigger.on('singleTap', function (e) { if (self.checkTapTime(e, 'bold') === false) { return; } self.command('bold', false, undefined, e); }); }, // 更新样式 updateStyle: function (editor) { var menuData = this; var $trigger = menuData.$trigger; if ( document.queryCommandState('bold') ) { menuData.selected = true; $trigger.addClass('selected'); } else { menuData.selected = false; $trigger.removeClass('selected'); } } }; }; }); // head window.___E_mod(function (E, $) { E.fn.addMenuHead = function (menuId) { var self = this; var menus = self.menus; menus[menuId] = { // 是否处于选中状态 selected: false, // 触发器 $trigger: $('
'), // 包裹触发器的容器 $wrap: $('
'), // 绑定触发器事件 bindEvent: function (editor) { var menuData = this; menuData.$trigger.on('singleTap', function (e) { if (self.checkTapTime(e, 'head') === false) { return; } // 执行命令 if (menuData.selected) { self.command('formatblock', false, 'p', e); } else { self.command('formatblock', false, 'h3', e); } }); }, // 更新样式 updateStyle: function (editor) { var menuData = this; var $trigger = menuData.$trigger; var value = document.queryCommandValue('formatblock'); if ( /^h\d{1}$/i.test(value) ) { menuData.selected = true; $trigger.addClass('selected'); } else { menuData.selected = false; $trigger.removeClass('selected'); } } }; }; }); // color window.___E_mod(function (E, $) { E.fn.addMenuColor = function (menuId) { var self = this; var menus = self.menus; var configColor = self.config.menuColorValue; menus[menuId] = { // 是否处于选中状态 selected: false, // 触发器 $trigger: $('
'), // 包裹触发器的容器 $wrap: $('
'), // 绑定触发器事件 bindEvent: function (editor) { var menuDate = this; var $trigger = menuDate.$trigger; $trigger.on('singleTap', function (e) { if (self.checkTapTime(e, 'color') === false) { return; } var color = configColor; if (menuDate.selected) { color = '#000'; } self.command('forecolor', false, color, e); }); }, // 更新样式 updateStyle: function (editor) { var menuDate = this; var $trigger = menuDate.$trigger; var foreColor = document.queryCommandValue('forecolor'); if (foreColor !== 'rgb(0, 0, 0)') { menuDate.selected = true; $trigger.addClass('selected'); } else { menuDate.selected = false; $trigger.removeClass('selected'); } } }; }; }); // quote window.___E_mod(function (E, $) { E.fn.addMenuQuote = function (menuId) { var self = this; var menus = self.menus; var $txt = self.$txt; var configQuoteStyle = self.config.menuQuoteStyle; menus[menuId] = { // 是否处于选中状态 selected: false, // 触发器 $trigger: $('
'), // 包裹触发器的容器 $wrap: $('
'), // 绑定触发器事件 bindEvent: function (editor) { var menuData = this; menuData.$trigger.on('singleTap', function (e) { if (self.checkTapTime(e, 'quote') === false) { return; } // 执行命令 var $focusElem = self.$focusElem; var $quoteElem; var text; var commandFn; if (menuData.selected) { // 此时已经是 quote 状态,此时点击,应该恢复为普通文字 // 获取当前的 quote 元素 if ($focusElem.get(0).nodeName === 'BLOCKQUOTE') { $quoteElem = $focusElem; } else { $quoteElem = $focusElem.closest('blockquote'); } if ($quoteElem.length === 0) { // 没有找到 blockquote 元素 return; } // 获取文本 text = $quoteElem.text(); // 定义一个自定义的命令事件 commandFn = function () { var $p = $('

' + text + '

'); $quoteElem.after($p); $quoteElem.remove(); }; // 执行盖自定义事件 self.customCommand(false, commandFn, e); } else { // 当前不是quote状态 // 执行命令,将段落设置为quote self.command('formatblock', false, 'blockquote', e); // 设置quote样式(刚刚被设置为quote) self.$txt.find('blockquote').each(function(key, node){ // 遍历编辑区域所有的quote var $quote = $(node), styleKey = 'w_editor_quote_style', hasStyle = $quote.attr(styleKey); // 如果没有标记,则设置样式并记录标记 if(hasStyle == null){ // configQuoteStyle 是配置的样式,可在 editor.config 中修改 $quote.css(configQuoteStyle); $quote.attr(styleKey, '1'); } }); } // else }); }, // 更新样式 updateStyle: function (editor) { var menuData = this; var $trigger = menuData.$trigger; var value = document.queryCommandValue('formatblock'); if ( /^blockquote$/i.test(value) ) { menuData.selected = true; $trigger.addClass('selected'); } else { menuData.selected = false; $trigger.removeClass('selected'); } } }; }; }); // list window.___E_mod(function (E, $) { E.fn.addMenuList = function (menuId) { var self = this; var menus = self.menus; menus[menuId] = { // 是否处于选中状态 selected: false, // 触发器 $trigger: $('
'), // 包裹触发器的容器 $wrap: $('
'), // 绑定触发器事件 bindEvent: function (editor) { var menuData = this; menuData.$trigger.on('singleTap', function (e) { if (self.checkTapTime(e, 'list') === false) { return; } if (!menuData.selected) { // 如果当前状态不是list // 执行命令前,先去掉 focus 的样式 self.$focusElem.removeClass('focus-elem'); } // 执行命令 self.command('InsertUnorderedList', false, undefined, e); }); }, // 更新样式 updateStyle: function (editor) { var menuData = this; var $trigger = menuData.$trigger; if ( document.queryCommandState('InsertUnorderedList') ) { menuData.selected = true; $trigger.addClass('selected'); } else { menuData.selected = false; $trigger.removeClass('selected'); } } }; }; }); // check window.___E_mod(function (E, $) { E.fn.addMenuCheck = function (menuId) { var self = this; var menus = self.menus; menus[menuId] = { // 是否处于选中状态 selected: false, // 触发器 $trigger: $('
'), // 包裹触发器的容器 $wrap: $('
'), // 绑定触发器事件 bindEvent: function (editor) { var tapTime = Date.now(); var menuData = this; menuData.$trigger.on('singleTap', function (e) { if (self.checkTapTime(e, 'check') === false) { return; } // 构建dom结构 var $checkbox = $(''); var $content = $('

'); $content.append($checkbox).append('  '); // 初始化 checkbox 事件 $checkbox.on('singleTap', function (e) { // 某些情况下,浏览器的tap事件会被连续触发两次,在此处理 if (Date.now() - tapTime < 50) { return; } else { tapTime = Date.now(); } var $checkbox = $(e.target); var checked = $checkbox.is(':checked'); $checkbox.prop('checked', !checked); // 最后不要忘记阻止冒泡 e.preventDefault(); e.stopPropagation(); }); // 执行命令 var fn = function () { self.$focusElem.after($content); }; self.customCommand(false, fn, e); }); }, // 更新样式 updateStyle: function (editor) { // 暂时不需要 } }; }; }); // 表情菜单 window.___E_mod(function (E, $) { E.fn.addMenuHappy = function (menuId) { var self = this; var $body = self.$body; var menus = self.menus; var happyUrlArr = self.config.happy; var $txt = self.$txt; // 提醒,不要使用编辑器自带的表情 if (happyUrlArr.length > 0) { if (happyUrlArr[0].indexOf('http://wangeditor.github.io/expressions') === 0) { E.warn('正在使用wangEdior提供的免费表情图标,它们将从 github 下载,速度很慢!!!建议将表情图标重新配置,请参见文档说明'); } } menus[menuId] = { // 选中状态 selected: false, // 触发器 $trigger: $('
'), // 包裹触发器的容器 $wrap: $('
'), // $modal $modal: $('
'), // 渲染 $modal renderModal: function () { var menuData = this; var $modal = menuData.$modal; var itemTpl = ''; var contentHtmlArr = []; // 拼接数据 $.each(happyUrlArr, function (key, value) { contentHtmlArr.push( itemTpl.replace(/#{imgUrl}/ig, value) ); }); $modal.append(contentHtmlArr.join('')); // 渲染到页面中 $body.append($modal); // 定位 var width = $modal.width(); $modal.css('margin-left', (0 - width)/2); // 绑定表情图标的事件 $modal.on('click', '.command-link', function (e) { // 屏蔽click事件的默认行为 e.preventDefault(); }); $modal.on('singleTap', '.command-link', function (e) { if (self.checkTapTime(e, 'happy command-link') === false) { return; } var $commandLink = $(e.currentTarget); var commandValue = $commandLink.attr('commandValue'); // 执行命令 self.command('InsertImage', false, commandValue, e); // 隐藏 modal menuData.hideModal(); }); }, // 显示 $modal showModal: function () { var menuData = this; var $modal = menuData.$modal; var scrollTop = $body.get(0).scrollTop; // 显示 modal $modal.show(); // 确定 modal 位置 $modal.css({ 'margin-top': (scrollTop + 50) + 'px' }); // 点击编辑区域隐藏modal var hideModalFn = function (e) { menuData.hideModal(); // 隐藏modal之后,接着取消事件绑定 $txt.off('singleTap', hideModalFn); }; // 绑定事件 $txt.on('singleTap', hideModalFn); }, // 隐藏 $modal hideModal: function () { this.$modal.hide(); }, // 绑定事件 bindEvent: function (editor) { var menuData = this; var $trigger = menuData.$trigger; var $modal = menuData.$modal; function commandFnForOneEvent() { menuData.renderModal(); menuData.showModal(); } function commandFnForOnEvent() { menuData.showModal(); } // one绑定的方法只执行一次 // 用于渲染 modal 元素并显示 $trigger.one('singleTap', function (e) { if (self.checkTapTime(e, 'happy one') === false) { return; } // 渲染modal并显示 self.customCommand(true, commandFnForOneEvent, e); }); // on 绑定的方法每次都执行 // 用于每次显示和隐藏modal $trigger.on('singleTap', function (e) { if (self.checkTapTime(e, 'happy on') === false) { return; } // 显示modal self.customCommand(true, commandFnForOnEvent, e); }); }, // 更新样式 updateStyle: function () { // 暂时不需要 } }; }; }); // 上传图片 window.___E_mod(function (E, $) { E.fn.addMenuImg = function (menuId) { var self = this; var $body = self.$body; var $txt = self.$txt; var menus = self.menus; var isAndroid = E.isAndroid; var isUC = E.isUC; var idDebugger = false; // 默认为 false,下文可能修改 // 针对 test 地址,打印信息 function log(info) { if (!idDebugger) { return; } E.log(info); } // 用随机数生成input 的 id var inputFileId = 'inputfile' + Math.random().toString().slice(2); menus[menuId] = { // 选中状态 selected: false, // 触发器 $trigger: (function () { // 不同os、不同browser的情况不一样 if (isAndroid || isUC) { return $('
'); } else { return $(''); } })(), // 包裹触发器的容器 $wrap: $('
'), // 渲染 form renderForm: function () { var fromHtml = [ '', ' ', '' ].join(''); var $container = $('
'); // var $container = $('
'); $container.html(fromHtml); // 渲染到页面中 $body.append($container); }, // 绑定菜单事件 bindEvent: function (editor) { var menuData = this; var $trigger = menuData.$trigger; // 将 form 渲染到页面上 menuData.renderForm(); // 获取 form input-file 对象 var $inputFlie = $('#' + inputFileId); // 将以base64的图片url数据转换为Blob function convertBase64UrlToBlob(urlData, filetype){ //去掉url的头,并转换为byte var bytes=window.atob(urlData.split(',')[1]); //处理异常,将ascii码小于0的转换为大于0 var ab = new ArrayBuffer(bytes.length); var ia = new Uint8Array(ab); for (var i = 0; i < bytes.length; i++) { ia[i] = bytes.charCodeAt(i); } // 类型 if (filetype === '' || !filetype) { filetype = 'image/png'; } return new Blob([ab], {type : filetype}); } // input 有文件选中时,显示预览图,提交 form $inputFlie.on('change', function (e) { // 获取配置项内容 var config = self.config; var uploadImgUrl = config.uploadImgUrl || ''; var testHostname = config.testHostname || 'localhost'; var loadingImgUrl = config.loadingImg; var timeout = config.uploadTimeout || 10000; idDebugger = testHostname === window.location.hostname; // 判断改浏览器是否支持 FormData 和 fileReader if (!window.FileReader || !window.FormData) { alert('当前浏览器不支持html5中的 FileReader 和 FormData,无法上传图片'); return; } if (uploadImgUrl === '') { // 没有配置上传图片的url alert( '没有配置 uploadImgUrl ,wangEditor 将无法上传图片。\n\n' + '想要配置上传图片,请参见文档说明。\n\n' + '不想要图片上传,可通过配置 menus 隐藏该功能。' ); return; } var files = $inputFlie[0].files || []; if (files.length === 0) { return; } var file = files[0]; var fileType = file.type || ''; var reader = new FileReader(); var $focusElem = self.$focusElem; log('选中的文件为:' + file.name); log('服务器端上传地址为:' + uploadImgUrl); reader.onload = function(e){ var base64 = e.target.result || this.result, prevImgSrc, prveImgContainerId = 'div' + Math.random().toString().slice(2), prevImgInfoId = 'info' + Math.random().toString().slice(2), xhr, formData, timeoutId; // ---------- 显示预览 ---------- if (window.URL && window.URL.createObjectURL) { // 如果浏览器支持预览本地图片功能,则预览本地图片 prevImgSrc = window.URL.createObjectURL(file); // 生成预览图片,设置半透明 $focusElem.after( '
' + '

上传中...

' + ' ' + '
' ); } else { // 如果浏览器不支持预览本地图片,则复制为一个配置的图片地址 prevImgSrc = loadingImgUrl; // 生成预览图片 $focusElem.after( '
' + ' ' + '
' ); } log('生成预览图片,src是:' + prevImgSrc); // ---------- 上传到服务器 ---------- xhr = new XMLHttpRequest(); formData = new FormData(); // 访问超时 function timeoutCallback() { log('访问超时(配置的超时事件是:'+ timeout +')'); var $prevImgContainer = $('#' + prveImgContainerId); $prevImgContainer.remove(); if (xhr.abort) { xhr.abort(); } alert('上传超时,请重试'); if (location.hostname.toLowerCase() === 'wangeditor.github.io') { // 官网demo的特殊提示 alert('提示:wangEditor官网demo没有后台服务,因此超时(该alert在实际项目中不会出现)'); } } log('准备上传文件...'); xhr.open('POST', uploadImgUrl, true); // 计时开始 timeoutId = setTimeout(timeoutCallback, timeout); xhr.onload = function () { // 得到消息之后,清除计时 clearTimeout(timeoutId); var resultSrc = xhr.responseText; //服务器端要返回图片url地址 var erroInfo; var $prevImgContainer = $('#' + prveImgContainerId); var loadImg; var $loadImg; log('服务器端的返回数据为:' + resultSrc); // 返回数据错误 if (resultSrc.indexOf('error|') === 0) { erroInfo = resultSrc.split('|')[1]; log('很遗憾,后台返回error,错误信息为:' + erroInfo); // 提示错误 alert('上传图片错误: \n' + erroInfo); // 移除预览图片 $prevImgContainer.remove(); } else { // 返回正确的图片地址 log('请确认以上图片是否有效,无效将无法显示在页面中'); log('准备下载该图片...'); // 下载图片,下载完成后赋值到编辑器中 $('#' + prevImgInfoId).text('加载中...'); loadImg = document.createElement('img'); loadImg.src = resultSrc; loadImg.onload = function () { log('下载完成,正式呈现在编辑区域'); $loadImg = $(loadImg); $loadImg.css('max-width', '100%'); $prevImgContainer.after($loadImg); $prevImgContainer.remove(); // 保存内容 self.saveSourceCode(); }; loadImg.onerror = function () { log('图片加载失败,请确定这个url是否能成功得到图片:' + resultSrc); alert('图片加载失败'); $prevImgContainer.remove(); // 保存内容 self.saveSourceCode(); }; } }; // 添加图片数据 // 1. 图片数据要经过 convertBase64UrlToBlob 转换 // 2. wangEditorMobileFile 要和后台一致 formData.append('wangEditorMobileFile', convertBase64UrlToBlob(base64, fileType)); xhr.send(formData); }; //读取文件 reader.readAsDataURL(file); }); // 点击菜单,触发 input 事件 $trigger.on('singleTap', function (e) { // singleTap需要验证 if (self.checkTapTime(e, 'img') === false) { return; } function fn() { $inputFlie.trigger('click'); } self.customCommand(true, fn, e); }); }, // 更新样式 updateStyle: function () { // 暂时不需要 } }; }; }); // 渲染编辑器区域 window.___E_mod(function (E, $) { E.fn.renderTxt = function () { var self = this; var $textarea = self.$textarea; var $txt = self.$txt; var $modalContainer = self.$modalContainer; var $body = self.$body; $textarea.after($txt); $textarea.hide(); $body.append($modalContainer); }; }); // 渲染菜单栏 window.___E_mod(function (E, $) { E.fn.renderMenu = function () { var self = this; var menus = self.menus; var $menuContainer = self.$menuContainer; var $menuItemContainer = self.$menuItemContainer; var $txt = self.$txt; var $gap = $('
'); var $body = self.$body; // 配置文件中的菜单配置 var configMenus = self.config.menus; // 遍历菜单配置集合,渲染菜单 $.each(configMenus, function (key, menuId) { var menu = menus[menuId]; if (menu == null) { return; } var $trigger = menu.$trigger; var $wrap = menu.$wrap; if ($trigger) { // 渲染菜单 if ($wrap) { $wrap.append($trigger); $menuItemContainer.append($wrap); } else { $menuItemContainer.append($trigger); } // 菜单之间的间隙 // 之所以需要加 clone 是因为 append 由一种『单例移动』的特性!!需注意!! $menuItemContainer.append($gap.clone()); } }); // 删除最后一个间隙(即最后一个子元素) $menuItemContainer.children().last().remove(); // 默认隐藏 $menuContainer.hide(); // 变量记录菜单容器的显示与隐藏 self.menuDisplayShow = false; $body.append($menuContainer); }; }); // 绑定document事件 window.___E_mod(function (E, $) { E.fn.bindDocumentEvent = function () { var $document = $(document); var self = this; var $txt = self.$txt; var srollTime = Date.now(); // 滚动时隐藏菜单栏 $document.on('scroll', function (e) { // 给滚动事件增加一个时间间隔的限制 if (Date.now() - srollTime <= 50) { return; } else { srollTime = Date.now(); } // 隐藏菜单 self.hideMenuContainer(); }); }; }); // 绑定编辑区域事件 window.___E_mod(function (E, $) { E.fn.bindTxtEvent = function () { var self = this; var $txt = self.$txt; var $menuContainer = self.$menuContainer; var menuContainer = $menuContainer.get(0); var srollTime = Date.now(); // 处理点击 $txt 的选区 // $txt 的 tap 事件中调用 function selectionHeadle () { var focusElem; var $focusElem; // 保存选中区域 self.saveSelection(); // 获取当前选中的元素,并设置一个高亮样式 focusElem = self.getWrapRangeElem(); if (focusElem) { $focusElem = $(focusElem); // 增加样式 $focusElem.addClass('focus-elem'); self.$focusElem = $focusElem; } // 更新菜单样式 self.updateMenuStyle(); } // 处理点击 $txt 的选区 // tap时,记录选区,并显示菜单 $txt.on('focus', function () { // 记录编辑器区域已经focus self.isFocus = true; }); $txt.on('singleTap', function (e) { if (self.checkTapTime(e, '$txt') === false) { return; } // 如果当前不是focus状态,则返回 if (!self.isFocus) { return; } // 获取 target 并保存 var $target = $(e.target); self.eventTarget($target); if ($target.hasClass('focus-elem')) { // 如果当前点击的就是上一次点击的元素,则隐藏菜单栏,返回 self.hideMenuContainer(); return; } if ($target.get(0).nodeName === 'IMG') { // 点击图片时,隐藏菜单,返回 self.hideMenuContainer(); return; } if ($target.get(0) === $txt.get(0)) { // 如果当前选中的编辑区域,则隐藏菜单,返回 self.hideMenuContainer(); return; } // 根据点击的位置,对菜单栏进行定位 self.setMenuContainerPosition(); // 如果有上一次选中的元素,则清除样式 var $focusElem = self.$focusElem; if ($focusElem) { $focusElem.removeClass('focus-elem'); } // 等待 xxx ms 处理点击 $txt 的选区 setTimeout(selectionHeadle, self.txtTapDelay); self.txtTapDelay = 100; // 最后,阻止冒泡,阻止document接收到(document的tap事件要隐藏菜单) e.stopPropagation(); }); // 滚动时隐藏菜单栏 $txt.on('scroll', function (e) { // 给滚动事件增加一个时间间隔的限制 if (Date.now() - srollTime <= 50) { return; } else { srollTime = Date.now(); } // 隐藏菜单 self.hideMenuContainer(); // 阻止冒泡 e.stopPropagation(); }); // 打字时隐藏菜单栏 $txt.on('keydown', function (e) { // 隐藏菜单 self.hideMenuContainer(); // 删除并且没有内容的时候,就禁止再删除了 var html = $txt.html(); if (e.keyCode === 8 && /^<(\w+)[^<>]*>
<\/\1>$/.test(html)) { e.preventDefault(); } }); // longtap doubletap 隐藏菜单 $txt.on('longTap, doubleTap', function () { // 隐藏菜单 self.hideMenuContainer(); }); // blur时,隐藏菜单栏 // 存储源代码 $txt.on('blur', function (e) { // 记录编辑区域已经 blur self.isFocus = false; var explicitOriginalTarget = e.explicitOriginalTarget; if (menuContainer.contains(explicitOriginalTarget)) { // firefox 中,点击菜单会导致 $txt blur // e.explicitOriginalTarget 有值,并且包含在菜单容器中,证明是 ff 点击菜单所致的 blur e.preventDefault(); return; } else { // 其他浏览器,点击菜单,都不会出现 blur 的情况 // 这是正常情况下的 blur // 存储源码代码 self.saveSourceCode(); // 隐藏菜单 fn self.hideMenuContainer(); } }); // $txt-blur end }; }); // 绑定菜单容器事件 window.___E_mod(function (E, $) { // ----------------- 绑定menucontiner事件 E.fn.bindMenuContainerEvent = function () { // tap时focus $text var self = this; var $menuContainer = self.$menuContainer; //tap时,阻止冒泡,因为上层的 $txt 会监测 tap 事件 $menuContainer.on('tap', function (e) { e.preventDefault(); e.stopPropagation(); }); }; }); // 绑定菜单按钮的事件 window.___E_mod(function (E, $) { E.fn.bindMenuBtnEvent = function () { var self = this; var menus = self.menus; // 遍历菜单配置项,执行bindEvent事件 $.each(menus, function (key, menu) { var bindEvent = menu.bindEvent; if (bindEvent && typeof bindEvent === 'function') { bindEvent.call(menu, self); } }); }; }); // $txt api window.___E_mod(function (E, $) { // focus API // 暂时没人用 // E.fn.focusTxt = function () { // var self = this; // var $txt = self.$txt; // if (!self.isFocus) { // $txt.focus(); // } // }; // 保存、获取 $txt tap时event对象的target元素 E.fn.eventTarget = function ($elem) { var self = this; if ($elem == null) { return self.$eventTargetElem; } else { self.$eventTargetElem = $elem; } }; // 保存源代码 E.fn.saveSourceCode = function () { var self = this; var $txt = self.$txt; var $textarea = self.$textarea; var sourceCode = ''; var $txtClone = $txt.clone(); var $focusElem1 = $txtClone.find('.focus-elem'); // 将当前的 $focusElem 清除样式 $focusElem1.removeClass('focus-elem'); // 获取源码 sourceCode = $txtClone.html(); sourceCode = sourceCode.replace(/\s?class=""/g, ''); $textarea.val(sourceCode); }; // 在编辑区域最后插入空行 E.fn.insertEmpltyLink = function () { var self = this; var $txt = self.$txt; var $children = $txt.children(); if ($children.length === 0) { $txt.append($('


')); return; } if ($children.last().html() !== '
') { $txt.append($('


')); } }; }); // menucontainer api window.___E_mod(function (E, $) { var positionFirst = true; // 第一次计算位置 var firstTop = 0; // -------------------计算菜单的位置,显示菜单------------------- E.fn.setMenuContainerPosition = function () { var self = this; // 目标元素 var $targetElem = self.eventTarget(); var targetElemTop = $targetElem.offset().top; // 编辑区域 var $txt = self.$txt; var txtTop = $txt.offset().top; var txtLeft = $txt.offset().left; // 菜单 var $menuContainer = self.$menuContainer; var menuHeight = $menuContainer.height(); // 计算 top 结果 var top = targetElemTop; if (top < txtTop) { // 停靠在编辑器区域上方 top = txtTop; } top = top - 55; // 上移 55px 即菜单栏的高度 // 设置菜单的样式,定位 $menuContainer.css({ 'top': top, 'left': txtLeft + 2 }); // 显示menucontainer self.showMenuContainer(); }; // -------------------显示菜单------------------- E.fn.showMenuContainer = function () { var self = this; var $menuContainer = self.$menuContainer; if (self.menuDisplayShow === false) { $menuContainer.show(); $menuContainer.css('opacity', '0.9'); // 此处要动画效果 // 记录状态 self.menuDisplayShow = true; } }; // -------------------隐藏菜单------------------- E.fn.hideMenuContainer = function () { var self = this; var $menuContainer = self.$menuContainer; var $txt = self.$txt; var $focusElem = self.$focusElem; var $otherFocusElem = $txt.find('.focus-elem'); // 得重新查找,可能发生变化 if (self.menuDisplayShow) { $menuContainer.hide(); // 此处隐藏之后,在设置透明度。不要动画效果了,效果不好 $menuContainer.css('opacity', '0'); // 记录状态 self.menuDisplayShow = false; // 隐藏 focuselem if ($focusElem) { $focusElem.removeClass('focus-elem'); $otherFocusElem.removeClass('focus-elem'); } } }; }); // menus api window.___E_mod(function (E, $) { // 更新菜单样式 E.fn.updateMenuStyle = function () { var self = this; var menus = self.menus; // 遍历所有菜单,更新他们的样式 $.each(menus, function (key, menu) { var fn = menu.updateStyle; if (fn) { fn.call(menu); } }); }; }); // 编辑器的命令事件 window.___E_mod(function (E, $) { // 符合这个正则表达式的命令,恢复选区时,不要恢复外围选区(如插入图片) var regRestoreNoWrapSelection = /insertimage/i; // 传统事件 E.fn.command = function (commandName, bool, commandValue, e, callback) { var self = this; // 验证该命令是否不能恢复外围选区,将传入到 customCommand 中 var regResult = regRestoreNoWrapSelection.test(commandName); var fn = function () { document.execCommand(commandName, !!bool, commandValue); }; // 执行事件 self.customCommand(regResult, fn, e, callback); }; // 自定义事件 E.fn.customCommand = function (isRestoreNoWrapSelection, fn, e, callback) { var self = this; var currentRange = self.currentRange(); var currentWrapRange = self.currentWrapRange(); var $txt = self.$txt; /* isRestoreNoWrapSelection 参数的作用: 1. 有些 command 是需要选中整个外围选区再进行操作的,一般是修改样式,例如加粗。 针对加粗这种样式操作,如果不默认选中一个选区,是看不到任何效果的。 2. 但是有些 command 一定不能选中外围选区,一般是插入操作,例如插入图片。 如果选中了一段区域,再执行插入图片,插入图片之后,刚才的那段选区就没有了。 因此,isRestoreNoWrapSelection 的作用就是来判断,是否要选中外围选区。 */ if (isRestoreNoWrapSelection) { // 恢复选区(非整个外围选区) self.restoreSelection(currentRange); } else { // 恢复选区(整个外围选区) self.restoreSelection(currentWrapRange); } // 执行命令 fn(); // 如果 $txt 最后没有空行,则增加一个 self.insertEmpltyLink(); // 重新保存选区,因为部分浏览器会自动清空选区 self.saveSelection(); // 恢复选区(非外围选区) self.restoreSelection(currentRange); // 阻止默认行为,阻止冒泡 if (e) { e.preventDefault(); e.stopPropagation(); } // 回调函数 if (callback) { callback.call(self); } // 隐藏菜单栏 self.hideMenuContainer(); }; }); // range selection 的相关操作 window.___E_mod(function (E, $) { // 设置或读取当前的range E.fn.currentRange = function (cr){ if (cr) { this.currentRangeData = cr; } else { return this.currentRangeData; } }; // 设置或读取当前range的wrapRange E.fn.currentWrapRange = function (cwr){ if (cwr) { this.currentWrapRangeData = cwr; } else { return this.currentWrapRangeData; } }; // 获取 wrapRange 的元素(不能是text类型) E.fn.getWrapRangeElem = function () { var self = this; var $txt = self.$txt; var txtClass = $txt.attr('class'); // 获取编辑区域的class var wrapRange = this.currentWrapRange(); var elem; var resultElem; var eventTargetElem = self.eventTarget().get(0); if (wrapRange == null) { return; } // 获取 range 的包含元素 elem = wrapRange.commonAncestorContainer; if (elem.nodeType === 3) { // text类型,获取父元素 resultElem = elem.parentNode; } else { // 不是 text 类型 resultElem = elem; } // 判断 resultElem 是不是 $txt (通过 class 判断) if (resultElem.className === txtClass) { // 如果 resultElem 正好是 $txt // 则将 resultElem 试图设置为 $txt 最后一个子元素 resultElem = $txt.children().last().get(0) || resultElem; } // 返回 return resultElem; }; // 保存选择区域 E.fn.saveSelection = function (range) { var self = this, _parentElem, selection, wrapRange, txt = self.$txt.get(0); if (range) { _parentElem = range.commonAncestorContainer; } else { selection = document.getSelection(); if (selection.getRangeAt && selection.rangeCount) { range = document.getSelection().getRangeAt(0); _parentElem = range.commonAncestorContainer; } } // 确定父元素一定要包含在编辑器区域内 if (_parentElem && (txt.contains(_parentElem) || txt === _parentElem) ) { // 保存选择区域 self.currentRange(range); // 保存 wrapRange wrapRange = document.getSelection().getRangeAt(0); wrapRange.selectNodeContents(_parentElem); self.currentWrapRange(wrapRange); } }; // 恢复选中区域 E.fn.restoreSelection = function (range){ var selection; if (!range) { return; } selection = document.getSelection(); selection.removeAllRanges(); selection.addRange(range); }; }); // editor API 对外开放的接口 window.___E_mod(function (E, $) { }); // 初始化编辑器对象 window.___E_mod(function (E, $) { E.fn.init = function () { var self = this; // 渲染编辑区域 self.renderTxt(); // 渲染菜单栏 self.renderMenu(); // 绑定事件 self.bindDocumentEvent(); self.bindTxtEvent(); self.bindMenuBtnEvent(); self.bindMenuContainerEvent(); }; });