/** @Name:layui.layedit 富文本编辑器 @Author:贤心 @License:MIT */ layui.define(['layer', 'form'], function(exports){ "use strict"; var $ = layui.$ ,layer = layui.layer ,form = layui.form ,hint = layui.hint() ,device = layui.device() ,MOD_NAME = 'layedit', THIS = 'layui-this', SHOW = 'layui-show', ABLED = 'layui-disabled' ,Edit = function(){ var that = this; that.index = 0; //全局配置 that.config = { //默认工具bar tool: [ 'strong', 'italic', 'underline', 'del' ,'|' ,'left', 'center', 'right' ,'|' ,'link', 'unlink', 'face', 'image' ] ,hideTool: [] ,height: 280 //默认高 }; }; //全局设置 Edit.prototype.set = function(options){ var that = this; $.extend(true, that.config, options); return that; }; //事件监听 Edit.prototype.on = function(events, callback){ return layui.onevent(MOD_NAME, events, callback); }; //建立编辑器 Edit.prototype.build = function(id, settings){ settings = settings || {}; var that = this ,config = that.config ,ELEM = 'layui-layedit', textArea = $(typeof(id)=='string'?'#'+id:id) ,name = 'LAY_layedit_'+ (++that.index) ,haveBuild = textArea.next('.'+ELEM) ,set = $.extend({}, config, settings) ,tool = function(){ var node = [], hideTools = {}; layui.each(set.hideTool, function(_, item){ hideTools[item] = true; }); layui.each(set.tool, function(_, item){ if(tools[item] && !hideTools[item]){ node.push(tools[item]); } }); return node.join(''); }() ,editor = $(['<div class="'+ ELEM +'">' ,'<div class="layui-unselect layui-layedit-tool">'+ tool +'</div>' ,'<div class="layui-layedit-iframe">' ,'<iframe id="'+ name +'" name="'+ name +'" textarea="'+ id +'" frameborder="0"></iframe>' ,'</div>' ,'</div>'].join('')) //编辑器不兼容ie8以下 if(device.ie && device.ie < 8){ return textArea.removeClass('layui-hide').addClass(SHOW); } haveBuild[0] && (haveBuild.remove()); setIframe.call(that, editor, textArea[0], set) textArea.addClass('layui-hide').after(editor); return that.index; }; //获得编辑器中内容 Edit.prototype.getContent = function(index){ var iframeWin = getWin(index); if(!iframeWin[0]) return; return toLower(iframeWin[0].document.body.innerHTML); }; //获得编辑器中纯文本内容 Edit.prototype.getText = function(index){ var iframeWin = getWin(index); if(!iframeWin[0]) return; return $(iframeWin[0].document.body).text(); }; /** * 设置编辑器内容 * @param {[type]} index 编辑器索引 * @param {[type]} content 要设置的内容 * @param {[type]} flag 是否追加模式 */ Edit.prototype.setContent = function(index, content, flag){ var iframeWin = getWin(index); if(!iframeWin[0]) return; if(flag){ $(iframeWin[0].document.body).append(content) }else{ $(iframeWin[0].document.body).html(content) }; layedit.sync(index) }; //将编辑器内容同步到textarea(一般用于异步提交时) Edit.prototype.sync = function(index){ var iframeWin = getWin(index); if(!iframeWin[0]) return; var textarea = $('#'+iframeWin[1].attr('textarea')); textarea.val(toLower(iframeWin[0].document.body.innerHTML)); }; //获取编辑器选中内容 Edit.prototype.getSelection = function(index){ var iframeWin = getWin(index); if(!iframeWin[0]) return; var range = Range(iframeWin[0].document); return document.selection ? range.text : range.toString(); }; //iframe初始化 var setIframe = function(editor, textArea, set){ var that = this, iframe = editor.find('iframe'); iframe.css({ height: set.height }).on('load', function(){ var conts = iframe.contents() ,iframeWin = iframe.prop('contentWindow') ,head = conts.find('head') ,style = $(['<style>' ,'*{margin: 0; padding: 0;}' ,'body{padding: 10px; line-height: 20px; overflow-x: hidden; word-wrap: break-word; font: 14px Helvetica Neue,Helvetica,PingFang SC,Microsoft YaHei,Tahoma,Arial,sans-serif; -webkit-box-sizing: border-box !important; -moz-box-sizing: border-box !important; box-sizing: border-box !important;}' ,'a{color:#01AAED; text-decoration:none;}a:hover{color:#c00}' ,'p{margin-bottom: 10px;}' ,'img{display: inline-block; border: none; vertical-align: middle;}' ,'pre{margin: 10px 0; padding: 10px; line-height: 20px; border: 1px solid #ddd; border-left-width: 6px; background-color: #F2F2F2; color: #333; font-family: Courier New; font-size: 12px;}' ,'</style>'].join('')) ,body = conts.find('body'); head.append(style); body.attr('contenteditable', 'true').css({ 'min-height': set.height }).html(textArea.value||''); hotkey.apply(that, [iframeWin, iframe, textArea, set]); //快捷键处理 toolActive.call(that, iframeWin, editor, set); //触发工具 }); } //获得iframe窗口对象 ,getWin = function(index){ var iframe = $('#LAY_layedit_'+ index) ,iframeWin = iframe.prop('contentWindow'); return [iframeWin, iframe]; } //IE8下将标签处理成小写 ,toLower = function(html){ if(device.ie == 8){ html = html.replace(/<.+>/g, function(str){ return str.toLowerCase(); }); } return html; } //快捷键处理 ,hotkey = function(iframeWin, iframe, textArea, set){ var iframeDOM = iframeWin.document, body = $(iframeDOM.body); body.on('keydown', function(e){ var keycode = e.keyCode; //处理回车 if(keycode === 13){ var range = Range(iframeDOM); var container = getContainer(range) ,parentNode = container.parentNode; if(parentNode.tagName.toLowerCase() === 'pre'){ if(e.shiftKey) return layer.msg('请暂时用shift+enter'); return false; } iframeDOM.execCommand('formatBlock', false, '<p>'); } }); //给textarea同步内容 $(textArea).parents('form').on('submit', function(){ var html = body.html(); //IE8下将标签处理成小写 if(device.ie == 8){ html = html.replace(/<.+>/g, function(str){ return str.toLowerCase(); }); } textArea.value = html; }); //处理粘贴 body.on('paste', function(e){ iframeDOM.execCommand('formatBlock', false, '<p>'); setTimeout(function(){ filter.call(iframeWin, body); textArea.value = body.html(); }, 100); }); } //标签过滤 ,filter = function(body){ var iframeWin = this ,iframeDOM = iframeWin.document; //清除影响版面的css属性 body.find('*[style]').each(function(){ var textAlign = this.style.textAlign; this.removeAttribute('style'); $(this).css({ 'text-align': textAlign || '' }) }); //修饰表格 body.find('table').addClass('layui-table'); //移除不安全的标签 body.find('script,link').remove(); } //Range对象兼容性处理 ,Range = function(iframeDOM){ return iframeDOM.selection ? iframeDOM.selection.createRange() : iframeDOM.getSelection().getRangeAt(0); } //当前Range对象的endContainer兼容性处理 ,getContainer = function(range){ return range.endContainer || range.parentElement().childNodes[0] } //在选区插入内联元素 ,insertInline = function(tagName, attr, range){ var iframeDOM = this.document ,elem = document.createElement(tagName) for(var key in attr){ elem.setAttribute(key, attr[key]); } elem.removeAttribute('text'); if(iframeDOM.selection){ //IE var text = range.text || attr.text; if(tagName === 'a' && !text) return; if(text){ elem.innerHTML = text; } range.pasteHTML($(elem).prop('outerHTML')); range.select(); } else { //非IE var text = range.toString() || attr.text; if(tagName === 'a' && !text) return; if(text){ elem.innerHTML = text; } range.deleteContents(); range.insertNode(elem); } } //工具选中 ,toolCheck = function(tools, othis){ var iframeDOM = this.document ,CHECK = 'layedit-tool-active' ,container = getContainer(Range(iframeDOM)) ,item = function(type){ return tools.find('.layedit-tool-'+type) } if(othis){ othis[othis.hasClass(CHECK) ? 'removeClass' : 'addClass'](CHECK); } tools.find('>i').removeClass(CHECK); item('unlink').addClass(ABLED); $(container).parents().each(function(){ var tagName = this.tagName.toLowerCase() ,textAlign = this.style.textAlign; //文字 if(tagName === 'b' || tagName === 'strong'){ item('b').addClass(CHECK) } if(tagName === 'i' || tagName === 'em'){ item('i').addClass(CHECK) } if(tagName === 'u'){ item('u').addClass(CHECK) } if(tagName === 'strike'){ item('d').addClass(CHECK) } //对齐 if(tagName === 'p'){ if(textAlign === 'center'){ item('center').addClass(CHECK); } else if(textAlign === 'right'){ item('right').addClass(CHECK); } else { item('left').addClass(CHECK); } } //超链接 if(tagName === 'a'){ item('link').addClass(CHECK); item('unlink').removeClass(ABLED); } }); } //触发工具 ,toolActive = function(iframeWin, editor, set){ var iframeDOM = iframeWin.document ,body = $(iframeDOM.body) ,toolEvent = { //超链接 link: function(range){ var container = getContainer(range) ,parentNode = $(container).parent(); link.call(body, { href: parentNode.attr('href') ,target: parentNode.attr('target') }, function(field){ var parent = parentNode[0]; if(parent.tagName === 'A'){ parent.href = field.url; } else { insertInline.call(iframeWin, 'a', { target: field.target ,href: field.url ,text: field.url }, range); } }); } //清除超链接 ,unlink: function(range){ iframeDOM.execCommand('unlink'); } //表情 ,face: function(range){ face.call(this, function(img){ insertInline.call(iframeWin, 'img', { src: img.src ,alt: img.alt }, range); }); } //图片 ,image: function(range){ var that = this; layui.use('upload', function(upload){ var uploadImage = set.uploadImage || {}; upload.render({ url: uploadImage.url ,method: uploadImage.type ,elem: $(that).find('input')[0] ,done: function(res){ if(res.code == 0){ res.data = res.data || {}; insertInline.call(iframeWin, 'img', { src: res.data.src ,alt: res.data.title }, range); } else { layer.msg(res.msg||'上传失败'); } } }); }); } //插入代码 ,code: function(range){ code.call(body, function(pre){ insertInline.call(iframeWin, 'pre', { text: pre.code ,'lay-lang': pre.lang }, range); }); } //帮助 ,help: function(){ layer.open({ type: 2 ,title: '帮助' ,area: ['600px', '380px'] ,shadeClose: true ,shade: 0.1 ,skin: 'layui-layer-msg' ,content: ['http://www.layui.com/about/layedit/help.html', 'no'] }); } } ,tools = editor.find('.layui-layedit-tool') ,click = function(){ var othis = $(this) ,events = othis.attr('layedit-event') ,command = othis.attr('lay-command'); if(othis.hasClass(ABLED)) return; body.focus(); var range = Range(iframeDOM) ,container = range.commonAncestorContainer if(command){ iframeDOM.execCommand(command); if(/justifyLeft|justifyCenter|justifyRight/.test(command)){ iframeDOM.execCommand('formatBlock', false, '<p>'); } setTimeout(function(){ body.focus(); }, 10); } else { toolEvent[events] && toolEvent[events].call(this, range); } toolCheck.call(iframeWin, tools, othis); } ,isClick = /image/ tools.find('>i').on('mousedown', function(){ var othis = $(this) ,events = othis.attr('layedit-event'); if(isClick.test(events)) return; click.call(this) }).on('click', function(){ var othis = $(this) ,events = othis.attr('layedit-event'); if(!isClick.test(events)) return; click.call(this) }); //触发内容区域 body.on('click', function(){ toolCheck.call(iframeWin, tools); layer.close(face.index); }); } //超链接面板 ,link = function(options, callback){ var body = this, index = layer.open({ type: 1 ,id: 'LAY_layedit_link' ,area: '350px' ,shade: 0.05 ,shadeClose: true ,moveType: 1 ,title: '超链接' ,skin: 'layui-layer-msg' ,content: ['<ul class="layui-form" style="margin: 15px;">' ,'<li class="layui-form-item">' ,'<label class="layui-form-label" style="width: 60px;">URL</label>' ,'<div class="layui-input-block" style="margin-left: 90px">' ,'<input name="url" lay-verify="url" value="'+ (options.href||'') +'" autofocus="true" autocomplete="off" class="layui-input">' ,'</div>' ,'</li>' ,'<li class="layui-form-item">' ,'<label class="layui-form-label" style="width: 60px;">打开方式</label>' ,'<div class="layui-input-block" style="margin-left: 90px">' ,'<input type="radio" name="target" value="_self" class="layui-input" title="当前窗口"' + ((options.target==='_self' || !options.target) ? 'checked' : '') +'>' ,'<input type="radio" name="target" value="_blank" class="layui-input" title="新窗口" ' + (options.target==='_blank' ? 'checked' : '') +'>' ,'</div>' ,'</li>' ,'<li class="layui-form-item" style="text-align: center;">' ,'<button type="button" lay-submit lay-filter="layedit-link-yes" class="layui-btn"> 确定 </button>' ,'<button style="margin-left: 20px;" type="button" class="layui-btn layui-btn-primary"> 取消 </button>' ,'</li>' ,'</ul>'].join('') ,success: function(layero, index){ var eventFilter = 'submit(layedit-link-yes)'; form.render('radio'); layero.find('.layui-btn-primary').on('click', function(){ layer.close(index); body.focus(); }); form.on(eventFilter, function(data){ layer.close(link.index); callback && callback(data.field); }); } }); link.index = index; } //表情面板 ,face = function(callback){ //表情库 var faces = function(){ var alt = ["[微笑]", "[嘻嘻]", "[哈哈]", "[可爱]", "[可怜]", "[挖鼻]", "[吃惊]", "[害羞]", "[挤眼]", "[闭嘴]", "[鄙视]", "[爱你]", "[泪]", "[偷笑]", "[亲亲]", "[生病]", "[太开心]", "[白眼]", "[右哼哼]", "[左哼哼]", "[嘘]", "[衰]", "[委屈]", "[吐]", "[哈欠]", "[抱抱]", "[怒]", "[疑问]", "[馋嘴]", "[拜拜]", "[思考]", "[汗]", "[困]", "[睡]", "[钱]", "[失望]", "[酷]", "[色]", "[哼]", "[鼓掌]", "[晕]", "[悲伤]", "[抓狂]", "[黑线]", "[阴险]", "[怒骂]", "[互粉]", "[心]", "[伤心]", "[猪头]", "[熊猫]", "[兔子]", "[ok]", "[耶]", "[good]", "[NO]", "[赞]", "[来]", "[弱]", "[草泥马]", "[神马]", "[囧]", "[浮云]", "[给力]", "[围观]", "[威武]", "[奥特曼]", "[礼物]", "[钟]", "[话筒]", "[蜡烛]", "[蛋糕]"], arr = {}; layui.each(alt, function(index, item){ arr[item] = layui.cache.dir + 'images/face/'+ index + '.gif'; }); return arr; }(); face.hide = face.hide || function(e){ if($(e.target).attr('layedit-event') !== 'face'){ layer.close(face.index); } } return face.index = layer.tips(function(){ var content = []; layui.each(faces, function(key, item){ content.push('<li title="'+ key +'"><img src="'+ item +'" alt="'+ key +'"></li>'); }); return '<ul class="layui-clear">' + content.join('') + '</ul>'; }(), this, { tips: 1 ,time: 0 ,skin: 'layui-box layui-util-face' ,maxWidth: 500 ,success: function(layero, index){ layero.css({ marginTop: -4 ,marginLeft: -10 }).find('.layui-clear>li').on('click', function(){ callback && callback({ src: faces[this.title] ,alt: this.title }); layer.close(index); }); $(document).off('click', face.hide).on('click', face.hide); } }); } //插入代码面板 ,code = function(callback){ var body = this, index = layer.open({ type: 1 ,id: 'LAY_layedit_code' ,area: '550px' ,shade: 0.05 ,shadeClose: true ,moveType: 1 ,title: '插入代码' ,skin: 'layui-layer-msg' ,content: ['<ul class="layui-form layui-form-pane" style="margin: 15px;">' ,'<li class="layui-form-item">' ,'<label class="layui-form-label">请选择语言</label>' ,'<div class="layui-input-block">' ,'<select name="lang">' ,'<option value="JavaScript">JavaScript</option>' ,'<option value="HTML">HTML</option>' ,'<option value="CSS">CSS</option>' ,'<option value="Java">Java</option>' ,'<option value="PHP">PHP</option>' ,'<option value="C#">C#</option>' ,'<option value="Python">Python</option>' ,'<option value="Ruby">Ruby</option>' ,'<option value="Go">Go</option>' ,'</select>' ,'</div>' ,'</li>' ,'<li class="layui-form-item layui-form-text">' ,'<label class="layui-form-label">代码</label>' ,'<div class="layui-input-block">' ,'<textarea name="code" lay-verify="required" autofocus="true" class="layui-textarea" style="height: 200px;"></textarea>' ,'</div>' ,'</li>' ,'<li class="layui-form-item" style="text-align: center;">' ,'<button type="button" lay-submit lay-filter="layedit-code-yes" class="layui-btn"> 确定 </button>' ,'<button style="margin-left: 20px;" type="button" class="layui-btn layui-btn-primary"> 取消 </button>' ,'</li>' ,'</ul>'].join('') ,success: function(layero, index){ var eventFilter = 'submit(layedit-code-yes)'; form.render('select'); layero.find('.layui-btn-primary').on('click', function(){ layer.close(index); body.focus(); }); form.on(eventFilter, function(data){ layer.close(code.index); callback && callback(data.field); }); } }); code.index = index; } //全部工具 ,tools = { html: '<i class="layui-icon layedit-tool-html" title="HTML源代码" lay-command="html" layedit-event="html""></i><span class="layedit-tool-mid"></span>' ,strong: '<i class="layui-icon layedit-tool-b" title="加粗" lay-command="Bold" layedit-event="b""></i>' ,italic: '<i class="layui-icon layedit-tool-i" title="斜体" lay-command="italic" layedit-event="i""></i>' ,underline: '<i class="layui-icon layedit-tool-u" title="下划线" lay-command="underline" layedit-event="u""></i>' ,del: '<i class="layui-icon layedit-tool-d" title="删除线" lay-command="strikeThrough" layedit-event="d""></i>' ,'|': '<span class="layedit-tool-mid"></span>' ,left: '<i class="layui-icon layedit-tool-left" title="左对齐" lay-command="justifyLeft" layedit-event="left""></i>' ,center: '<i class="layui-icon layedit-tool-center" title="居中对齐" lay-command="justifyCenter" layedit-event="center""></i>' ,right: '<i class="layui-icon layedit-tool-right" title="右对齐" lay-command="justifyRight" layedit-event="right""></i>' ,link: '<i class="layui-icon layedit-tool-link" title="插入链接" layedit-event="link""></i>' ,unlink: '<i class="layui-icon layedit-tool-unlink layui-disabled" title="清除链接" lay-command="unlink" layedit-event="unlink""></i>' ,face: '<i class="layui-icon layedit-tool-face" title="表情" layedit-event="face""></i>' ,image: '<i class="layui-icon layedit-tool-image" title="图片" layedit-event="image"><input type="file" name="file"></i>' ,code: '<i class="layui-icon layedit-tool-code" title="插入代码" layedit-event="code"></i>' ,help: '<i class="layui-icon layedit-tool-help" title="帮助" layedit-event="help"></i>' } ,edit = new Edit(); exports(MOD_NAME, edit); });