├── README.md ├── emotion ├── 01.gif ├── 02.gif ├── 03.gif ├── 04.gif ├── 05.gif ├── 06.gif ├── 07.gif ├── 08.gif ├── 09.gif ├── 10.gif ├── 11.gif ├── 12.gif ├── 13.gif ├── 14.gif ├── 15.gif ├── 16.gif ├── 17.gif ├── 18.gif ├── 19.gif ├── 20.gif ├── 21.gif ├── 22.gif ├── 23.gif ├── 24.gif ├── 25.gif ├── 26.gif ├── 27.gif ├── 28.gif ├── 29.gif └── 30.gif ├── gulpfile.js ├── index.html ├── static ├── css │ └── main.css └── js │ ├── editor.js │ └── editor.min.js └── upload.php /README.md: -------------------------------------------------------------------------------- 1 | #简介 2 | liteditor是一款小巧的富文本编辑器,无任何依赖,适合那些追求简洁高效的朋友使用。 3 | 演示地址:http://yrdr.github.io/liteditor/ 4 | 1. 目前普通功能支持IE9+浏览器 5 | 2. 上传图片比较特殊,需要IE10+支持 6 | 3. 暂不支持跨域上传图片 7 | 8 | #使用方法 9 | 使用方法请参照index.html的配置 10 | 此版本为开发版,请不要直接用于生产环境 11 | -------------------------------------------------------------------------------- /emotion/01.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yrdr/liteditor/ee2457ece7464cf348004411bb7d665c75db801f/emotion/01.gif -------------------------------------------------------------------------------- /emotion/02.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yrdr/liteditor/ee2457ece7464cf348004411bb7d665c75db801f/emotion/02.gif -------------------------------------------------------------------------------- /emotion/03.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yrdr/liteditor/ee2457ece7464cf348004411bb7d665c75db801f/emotion/03.gif -------------------------------------------------------------------------------- /emotion/04.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yrdr/liteditor/ee2457ece7464cf348004411bb7d665c75db801f/emotion/04.gif -------------------------------------------------------------------------------- /emotion/05.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yrdr/liteditor/ee2457ece7464cf348004411bb7d665c75db801f/emotion/05.gif -------------------------------------------------------------------------------- /emotion/06.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yrdr/liteditor/ee2457ece7464cf348004411bb7d665c75db801f/emotion/06.gif -------------------------------------------------------------------------------- /emotion/07.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yrdr/liteditor/ee2457ece7464cf348004411bb7d665c75db801f/emotion/07.gif -------------------------------------------------------------------------------- /emotion/08.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yrdr/liteditor/ee2457ece7464cf348004411bb7d665c75db801f/emotion/08.gif -------------------------------------------------------------------------------- /emotion/09.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yrdr/liteditor/ee2457ece7464cf348004411bb7d665c75db801f/emotion/09.gif -------------------------------------------------------------------------------- /emotion/10.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yrdr/liteditor/ee2457ece7464cf348004411bb7d665c75db801f/emotion/10.gif -------------------------------------------------------------------------------- /emotion/11.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yrdr/liteditor/ee2457ece7464cf348004411bb7d665c75db801f/emotion/11.gif -------------------------------------------------------------------------------- /emotion/12.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yrdr/liteditor/ee2457ece7464cf348004411bb7d665c75db801f/emotion/12.gif -------------------------------------------------------------------------------- /emotion/13.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yrdr/liteditor/ee2457ece7464cf348004411bb7d665c75db801f/emotion/13.gif -------------------------------------------------------------------------------- /emotion/14.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yrdr/liteditor/ee2457ece7464cf348004411bb7d665c75db801f/emotion/14.gif -------------------------------------------------------------------------------- /emotion/15.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yrdr/liteditor/ee2457ece7464cf348004411bb7d665c75db801f/emotion/15.gif -------------------------------------------------------------------------------- /emotion/16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yrdr/liteditor/ee2457ece7464cf348004411bb7d665c75db801f/emotion/16.gif -------------------------------------------------------------------------------- /emotion/17.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yrdr/liteditor/ee2457ece7464cf348004411bb7d665c75db801f/emotion/17.gif -------------------------------------------------------------------------------- /emotion/18.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yrdr/liteditor/ee2457ece7464cf348004411bb7d665c75db801f/emotion/18.gif -------------------------------------------------------------------------------- /emotion/19.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yrdr/liteditor/ee2457ece7464cf348004411bb7d665c75db801f/emotion/19.gif -------------------------------------------------------------------------------- /emotion/20.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yrdr/liteditor/ee2457ece7464cf348004411bb7d665c75db801f/emotion/20.gif -------------------------------------------------------------------------------- /emotion/21.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yrdr/liteditor/ee2457ece7464cf348004411bb7d665c75db801f/emotion/21.gif -------------------------------------------------------------------------------- /emotion/22.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yrdr/liteditor/ee2457ece7464cf348004411bb7d665c75db801f/emotion/22.gif -------------------------------------------------------------------------------- /emotion/23.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yrdr/liteditor/ee2457ece7464cf348004411bb7d665c75db801f/emotion/23.gif -------------------------------------------------------------------------------- /emotion/24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yrdr/liteditor/ee2457ece7464cf348004411bb7d665c75db801f/emotion/24.gif -------------------------------------------------------------------------------- /emotion/25.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yrdr/liteditor/ee2457ece7464cf348004411bb7d665c75db801f/emotion/25.gif -------------------------------------------------------------------------------- /emotion/26.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yrdr/liteditor/ee2457ece7464cf348004411bb7d665c75db801f/emotion/26.gif -------------------------------------------------------------------------------- /emotion/27.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yrdr/liteditor/ee2457ece7464cf348004411bb7d665c75db801f/emotion/27.gif -------------------------------------------------------------------------------- /emotion/28.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yrdr/liteditor/ee2457ece7464cf348004411bb7d665c75db801f/emotion/28.gif -------------------------------------------------------------------------------- /emotion/29.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yrdr/liteditor/ee2457ece7464cf348004411bb7d665c75db801f/emotion/29.gif -------------------------------------------------------------------------------- /emotion/30.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yrdr/liteditor/ee2457ece7464cf348004411bb7d665c75db801f/emotion/30.gif -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | concat = require('gulp-concat'), 3 | uglify = require('gulp-uglify'), 4 | rename = require('gulp-rename'), 5 | del = require('del'); 6 | 7 | gulp.task('minifyjs', function() { 8 | return gulp.src('./static/js/editor.js') 9 | .pipe(rename({suffix: '.min'})) //rename压缩后的文件名 10 | .pipe(uglify()) //压缩 11 | .pipe(gulp.dest('./static/js')); //输出 12 | }); 13 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 编辑器 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 20 | -------------------------------------------------------------------------------- /static/css/main.css: -------------------------------------------------------------------------------- 1 | html,body{padding:0;margin:0} 2 | input{outline:none} 3 | table{border-collapse:collapse;table-layout:fixed} 4 | @font-face { 5 | font-family: 'iconfont'; 6 | src: url('//at.alicdn.com/t/font_1450424898_0892522.eot'); /* IE9*/ 7 | src: url('//at.alicdn.com/t/font_1450424898_0892522.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 8 | url('//at.alicdn.com/t/font_1450424898_0892522.woff') format('woff'), /* chrome、firefox */ 9 | url('//at.alicdn.com/t/font_1450424898_0892522.ttf') format('truetype'), /* chrome、firefox、opera、Safari, Android, iOS 4.2+*/ 10 | url('//at.alicdn.com/t/font_1450424898_0892522.svg#iconfont') format('svg'); /* iOS 4.1- */ 11 | } 12 | .iconfont{font-style:normal;font-family:"iconfont"} 13 | 14 | .editor{min-width:250px;min-height:200px;margin:50px;width:800px;border:1px solid #ddd;box-shadow:0 0 15px #ccc} 15 | 16 | .editor .tools{padding:0 10px 8px 10px;border-bottom:1px solid #eee} 17 | .editor .tools button{padding:4px;border:0;color:#555;background:#fff; 18 | cursor: pointer;font-style:normal;font-family:"iconfont";font-size:20px;margin:8px 8px 0 8px;outline:none} 19 | .editor .tools button:hover{background:#eee} 20 | .editor .content{line-height:20px;height:100%;outline:none;padding:20px;font-size:14px} 21 | 22 | /*************************************fix*****************************************/ 23 | .editor .content ul,.editor .content ol{margin:0} 24 | .editor .content img{cursor:pointer} 25 | 26 | /*************************************dialog**************************************/ 27 | .dialog{position:fixed;z-index:1000;min-width:200px;min-height:100px;border:1px solid #eee;box-shadow: 0 0 6px #eee;background:#fff} 28 | .dialog .title{width:100%;height:40px;border-bottom:1px solid #eee;cursor:move} 29 | .dialog .title span,.dialog .title a{display:block;height:40px;padding-left:15px;line-height:40px;font-size:14px;float:left;cursor:pointer} 30 | .dialog .title a{padding:0 10px;border-right:1px solid #eee} 31 | .dialog .title a:hover,.dialog .title a.active{background:#51bade;color:#fff} 32 | .dialog .title i{display:block;padding-right:15px;height:40px;line-height:40px;float:right;cursor:pointer} 33 | .dialog .title i:hover{color:#488fce} 34 | .dialog .content{padding:15px} 35 | .dialog .confirm{padding:0 15px;height:42px;background:#fafafa} 36 | .dialog .confirm input{border:0;border-radius:4px;background:#ff9000;color:#fff;height:26px;padding:0 6px;float:right;margin:8px 0 0 10px;vertical-align:middle;outline:none;cursor:pointer} 37 | 38 | /**************************************tip****************************************/ 39 | .tip{position:fixed;height:36px;padding:0 15px;line-height:36px;background:#ff9000;color:#fff;font-size:14px;border-radius: 18px;z-index:10000} 40 | 41 | /**************************************表情****************************************/ 42 | .emotion img{width:25px;height:25px;padding:5px;cursor:pointer} 43 | 44 | /**************************************link****************************************/ 45 | .dialog .link{position:relative;height:80px} 46 | .dialog .link *{position:absolute} 47 | .dialog .link input[type="text"]{top:10px;width:100%;text-indent:5px;height:30px;border:1px solid #ddd} 48 | .dialog .link input[type="checkbox"]{top:60px;margin:0;padding:0} 49 | .dialog .link label{line-height:13px;top:60px;left:22px;font-size:13px;color:#555} 50 | 51 | /***************************************color**************************************/ 52 | .dialog .color input{margin:5px 0 5px 10px;width:17px;height:17px;border:1px solid #eee;cursor:pointer} 53 | 54 | /***************************************video**************************************/ 55 | .dialog .video label{font-size:13px;color:#777} 56 | .dialog .video input{width:100%;border:1px solid #ddd;height:28px;text-indent:8px;margin-top:15px} 57 | 58 | /***************************************table**************************************/ 59 | .dialog .table-num input{width:60px;float:left;margin:15px 10px 0 10px;height:28px;border:1px solid #ddd;text-align:center} 60 | .table{border:1px solid #eee} 61 | .table tr td{border:1px solid #eee;padding:8px 10px;min-width:50px;height:20px;line-height:20px;vertical-align: top} 62 | 63 | /**************************************upload*************************************/ 64 | .upload .tab{height:40px;margin-bottom:10px} 65 | .upload .tab span{display:block;font-size:13px;background:#e8e8e8;color:#777;padding:8px 12px;cursor:pointer;float:left} 66 | .upload .tab span.active{background:#ff9000;color:#fff} 67 | .upload .queue{border:1px solid #ddd;overflow-y:scroll;height:350px;word-break:break-all} 68 | .upload .queue .upload_img{display:inline-block;width:100px;height:100px;margin:10px 0 0 10px;overflow:hidden;position:relative} 69 | .upload .queue .upload_img img{height:100%;border:0;cursor:pointer} 70 | .upload .queue .upload_cover{position:absolute;text-align:center;line-height:100px;color:#fff;top:0;left:0;width:100px;height:100px;z-index:1001;background:#555;opacity:.5;filter:Alpha(opacity=50);} 71 | .upload input[type="text"]{width:100%;height:30px;border:1px solid #ddd;line-height:30px;outline:none;text-indent:6px} 72 | .upload .ibox{height:40px;border:1px solid #e8e8e8;margin-top:10px} 73 | .upload .ibox:first-child{margin-top:0} 74 | .upload .ibox input{width:70%;height:40px;border:0;outline:none;float:left} 75 | .upload .ibox span{display:inline-block;width:28%;height:40px;background:#eee;color:#555;border-left:1px solid #e8e8e8;text-align:center;line-height:40px;float:right} 76 | .upload .upload_local{position:relative} 77 | .upload .upload_net{positon:relative;padding-top:10px} 78 | .upload .upload_file{position:absolute;top:10px;width:80px;height:35px;opacity:0;filter:Alpha(opacity=0);z-index:1000} 79 | .upload .upload_btn{position:absolute;top:10px;width:80px;height:35px;border:0;line-height:35px;background:#488fce;color:#fff;text-align:center} 80 | .upload .fetch_btn{padding:0 10px;margin-top:10px;height:35px;border:0;line-height:35px;background:#488fce;color:#fff;text-align:center} -------------------------------------------------------------------------------- /static/js/editor.js: -------------------------------------------------------------------------------- 1 | (function(global, variable) { 2 | //使用严格模式 3 | 'use strict'; 4 | 5 | var Selector, Upload, Editor, tools, color, Dialog; 6 | 7 | Selector = function(selector, context) { 8 | return new Selector.prototype.__init__(selector, context); 9 | }; 10 | 11 | Selector.prototype = { 12 | //修正构造函数 13 | constructor : Selector, 14 | 15 | //初始化选择器 16 | __init__ : function(selector, context) { 17 | if (Selector.type(selector) === 'string') { 18 | selector = (context || global.document).querySelectorAll(selector); 19 | } else if (selector.nodeType) { 20 | selector = [selector]; 21 | } else { 22 | return this; 23 | } 24 | return Selector.extend(this, { 25 | '0' : selector.length > 1 ? selector : selector[0], 26 | length : selector.length, 27 | context : context 28 | }); 29 | }, 30 | 31 | //遍历节点数组 32 | each : function(fn) { 33 | return Selector.each(this.length > 1 ? this[0] : [this[0]], fn); 34 | }, 35 | 36 | //获取指定序列的节点 37 | eq : function(index) { 38 | return (index == 0 || this.length < 2) ? this : Selector(this[0][index]); 39 | }, 40 | 41 | //显示元素 delay:动画时间 42 | show : function() { 43 | if (this.length == 1) { 44 | this[0].style.display = ''; 45 | } else { 46 | Selector.eachCall('show', this[0]); 47 | } 48 | return this; 49 | }, 50 | 51 | //隐藏元素 52 | hide : function() { 53 | if (this.length == 1) { 54 | this[0].style.display = 'none'; 55 | } else { 56 | Selector.eachCall('hide', this[0]); 57 | } 58 | return this; 59 | }, 60 | 61 | //设置或获取attribute属性 62 | attr : function(name, value) { 63 | return typeof value !== 'string' ? this[0].getAttribute(name) : function(obj) { 64 | if (typeof name === 'string') { 65 | obj[0].setAttribute(name, value); 66 | } else { 67 | Selector.each(name, function(item, key) { 68 | obj[0].setAttribute(key, item); 69 | }); 70 | } 71 | return obj; 72 | }(this); 73 | }, 74 | 75 | //节点设置或获取css属性 76 | css : function(name, value) { 77 | return (typeof value === 'undefined' && typeof name === 'string') ? this[0].style[name] : function(obj) { 78 | if (typeof name === 'string') { 79 | obj[0].style[name] = value; 80 | } else { 81 | Selector.each(name, function(item, key) { 82 | obj[0].style[key] = item; 83 | }); 84 | } 85 | return obj; 86 | }(this); 87 | }, 88 | 89 | //设置或获取节点html内容 90 | html : function(html) { 91 | return typeof html !== 'string' ? this[0].innerHTML : function(obj) { 92 | obj[0].innerHTML = html; 93 | return obj; 94 | }(this); 95 | }, 96 | 97 | //添加子节点 98 | append : function() { 99 | var i, fragment, div; 100 | for (i = 0; i < arguments.length; i ++) { 101 | if (typeof arguments[i] === 'string') { 102 | fragment = Selector.htmlToFragment(arguments[i]); 103 | } else { 104 | fragment = arguments.constructor ? arguments[i][0] : arguments[i]; 105 | } 106 | this[0].appendChild(fragment); 107 | } 108 | return this; 109 | }, 110 | 111 | //将元素插入到指定节点 112 | appendTo : function(selector) { 113 | (selector[0] || selector).appendChild(this[0]); 114 | return this; 115 | }, 116 | 117 | //在指定节点前面插入节点 118 | before : function(node) { 119 | if (node && this[0].parentNode) { 120 | this[0].parentNode.insertBefore(node.constructor ? node[0] : node, this[0]); 121 | } 122 | return this; 123 | }, 124 | 125 | //设置或获取宽 126 | width : function(width) { 127 | return typeof width === 'undefined' ? parseInt(this[0].style.width || this[0].offsetWidth) : function(obj) { 128 | obj[0].style.width = width + 'px'; 129 | return obj; 130 | }(this); 131 | }, 132 | 133 | //设置或获取高 134 | height : function(height) { 135 | return typeof height === 'undefined' ? parseInt(this[0].style.height || this[0].offsetHeight) : function(obj) { 136 | obj[0].style.height = height + 'px'; 137 | return obj; 138 | }(this); 139 | }, 140 | 141 | //获取元素偏移位置 142 | offset : function(offset) { 143 | offset = offset || {top : 0, left : 0}; 144 | offset.top += this[0].offsetTop; 145 | offset.left += this[0].offsetLeft; 146 | if (this[0].offsetParent.tagName != 'BODY') { 147 | offset = this.offset(this[0].offsetParent, offset); 148 | } 149 | return offset; 150 | }, 151 | 152 | //绑定事件 153 | bind : function(type, fn) { 154 | Selector.addEvent(this[0], type, fn); 155 | return this; 156 | } 157 | }; 158 | 159 | Selector.htmlToFragment = function(html) { 160 | var fragment = global.document.createDocumentFragment(), 161 | div = this.create('div', { 162 | innerHTML : html 163 | }); 164 | while (div.firstChild) { 165 | fragment.appendChild(div.firstChild); 166 | } 167 | return fragment; 168 | }; 169 | 170 | //绑定事件 171 | Selector.addEvent = function(element, type, fn) { 172 | element.addEventListener(type, fn, false); 173 | }; 174 | 175 | //循环继承参数 176 | Selector.extend = function(target, source) { 177 | for (var key in source) { 178 | target[key] = source[key]; 179 | } 180 | return target; 181 | }; 182 | 183 | //检测参数类型 184 | Selector.type = function(args) { 185 | var type; 186 | if (args == null || args == 'undefined') { 187 | type = args; 188 | } else { 189 | type = Object.prototype.toString.call(args).split(' ')[1]; 190 | type = type.replace(']', '').toLowerCase(); 191 | if (type.indexOf('node') > -1) { 192 | type = 'node'; 193 | } 194 | } 195 | return type; 196 | }; 197 | 198 | //循环call自身 199 | Selector.eachCall = function(method, array) { 200 | Selector.each(array, function(item) { 201 | Selector.prototype[method].call([item]); 202 | }); 203 | }; 204 | 205 | //检测是否为数组 206 | Selector.isArray = function(args) { 207 | return this.type(args) === 'array'; 208 | }; 209 | 210 | //便利数组或对象 211 | Selector.each = function(data, fn) { 212 | for (var key in data) { 213 | fn.apply(data[key], [data[key], key]); 214 | } 215 | }; 216 | 217 | //创建元素 218 | Selector.create = function(element, args) { 219 | element = global.document.createElement(element); 220 | if (typeof args !== 'undefined') { 221 | var fn, key; 222 | fn = function(ele, list) { 223 | for (key in list) { 224 | //object 225 | if (typeof list[key] != 'string' && !list[key].length) { 226 | fn(ele[key], list[key]); 227 | } else { 228 | ele[key] = list[key]; 229 | } 230 | } 231 | }; 232 | fn(element, args); 233 | } 234 | return element; 235 | }; 236 | 237 | //判断元素是否是在指定数组中 238 | Selector.inArray = function(args, array) { 239 | var index = -1, i; 240 | if (Array.prototype.indexOf) { 241 | array.indexOf(args); 242 | } else { 243 | for (i = 0; i < array.length; i ++) { 244 | if (array[i] === args) { 245 | index = i; 246 | break; 247 | } 248 | } 249 | } 250 | return index; 251 | }; 252 | 253 | //元素包含检测 254 | Selector.contains = function(parentNode, childNode) 255 | { 256 | if ('contains' in parentNode) { 257 | return parentNode.contains(childNode); 258 | } else if ('compareDocumentPosition' in parentNode) { 259 | return parentNode.compareDocumentPosition(childNode) % 16; 260 | } else { 261 | return false; 262 | } 263 | }; 264 | 265 | //创建ajax执行方法 266 | Selector.extend(Selector, (function(Selector) { 267 | //创建Ajax请求 268 | function createXMLHttpRequest() 269 | { 270 | var request; 271 | try { 272 | request = new ActiveXObject("Msxml2.XMLHTTP");//IE高版本创建XMLHTTP 273 | } catch(e) { 274 | try { 275 | request = new ActiveXObject("Microsoft.XMLHTTP");//IE低版本创建XMLHTTP 276 | } catch(e) { 277 | request = new XMLHttpRequest();//兼容非IE浏览器,直接创建XMLHTTP对象 278 | } 279 | } 280 | return request; 281 | } 282 | 283 | //发送ajax请求 284 | function sendAjaxRequest(options, request) 285 | { 286 | options.data = analyzeAjaxData(options.method, options.data); 287 | if (options.method == 'get' && options.data) { 288 | options.url += (/\?/.test(options.url) ? '&' : '?') + options.data; 289 | } 290 | request.open(options.method, options.url, options.async); 291 | request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); 292 | request.onreadystatechange = function() { 293 | processResponse(options.success, options.error, options.type, request); 294 | }; 295 | request.send(options.data); 296 | } 297 | 298 | //分析数据 299 | function analyzeAjaxData(method, data) 300 | { 301 | if (!data || data.length < 1) return null; 302 | var _data = []; 303 | for (var key in data) _data.push(key + '=' + encodeContent(data[key])); 304 | return _data.join('&'); 305 | } 306 | 307 | //回调函数,判断请求执行成功还是失败,并调用对应的函数 308 | function processResponse(success, error, type, request) 309 | { 310 | if (request.readyState == 4) { 311 | if (request.status == 200) { 312 | var text = request.response || request.responseText; 313 | //实现回调 314 | if (type.toLowerCase() == 'json') { 315 | try { 316 | text = JSON.parse(text); 317 | } catch (e) {} 318 | } 319 | success(text, 'success'); 320 | } else { 321 | error(request.readyState, request.status); 322 | } 323 | } 324 | } 325 | 326 | //特殊字符转义 327 | function encodeContent(data){ 328 | if (/^data:image\/\w+;base64/i.test(data)) { 329 | return data.replace(/\+/g, "%2B").replace(/\&/g, "%26"); 330 | } 331 | return encodeURI(data).replace(/&/g,'%26').replace(/\+/g,'%2B').replace(/\s/g,'%20').replace(/#/g,'%23'); 332 | } 333 | 334 | return { 335 | ajax : function(options) { 336 | sendAjaxRequest(Selector.extend({ 337 | method : 'get', 338 | url : null, 339 | async : true, 340 | data : null, 341 | type : 'json', 342 | success : function() {}, 343 | error : function() {} 344 | }, options), createXMLHttpRequest()); 345 | }, 346 | 347 | post : function(_url, _data, _fn, _type) { 348 | this.ajax({method : 'post', url : _url, data : _data, success : _fn, type : _type || 'json'}); 349 | }, 350 | 351 | get : function(_url, _data, _fn, _type) { 352 | if (_data instanceof Function) { 353 | _type = _fn; 354 | _fn = _data; 355 | _data = null; 356 | } 357 | this.ajax({method : 'get', url : _url, data : _data, success : _fn, type : _type || 'json'}); 358 | } 359 | }; 360 | }(Selector))); 361 | 362 | Selector.prototype.__init__.prototype = Selector.prototype; 363 | 364 | 365 | /****************************图片上传*************************/ 366 | 367 | Upload = function(options) { 368 | this.options = Selector.extend({ 369 | //允许图片的格式 370 | allow_ext : ['jpeg', 'jpg', 'png', 'gif'], 371 | //图片最大尺寸 372 | maxsize : 2048, 373 | //上传图片的地址(post) 374 | upload_url : 'upload.php', 375 | //抓取远程图片的地址 376 | fetch_url : 'fetch.php', 377 | //开始上传前事件 378 | uploadBefore : function(file, index){}, 379 | //回调函数data为json格式 380 | callback : function(data, index){} 381 | }, options); 382 | }; 383 | 384 | Upload.prototype = { 385 | //修正构造函数 386 | constructor : Upload, 387 | 388 | count : 1, 389 | 390 | //检测图片后缀 391 | checkSubfix : function(filename) { 392 | return Selector.inArray( 393 | filename.substring(filename.lastIndexOf('.') + 1).toLowerCase(), 394 | this.options.allow_ext 395 | ); 396 | }, 397 | 398 | //检测图片地址是否合法 399 | checkHost : function(url) { 400 | return /^(https?:)?\/\/\w+\.(feidee|cardniu)\.(com|net)/i.test(url); 401 | }, 402 | 403 | //上传图片主要方法 404 | startUpload : function(file) { 405 | var reader = new FileReader(), _this = this; 406 | reader.readAsDataURL(file); 407 | reader.onload = function() { 408 | Selector.post(_this.options.upload_url, {'file' : this.result}, function(data) { 409 | if (data.code == 1) { 410 | _this.options.callback(data.items, file.index); 411 | } 412 | }); 413 | reader = null; 414 | } 415 | }, 416 | 417 | reader : function(file) { 418 | var reader = new FileReader(), _this = this; 419 | reader.readAsDataURL(file); 420 | reader.onload = function() { 421 | _this.options.uploadBefore(this.result, file.index); 422 | reader = null; 423 | } 424 | }, 425 | 426 | //开始上传图片 427 | upload : function(files) { 428 | var i; 429 | for (i = 0; i < files.length; i ++) { 430 | if (this.checkSubfix(files[i].name)) { 431 | files[i].index = this.count; 432 | this.count++; 433 | this.reader(files[i]); 434 | } 435 | } 436 | for (i = 0; i < files.length; i ++) { 437 | if (this.checkSubfix(files[i].name)) { 438 | this.startUpload(files[i]); 439 | } 440 | } 441 | }, 442 | 443 | //获取站外图片 444 | fetch : function(url) { 445 | if (this.checkSubfix(url) && this.checkHost(url)) { 446 | this.options.callback({code : 1, url : url}); 447 | } 448 | } 449 | }; 450 | 451 | /****************************弹出框部分************************/ 452 | 453 | Dialog = function(options) { 454 | return new Dialog.prototype.__init__(options); 455 | }; 456 | 457 | Dialog.prototype = { 458 | //修正构造函数 459 | constructor : Dialog, 460 | //初始化本类 461 | __init__ : function(options) { 462 | this.options = Selector.extend({ 463 | width : 200, 464 | height : 0, 465 | title : '通知', 466 | content : '', 467 | confirm : null 468 | }, options); 469 | this.create(); 470 | }, 471 | //创建弹出框 472 | create : function() { 473 | var _this = this; 474 | Selector.extend(this, { 475 | dialog : Selector.create('div', { 476 | className : 'dialog', 477 | innerHTML : '
' 478 | }), 479 | content : Selector.create('div', { 480 | className : 'content' 481 | }) 482 | }); 483 | this.dialog.appendChild(this.content); 484 | this.title = this.dialog.querySelector('.title span'); 485 | Selector.addEvent(this.dialog.querySelector('.title i'), 'click', function() { 486 | _this.hide(); 487 | }); 488 | this.setContent(this.options.content).setTitle(this.options.title); 489 | if (this.options.confirm) { 490 | this.confirm(this.options.confirm); 491 | } 492 | return this; 493 | }, 494 | //设置标题 495 | setTitle : function(title) { 496 | this.title.innerHTML = title; 497 | return this; 498 | }, 499 | //设置内容 500 | setContent : function(content) { 501 | if (typeof content === 'string') { 502 | this.content.innerHTML = content; 503 | } else { 504 | this.content.innerHTML = ''; 505 | this.content.appendChild(content); 506 | } 507 | return this; 508 | }, 509 | //添加确认按钮 510 | confirm : function(callback) { 511 | var wrapper; 512 | wrapper = Selector.create('div', { 513 | className : 'confirm' 514 | }); 515 | this.confirm = Selector.create('input', { 516 | type : 'button', 517 | value : '确定' 518 | }); 519 | wrapper.appendChild(this.confirm); 520 | this.dialog.appendChild(wrapper); 521 | Selector.addEvent(this.confirm, 'click', function() { 522 | callback(); 523 | }); 524 | return this; 525 | }, 526 | //设置属性 527 | setAttribute : function() { 528 | if (this.options.height > 50) { 529 | this.content.style.height = this.options.height + 'px'; 530 | } 531 | Selector.extend(this.dialog.style, { 532 | width : this.options.width + 'px', 533 | top : '100px', 534 | left : (global.screen.availWidth - this.options.width) / 2 + 'px' 535 | }); 536 | global.document.body.appendChild(this.dialog); 537 | return true; 538 | }, 539 | //显示弹出层 540 | show : function() { 541 | if (!this.exist) { 542 | this.exist = this.setAttribute(); 543 | } 544 | this.dialog.style.display = ''; 545 | return this; 546 | }, 547 | //隐藏弹出层 548 | hide : function() { 549 | this.dialog.style.display = 'none'; 550 | return this; 551 | } 552 | }; 553 | 554 | Dialog.prototype.__init__.prototype = Dialog.prototype; 555 | 556 | //遮罩效果 557 | Dialog.cover = function() { 558 | function createCover() { 559 | return Selector.create('div', { 560 | className : 'cover', 561 | style : { 562 | height : global.screen.availHeight + 'px' 563 | } 564 | }); 565 | } 566 | return { 567 | //显示遮罩 568 | show : function() { 569 | if (!createCover.cover) { 570 | createCover.cover = Selector(createCover()).appendTo(global.document.body); 571 | } 572 | createCover.cover.show(); 573 | }, 574 | //隐藏遮罩 575 | hide : function() { 576 | createCover.cover.hide(); 577 | } 578 | } 579 | }(); 580 | 581 | Dialog.tip = function() { 582 | function createTip() { 583 | return Selector.create('div', {className : 'tip'}); 584 | } 585 | return function(message) { 586 | if (!createTip.tip) { 587 | createTip.tip = Selector(createTip()).appendTo(global.document.body); 588 | } 589 | createTip.tip.html(message).show().css({ 590 | top : '100px', 591 | left : (global.document.body.offsetWidth - createTip.tip.width()) / 2 + 'px' 592 | }); 593 | setTimeout(function() { 594 | createTip.tip.hide(); 595 | }, 2500); 596 | }; 597 | }(); 598 | 599 | /****************************编辑器部分************************/ 600 | 601 | //可选颜色列表 602 | color = [ 603 | '#000000', '#fff', '#000066', '#000099', '#0000CC', '#0000FF', 604 | '#0033CC', '#0033FF', '#006600', '#006633', '#006666', '#006699', 605 | '#009966', '#009999', '#0099CC', '#0099FF', '#00CC00', '#00CC33', 606 | '#666600', '#666666', '#6666CC', '#669900', '#669966', '#6699CC', 607 | '#66CCCC', '#66CCFF', '#66FF00', '#66FF99', '#66FFFF', '#990000', 608 | '#996600', '#996666', '#9966CC', '#9966FF', '#999900', '#999966', 609 | 'red', '#CC3366', '#CC9966', '#CC9999', '#CCCC00', '#F78B00' 610 | ]; 611 | 612 | //可选工具列表 613 | tools = [ 614 | {name : 'cleanFormat', icon : '', tip : '清除格式'}, 615 | {name : 'justifyLeft', icon : '', tip : '左对齐'}, 616 | {name : 'justifyCenter', icon : '', tip : '居中对齐'}, 617 | {name : 'justifyRight', icon : '', tip : '右对齐'}, 618 | {name : 'bold', icon : '', tip : '加粗'}, 619 | {name : 'table', icon : '', tip : '表格'}, 620 | {name : 'insertUnorderedList', icon : '', tip : '无序列表'}, 621 | {name : 'insertOrderedList', icon : '', tip : '有序列表'}, 622 | {name : 'insertImage', icon : '', tip : '图片'}, 623 | {name : 'color', icon : '', tip : '文字颜色'}, 624 | {name : 'createLink', icon : '', tip : '超链接'}, 625 | {name : 'unLink', icon : '', tip : '取消超链接'}, 626 | {name : 'video', icon : '', tip : '视频'}, 627 | {name : 'emotion', icon : '', tip : '表情'}, 628 | {name : 'undo', icon : '', tip : '撤销操作'} 629 | ]; 630 | 631 | Editor = function(options) { 632 | this.options = Selector.extend({ 633 | //存放内容的文本域 634 | textarea : null, 635 | //编辑器宽,默认为textarea的宽度 636 | width : null, 637 | //编辑器高,默认为textarea的高度 638 | height : null, 639 | //是否允许插入图片 640 | allowImage : true, 641 | //是否允许上传图片 642 | allowUploadImage : true, 643 | //上传图片地址 644 | uploadImageUrl : null, 645 | //显示的组件列表,默认显示全部 646 | tools : null 647 | }, options); 648 | 649 | //存储零散数据 650 | this.data = {}; 651 | 652 | if (typeof this.options.textarea === 'string') { 653 | this.options.textarea = Selector(this.options.textarea); 654 | } 655 | Selector.extend(this, initialization(this.options)); 656 | //绑定工具栏事件 657 | bindToolsEvent(this.tools, this); 658 | //绑定内容事件 659 | bindContentEvent(this.content, this); 660 | }; 661 | 662 | //初始化编辑器 663 | function initialization(options) 664 | { 665 | var editor = { 666 | editor : Selector(Selector.create('div', { 667 | className : 'editor' 668 | })), 669 | tools : Selector(Selector.create('div', { 670 | className : 'tools', 671 | unSelectable : 'on', 672 | innerHTML : createTools(options.tools) 673 | })), 674 | content : Selector(Selector.create('div', { 675 | className : 'content', 676 | contentEditable : 'true' 677 | })) 678 | }; 679 | editor.editor.append(editor.tools, editor.content); 680 | //修复编辑器高 681 | editor.content.css('minHeight', global.document.body.clientHeight + 'px'); 682 | options.textarea.before(editor.editor); 683 | options.textarea.hide(); 684 | return editor; 685 | } 686 | 687 | //初始化工具栏 688 | function createTools(items) 689 | { 690 | var key, html = ''; 691 | for (key in tools) { 692 | if (!items || !items.length || Selector.inArray(tools[key].name, items) > -1) { 693 | html += ''; 694 | } 695 | } 696 | return html; 697 | } 698 | 699 | //绑定工具栏事件 700 | function bindToolsEvent(tools, editor) 701 | { 702 | tools.bind('click', function(e) { 703 | if (e.target.tagName == 'BUTTON') { 704 | (e.target.name in editor) && editor[e.target.name](); 705 | } 706 | editor.saveRange(); 707 | }); 708 | } 709 | 710 | //绑定内容事件 711 | function bindContentEvent(content, editor) 712 | { 713 | var selection, abort, target; 714 | content.bind('click', function(e) { 715 | selection = global.getSelection(); 716 | abort = false; 717 | target = e.target; 718 | while (target != editor.content[0] && !abort) { 719 | switch (target.tagName) { 720 | case 'A': 721 | selection.selectAllChildren(target); 722 | editor.createLink(target.href, target.target == '_blank'); 723 | e.preventDefault(); 724 | abort = true; 725 | break; 726 | } 727 | target = target.parentNode; 728 | } 729 | editor.saveRange(selection.getRangeAt(0)); 730 | }); 731 | } 732 | 733 | //创建链接操作弹层 734 | function createLinkDialog() 735 | { 736 | return { 737 | link : Selector.create('div', { 738 | className : 'link' 739 | }), 740 | input : Selector.create('input', { 741 | type : 'text', 742 | placeholder : '请输入链接' 743 | }), 744 | checkbox : Selector.create('input', { 745 | type : 'checkbox' 746 | }), 747 | label : Selector.create('label', { 748 | innerHTML : '新窗口打开' 749 | }) 750 | }; 751 | } 752 | 753 | //创建表情 754 | function createEmotionDialog() 755 | { 756 | var html = '', i; 757 | for (i = 1; i < 31; i ++) { 758 | html += ''; 759 | } 760 | return html; 761 | } 762 | 763 | //创建上传弹出框 764 | function createUploadDialog() 765 | { 766 | var html = ''; 767 | html += '
上传图片网络图片
'; 768 | html += '
'; 769 | html += '
'; 770 | html += ''; 771 | html += ''; 772 | html += '
'; 773 | html += ''; 777 | return html; 778 | } 779 | 780 | Editor.prototype = { 781 | //修正构造函数 782 | constructor : Editor, 783 | 784 | //给当前输入域聚焦 785 | focus : function() { 786 | this.content[0].focus(); 787 | return this; 788 | }, 789 | 790 | //获取selection 791 | getSelection : function() { 792 | return global.getSelection(); 793 | }, 794 | 795 | //保存选择内容 796 | saveRange : function(range) { 797 | this.data.range = range ? range.cloneRange() : function(selection, obj) { 798 | return selection.rangeCount ? selection.getRangeAt(0).cloneRange() : (obj.data.range || null); 799 | }(this.getSelection(), this); 800 | return this; 801 | }, 802 | 803 | //恢复选择内容 804 | recoveryRange : function() { 805 | //如果存在range,则恢复 806 | this.focus(); 807 | if ( 808 | this.data.range && 809 | ( 810 | this.data.range.commonAncestorContainer == this.content[0] || 811 | this.isContains(this.content[0], this.data.range.commonAncestorContainer) 812 | ) 813 | ) { 814 | this.data.selection = this.getSelection(); 815 | this.data.selection.removeAllRanges(); 816 | this.data.selection.addRange(this.data.range); 817 | this.data.range = this.data.selection.getRangeAt(0); 818 | } 819 | return this; 820 | }, 821 | 822 | //执行execCommand相关命令 823 | execCommand : function(command, bool, value) { 824 | return global.document.execCommand(command, bool || false, value || null); 825 | }, 826 | 827 | //在range处插入 828 | insertAtRange : function(source, selection) { 829 | var range; 830 | if (typeof source === 'string') { 831 | source = Selector.htmlToFragment(source); 832 | } 833 | selection = selection || this.getSelection(); 834 | range = selection.getRangeAt(0); 835 | range.deleteContents(); 836 | range.insertNode(source); 837 | selection.addRange(range); 838 | return this; 839 | }, 840 | 841 | //寻找父节点 842 | searchParent : function(node, father) { 843 | if (typeof father === 'string') { 844 | if (node && !node.tagName || node.tagName != father && node.parentNode) { 845 | node = this.searchParent(node.parentNode, father); 846 | } 847 | } else { 848 | if (node != father && node.parentNode) { 849 | node = this.searchParent(node.parentNode, father); 850 | } 851 | } 852 | return (typeof father === 'string' ? node.tagName : node) == father ? node : null; 853 | }, 854 | 855 | //是否包含指定子节点 856 | isContains : function(node, child) { 857 | if (typeof child === 'string') { 858 | if (node.tagName != child && node.firstChild) { 859 | node = node.querySelector(child); 860 | } else if (node.tagName != child) { 861 | node = null; 862 | } 863 | } else if (node != child) { 864 | node = Selector.contains(node, child); 865 | } 866 | return node ? true : false; 867 | }, 868 | 869 | //获取编辑器内容 870 | getContent : function() { 871 | return this.content.html(); 872 | }, 873 | 874 | //设置编辑器内容 875 | setContent : function(content) { 876 | this.content.html(content); 877 | return this; 878 | }, 879 | 880 | //清除格式 881 | cleanFormat : function() { 882 | var selection, range, fragment; 883 | selection = this.getSelection(); 884 | fragment = Selector.htmlToFragment(selection.toString()); 885 | range = selection.getRangeAt(0); 886 | range.deleteContents(); 887 | range.insertNode(fragment); 888 | selection.addRange(range); 889 | return this.execCommand('RemoveFormat'); 890 | }, 891 | 892 | //左对齐 893 | justifyLeft : function() { 894 | this.execCommand('justifyLeft'); 895 | return this; 896 | }, 897 | 898 | //居中对齐 899 | justifyCenter : function() { 900 | this.execCommand('justifyCenter'); 901 | return this; 902 | }, 903 | 904 | //右对其 905 | justifyRight : function() { 906 | this.execCommand('justifyRight'); 907 | return this; 908 | }, 909 | 910 | //加粗 911 | bold : function() { 912 | this.execCommand('bold', false, []); 913 | return this; 914 | }, 915 | 916 | //添加表格 917 | table : function() { 918 | if (!this.data.tableDialog) { 919 | var _this = this, row, col, html = '', r, c; 920 | this.data.tableDialog = Dialog({ 921 | title : '表格', 922 | width : 150, 923 | height : 60, 924 | content : '
' 925 | }); 926 | this.data.tableDialog.confirm(function() { 927 | row = _this.data.tableDialog.content.querySelector('.row').value; 928 | col = _this.data.tableDialog.content.querySelector('.col').value; 929 | if (row < 1 || col < 1 || col > 5) { 930 | Dialog.tip('行和列必须大于0,且列数不得大于5'); 931 | } else { 932 | html = ''; 933 | for (r = 0; r < row; r ++) { 934 | html += ''; 935 | for (c = 0; c < col; c ++) { 936 | html += ''; 937 | } 938 | html += ''; 939 | } 940 | html += '
'; 941 | _this.recoveryRange(); 942 | _this.insertAtRange(html); 943 | } 944 | }); 945 | } 946 | this.data.tableDialog.show(); 947 | }, 948 | 949 | //添加无序列表 950 | insertUnorderedList : function() { 951 | this.execCommand('insertUnorderedList', false, null); 952 | }, 953 | 954 | //添加有序列表 955 | insertOrderedList : function() { 956 | this.execCommand('insertOrderedList', false, null); 957 | }, 958 | 959 | //添加图片 960 | insertImage : function() { 961 | if (!this.data.upload) { 962 | var upload = Selector.create('div', { 963 | className : 'upload', 964 | innerHTML : createUploadDialog() 965 | }), _this = this; 966 | this.data.upload = Dialog({ 967 | title : '添加图片', 968 | width : 500, 969 | height : 500, 970 | content : upload 971 | }); 972 | this.data.uploadDialog = { 973 | queue : upload.querySelector('.queue'), 974 | upload_local : upload.querySelector('.upload_local'), 975 | upload_net : upload.querySelector('.upload_net'), 976 | fetch_url : upload.querySelector('.fetch_url') 977 | }; 978 | Selector.addEvent(upload.querySelector('.tab'), 'click', function(e) { 979 | var brother; 980 | if (e.target.tagName == 'SPAN') { 981 | brother = e.target.nextSibling || e.target.previousSibling; 982 | e.target.className = 'active'; 983 | brother.className = ''; 984 | _this.data.uploadDialog['upload_' + brother.getAttribute('rel')].style.display = 'none'; 985 | _this.data.uploadDialog['upload_' + e.target.getAttribute('rel')].style.display = ''; 986 | } 987 | }); 988 | //选中了文件 989 | Selector.addEvent(upload.querySelector('.upload_file'), 'change', function() { 990 | var img; 991 | if (!_this.data.uploadContext) { 992 | _this.data.uploadContext = new Upload({ 993 | uploadBefore : function(file, index) { 994 | _this.data.uploadDialog.queue.innerHTML += '
加载中...
' 995 | }, 996 | callback : function(file, index) { 997 | img = _this.data.uploadDialog.queue.querySelector('img[data-index="' + index + '"]'); 998 | img.dataset.src = file; 999 | img.nextSibling.style.display = 'none'; 1000 | } 1001 | }); 1002 | } 1003 | _this.data.uploadContext.upload(this.files); 1004 | }); 1005 | //获取图片事件 1006 | Selector.addEvent(upload.querySelector('.fetch_btn'), 'click', function() { 1007 | if (_this.data.uploadDialog.fetch_url.value) { 1008 | _this.data.uploadDialog.queue.innerHTML += '
'; 1009 | } else { 1010 | Dialog.tip('请输入正确的图片地址'); 1011 | } 1012 | }); 1013 | //图片点击事件 1014 | Selector.addEvent(this.data.uploadDialog.queue, 'click', function(e) { 1015 | if (e.target.tagName == 'IMG') { 1016 | _this.insertAtRange(''); 1017 | } 1018 | }); 1019 | } 1020 | this.data.upload.show(); 1021 | }, 1022 | 1023 | //上传图片实现方法 1024 | uploadImage : function() { 1025 | if (!this.data.upload) { 1026 | this.data.upload = Dialog({ 1027 | title : '添加图片', 1028 | width : 500, 1029 | height : 300, 1030 | content : '' 1031 | }); 1032 | } 1033 | this.data.show(); 1034 | }, 1035 | 1036 | //设置文字颜色 1037 | color : function() { 1038 | if (!this.data.colorDialog) { 1039 | var html = '
', _this = this; 1040 | Selector.each(color, function(item) { 1041 | html += ''; 1042 | }); 1043 | this.data.colorDialog = Dialog({ 1044 | title : '调色板', 1045 | width : 200, 1046 | content : html 1047 | }); 1048 | Selector.addEvent(this.data.colorDialog.content.querySelector('.color'), 'click', function(e) { 1049 | _this.execCommand('foreColor', false, e.target.style.background); 1050 | }); 1051 | } 1052 | this.data.colorDialog.show(); 1053 | }, 1054 | 1055 | //链接操作框 1056 | createLinkCommand : function(url, target) { 1057 | var selection; 1058 | this.recoveryRange(); 1059 | this.execCommand('createLink', false, url); 1060 | selection = this.getSelection(); 1061 | if (target && selection.anchorNode.parentNode.tagName == 'A') { 1062 | selection.anchorNode.parentNode.setAttribute('target', '_blank'); 1063 | } 1064 | this.data.link.hide(); 1065 | }, 1066 | 1067 | //添加链接 1068 | createLink : function(url, checked) { 1069 | if (!this.data.link) { 1070 | var _this = this, data = {}; 1071 | Selector.extend(data, createLinkDialog()); 1072 | data.link.appendChild(data.input); 1073 | data.link.appendChild(data.checkbox); 1074 | data.link.appendChild(data.label); 1075 | this.data.link = Dialog({ 1076 | title : '添加链接', 1077 | width : 250, 1078 | height : 80, 1079 | content : data.link, 1080 | confirm : function() { 1081 | if (/\/\//.test(data.input.value)) { 1082 | _this.createLinkCommand(data.input.value, data.checkbox.checked); 1083 | } else { 1084 | Dialog.tip('链接格式错误'); 1085 | } 1086 | } 1087 | }); 1088 | this.data.linkvar = data; 1089 | } 1090 | url && (this.data.linkvar.input.value = url); 1091 | checked && (this.data.linkvar.checkbox.checked = true); 1092 | this.data.link.show(); 1093 | }, 1094 | 1095 | //移除链接 1096 | unLink : function() { 1097 | this.execCommand('unLink', false, null); 1098 | return this; 1099 | }, 1100 | 1101 | //插入视频 1102 | video : function() { 1103 | if (!this.data.videoDialog) { 1104 | var _this = this, url, id, video; 1105 | this.data.videoDialog = Dialog({ 1106 | title : '添加视频', 1107 | width : 400, 1108 | height : 80, 1109 | content : '
' 1110 | }); 1111 | this.data.videoDialog.confirm(function() { 1112 | url = _this.data.videoDialog.content.querySelector('.url').value; 1113 | video = ''; 1114 | if (url.length < 20) { 1115 | Dialog.tip('链接格式错误'); 1116 | } else if (/(https?:\/\/)?\w+\.youku\.com/i.test(url) && (id = url.match(/id_(\w+)/i)) && id[1]) { 1117 | video = 'http://player.youku.com/embed/' + id[1]; 1118 | } else if (/(https?:\/\/)?\w+\.qq\.com/i.test(url) && (id = url.match(/vid=(\w+)/i)) && id[1]) { 1119 | video = 'http://v.qq.com/iframe/player.html?vid=' + id[1] + '&tiny=0&auto=0'; 1120 | } else { 1121 | Dialog.tip('暂时只支持优酷和腾讯视频'); 1122 | } 1123 | if (video) { 1124 | _this.recoveryRange(); 1125 | _this.insertAtRange(''); 1126 | } 1127 | }); 1128 | } 1129 | this.data.videoDialog.show(); 1130 | }, 1131 | 1132 | //插入表情 1133 | emotion : function() { 1134 | if (!this.data.emotion) { 1135 | var emotion = Selector.create('div', { 1136 | className : 'emotion', 1137 | innerHTML : createEmotionDialog() 1138 | }), _this = this; 1139 | this.data.emotion = Dialog({ 1140 | title : '添加表情', 1141 | width : 240, 1142 | content : emotion 1143 | }); 1144 | Selector.addEvent(emotion, 'click', function(e) { 1145 | _this.saveRange(); 1146 | if (e.target.tagName == 'IMG') { 1147 | _this.recoveryRange(); 1148 | _this.execCommand('insertImage', false, e.target.src); 1149 | } 1150 | }); 1151 | } 1152 | this.data.emotion.show(); 1153 | }, 1154 | 1155 | //撤销操作 1156 | undo : function() { 1157 | //待定功能 1158 | this.execCommand('undo'); 1159 | } 1160 | }; 1161 | 1162 | global[variable] = function(options) { 1163 | return new Editor(options).focus().saveRange(); 1164 | } 1165 | 1166 | }(window, 'Editor')); 1167 | -------------------------------------------------------------------------------- /static/js/editor.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"use strict";function n(e){var n={editor:l(l.create("div",{className:"editor"})),tools:l(l.create("div",{className:"tools",unSelectable:"on",innerHTML:i(e.tools)})),content:l(l.create("div",{className:"content",contentEditable:"true"}))};return n.editor.append(n.tools,n.content),n.content.css("minHeight",t.document.body.clientHeight+"px"),e.textarea.before(n.editor),e.textarea.hide(),n}function i(t){var e,n="";for(e in h)(!t||!t.length||l.inArray(h[e].name,t)>-1)&&(n+='");return n}function o(t,e){t.bind("click",function(t){"BUTTON"==t.target.tagName&&t.target.name in e&&e[t.target.name](),e.saveRange()})}function a(e,n){var i,o,a;e.bind("click",function(e){for(i=t.getSelection(),o=!1,a=e.target;a!=n.content[0]&&!o;){switch(a.tagName){case"A":i.selectAllChildren(a),n.createLink(a.href,"_blank"==a.target),e.preventDefault(),o=!0}a=a.parentNode}n.saveRange(i.getRangeAt(0))})}function r(){return{link:l.create("div",{className:"link"}),input:l.create("input",{type:"text",placeholder:"请输入链接"}),checkbox:l.create("input",{type:"checkbox"}),label:l.create("label",{innerHTML:"新窗口打开"})}}function s(){var t,e="";for(t=1;31>t;t++)e+='';return e}function c(){var t="";return t+='
上传图片网络图片
',t+='
',t+='
',t+='',t+='',t+="
",t+='"}var l,u,d,h,p,f;l=function(t,e){return new l.prototype.__init__(t,e)},l.prototype={constructor:l,__init__:function(e,n){if("string"===l.type(e))e=(n||t.document).querySelectorAll(e);else{if(!e.nodeType)return this;e=[e]}return l.extend(this,{0:e.length>1?e:e[0],length:e.length,context:n})},each:function(t){return l.each(this.length>1?this[0]:[this[0]],t)},eq:function(t){return 0==t||this.length<2?this:l(this[0][t])},show:function(){return 1==this.length?this[0].style.display="":l.eachCall("show",this[0]),this},hide:function(){return 1==this.length?this[0].style.display="none":l.eachCall("hide",this[0]),this},attr:function(t,e){return"string"!=typeof e?this[0].getAttribute(t):function(n){return"string"==typeof t?n[0].setAttribute(t,e):l.each(t,function(t,e){n[0].setAttribute(e,t)}),n}(this)},css:function(t,e){return"undefined"==typeof e&&"string"==typeof t?this[0].style[t]:function(n){return"string"==typeof t?n[0].style[t]=e:l.each(t,function(t,e){n[0].style[e]=t}),n}(this)},html:function(t){return"string"!=typeof t?this[0].innerHTML:function(e){return e[0].innerHTML=t,e}(this)},append:function(){var t,e;for(t=0;t-1&&(e="node")),e},l.eachCall=function(t,e){l.each(e,function(e){l.prototype[t].call([e])})},l.isArray=function(t){return"array"===this.type(t)},l.each=function(t,e){for(var n in t)e.apply(t[n],[t[n],n])},l.create=function(e,n){if(e=t.document.createElement(e),"undefined"!=typeof n){var i,o;i=function(t,e){for(o in e)"string"==typeof e[o]||e[o].length?t[o]=e[o]:i(t[o],e[o])},i(e,n)}return e},l.inArray=function(t,e){var n,i=-1;if(Array.prototype.indexOf)e.indexOf(t);else for(n=0;n
'}),content:l.create("div",{className:"content"})}),this.dialog.appendChild(this.content),this.title=this.dialog.querySelector(".title span"),l.addEvent(this.dialog.querySelector(".title i"),"click",function(){t.hide()}),this.setContent(this.options.content).setTitle(this.options.title),this.options.confirm&&this.confirm(this.options.confirm),this},setTitle:function(t){return this.title.innerHTML=t,this},setContent:function(t){return"string"==typeof t?this.content.innerHTML=t:(this.content.innerHTML="",this.content.appendChild(t)),this},confirm:function(t){var e;return e=l.create("div",{className:"confirm"}),this.confirm=l.create("input",{type:"button",value:"确定"}),e.appendChild(this.confirm),this.dialog.appendChild(e),l.addEvent(this.confirm,"click",function(){t()}),this},setAttribute:function(){return this.options.height>50&&(this.content.style.height=this.options.height+"px"),l.extend(this.dialog.style,{width:this.options.width+"px",top:"100px",left:(t.screen.availWidth-this.options.width)/2+"px"}),t.document.body.appendChild(this.dialog),!0},show:function(){return this.exist||(this.exist=this.setAttribute()),this.dialog.style.display="",this},hide:function(){return this.dialog.style.display="none",this}},f.prototype.__init__.prototype=f.prototype,f.cover=function(){function e(){return l.create("div",{className:"cover",style:{height:t.screen.availHeight+"px"}})}return{show:function(){e.cover||(e.cover=l(e()).appendTo(t.document.body)),e.cover.show()},hide:function(){e.cover.hide()}}}(),f.tip=function(){function e(){return l.create("div",{className:"tip"})}return function(n){e.tip||(e.tip=l(e()).appendTo(t.document.body)),e.tip.html(n).show().css({top:"100px",left:(t.document.body.offsetWidth-e.tip.width())/2+"px"}),setTimeout(function(){e.tip.hide()},2500)}}(),p=["#000000","#fff","#000066","#000099","#0000CC","#0000FF","#0033CC","#0033FF","#006600","#006633","#006666","#006699","#009966","#009999","#0099CC","#0099FF","#00CC00","#00CC33","#666600","#666666","#6666CC","#669900","#669966","#6699CC","#66CCCC","#66CCFF","#66FF00","#66FF99","#66FFFF","#990000","#996600","#996666","#9966CC","#9966FF","#999900","#999966","red","#CC3366","#CC9966","#CC9999","#CCCC00","#F78B00"],h=[{name:"cleanFormat",icon:"",tip:"清除格式"},{name:"justifyLeft",icon:"",tip:"左对齐"},{name:"justifyCenter",icon:"",tip:"居中对齐"},{name:"justifyRight",icon:"",tip:"右对齐"},{name:"bold",icon:"",tip:"加粗"},{name:"table",icon:"",tip:"表格"},{name:"insertUnorderedList",icon:"",tip:"无序列表"},{name:"insertOrderedList",icon:"",tip:"有序列表"},{name:"insertImage",icon:"",tip:"图片"},{name:"color",icon:"",tip:"文字颜色"},{name:"createLink",icon:"",tip:"超链接"},{name:"unLink",icon:"",tip:"取消超链接"},{name:"video",icon:"",tip:"视频"},{name:"emotion",icon:"",tip:"表情"},{name:"undo",icon:"",tip:"撤销操作"}],d=function(t){this.options=l.extend({textarea:null,width:null,height:null,allowImage:!0,allowUploadImage:!0,uploadImageUrl:null,tools:null},t),this.data={},"string"==typeof this.options.textarea&&(this.options.textarea=l(this.options.textarea)),l.extend(this,n(this.options)),o(this.tools,this),a(this.content,this)},d.prototype={constructor:d,focus:function(){return this.content[0].focus(),this},getSelection:function(){return t.getSelection()},saveRange:function(t){return this.data.range=t?t.cloneRange():function(t,e){return t.rangeCount?t.getRangeAt(0).cloneRange():e.data.range||null}(this.getSelection(),this),this},recoveryRange:function(){return this.focus(),this.data.range&&(this.data.range.commonAncestorContainer==this.content[0]||this.isContains(this.content[0],this.data.range.commonAncestorContainer))&&(this.data.selection=this.getSelection(),this.data.selection.removeAllRanges(),this.data.selection.addRange(this.data.range),this.data.range=this.data.selection.getRangeAt(0)),this},execCommand:function(e,n,i){return t.document.execCommand(e,n||!1,i||null)},insertAtRange:function(t,e){var n;return"string"==typeof t&&(t=l.htmlToFragment(t)),e=e||this.getSelection(),n=e.getRangeAt(0),n.deleteContents(),n.insertNode(t),e.addRange(n),this},searchParent:function(t,e){return"string"==typeof e?(t&&!t.tagName||t.tagName!=e&&t.parentNode)&&(t=this.searchParent(t.parentNode,e)):t!=e&&t.parentNode&&(t=this.searchParent(t.parentNode,e)),("string"==typeof e?t.tagName:t)==e?t:null},isContains:function(t,e){return"string"==typeof e?t.tagName!=e&&t.firstChild?t=t.querySelector(e):t.tagName!=e&&(t=null):t!=e&&(t=l.contains(t,e)),t?!0:!1},getContent:function(){return this.content.html()},setContent:function(t){return this.content.html(t),this},cleanFormat:function(){var t,e,n;return t=this.getSelection(),n=l.htmlToFragment(t.toString()),e=t.getRangeAt(0),e.deleteContents(),e.insertNode(n),t.addRange(e),this.execCommand("RemoveFormat")},justifyLeft:function(){return this.execCommand("justifyLeft"),this},justifyCenter:function(){return this.execCommand("justifyCenter"),this},justifyRight:function(){return this.execCommand("justifyRight"),this},bold:function(){return this.execCommand("bold",!1,[]),this},table:function(){if(!this.data.tableDialog){var t,e,n,i,o=this,a="";this.data.tableDialog=f({title:"表格",width:150,height:60,content:'
'}),this.data.tableDialog.confirm(function(){if(t=o.data.tableDialog.content.querySelector(".row").value,e=o.data.tableDialog.content.querySelector(".col").value,1>t||1>e||e>5)f.tip("行和列必须大于0,且列数不得大于5");else{for(a='',n=0;t>n;n++){for(a+="",i=0;e>i;i++)a+="";a+=""}a+="
",o.recoveryRange(),o.insertAtRange(a)}})}this.data.tableDialog.show()},insertUnorderedList:function(){this.execCommand("insertUnorderedList",!1,null)},insertOrderedList:function(){this.execCommand("insertOrderedList",!1,null)},insertImage:function(){if(!this.data.upload){var t=l.create("div",{className:"upload",innerHTML:c()}),e=this;this.data.upload=f({title:"添加图片",width:500,height:500,content:t}),this.data.uploadDialog={queue:t.querySelector(".queue"),upload_local:t.querySelector(".upload_local"),upload_net:t.querySelector(".upload_net"),fetch_url:t.querySelector(".fetch_url")},l.addEvent(t.querySelector(".tab"),"click",function(t){var n;"SPAN"==t.target.tagName&&(n=t.target.nextSibling||t.target.previousSibling,t.target.className="active",n.className="",e.data.uploadDialog["upload_"+n.getAttribute("rel")].style.display="none",e.data.uploadDialog["upload_"+t.target.getAttribute("rel")].style.display="")}),l.addEvent(t.querySelector(".upload_file"),"change",function(){var t;e.data.uploadContext||(e.data.uploadContext=new u({uploadBefore:function(t,n){e.data.uploadDialog.queue.innerHTML+='
加载中...
'},callback:function(n,i){t=e.data.uploadDialog.queue.querySelector('img[data-index="'+i+'"]'),t.dataset.src=n,t.nextSibling.style.display="none"}})),e.data.uploadContext.upload(this.files)}),l.addEvent(t.querySelector(".fetch_btn"),"click",function(){e.data.uploadDialog.fetch_url.value?e.data.uploadDialog.queue.innerHTML+='
':f.tip("请输入正确的图片地址")}),l.addEvent(this.data.uploadDialog.queue,"click",function(t){"IMG"==t.target.tagName&&e.insertAtRange('')})}this.data.upload.show()},uploadImage:function(){this.data.upload||(this.data.upload=f({title:"添加图片",width:500,height:300,content:""})),this.data.show()},color:function(){if(!this.data.colorDialog){var t='
',e=this;l.each(p,function(e){t+=''}),this.data.colorDialog=f({title:"调色板",width:200,content:t}),l.addEvent(this.data.colorDialog.content.querySelector(".color"),"click",function(t){e.execCommand("foreColor",!1,t.target.style.background)})}this.data.colorDialog.show()},createLinkCommand:function(t,e){var n;this.recoveryRange(),this.execCommand("createLink",!1,t),n=this.getSelection(),e&&"A"==n.anchorNode.parentNode.tagName&&n.anchorNode.parentNode.setAttribute("target","_blank"),this.data.link.hide()},createLink:function(t,e){if(!this.data.link){var n=this,i={};l.extend(i,r()),i.link.appendChild(i.input),i.link.appendChild(i.checkbox),i.link.appendChild(i.label),this.data.link=f({title:"添加链接",width:250,height:80,content:i.link,confirm:function(){/\/\//.test(i.input.value)?n.createLinkCommand(i.input.value,i.checkbox.checked):f.tip("链接格式错误")}}),this.data.linkvar=i}t&&(this.data.linkvar.input.value=t),e&&(this.data.linkvar.checkbox.checked=!0),this.data.link.show()},unLink:function(){return this.execCommand("unLink",!1,null),this},video:function(){if(!this.data.videoDialog){var t,e,n,i=this;this.data.videoDialog=f({title:"添加视频",width:400,height:80,content:'
'}),this.data.videoDialog.confirm(function(){t=i.data.videoDialog.content.querySelector(".url").value,n="",t.length<20?f.tip("链接格式错误"):/(https?:\/\/)?\w+\.youku\.com/i.test(t)&&(e=t.match(/id_(\w+)/i))&&e[1]?n="http://player.youku.com/embed/"+e[1]:/(https?:\/\/)?\w+\.qq\.com/i.test(t)&&(e=t.match(/vid=(\w+)/i))&&e[1]?n="http://v.qq.com/iframe/player.html?vid="+e[1]+"&tiny=0&auto=0":f.tip("暂时只支持优酷和腾讯视频"),n&&(i.recoveryRange(),i.insertAtRange(''))})}this.data.videoDialog.show()},emotion:function(){if(!this.data.emotion){var t=l.create("div",{className:"emotion",innerHTML:s()}),e=this;this.data.emotion=f({title:"添加表情",width:240,content:t}),l.addEvent(t,"click",function(t){e.saveRange(),"IMG"==t.target.tagName&&(e.recoveryRange(),e.execCommand("insertImage",!1,t.target.src))})}this.data.emotion.show()},undo:function(){this.execCommand("undo")}},t[e]=function(t){return new d(t).focus().saveRange()}}(window,"Editor"); -------------------------------------------------------------------------------- /upload.php: -------------------------------------------------------------------------------- 1 | $code, 24 | 'msg' => $message, 25 | 'items' => $items 26 | ]); 27 | exit; 28 | } 29 | 30 | /** 31 | * 构造函数,检测目录 32 | */ 33 | public function __construct() 34 | { 35 | $this->save_path = __DIR__ . '/' . $this->save_path; 36 | clearstatcache(); 37 | if (!is_dir($this->save_path)) { 38 | $this->showMessage(0, $this->save_path . '不存在'); 39 | } else if (is_writable($this->save_path)) { 40 | $this->makeDir($this->save_path); 41 | } else { 42 | $this->showMessage(0, '目录权限不足'); 43 | } 44 | } 45 | 46 | /** 47 | * 获取文件存储路径 48 | * @param $file 49 | * @param $extension 50 | * @return string 51 | */ 52 | private function getFilePath($file, $extension) 53 | { 54 | return $this->save_path . md5($file) . $extension; 55 | } 56 | 57 | /** 58 | * 循环生成目录 59 | * @param $path 60 | * @return bool 61 | */ 62 | public function makeDir($path){ 63 | return is_dir($path) || ($this->makeDir(dirname($path)) && mkdir($path, 0777)); 64 | } 65 | 66 | /** 67 | * 上传图片实现 68 | */ 69 | public function start() 70 | { 71 | $file = $_POST['file']; 72 | list($type, $file) = explode(',', $file); 73 | preg_match('/(jpeg|png|gif)/', $type, $extension); 74 | if (!isset($extension[1])) { 75 | $this->showMessage(0, '图片格式错误'); 76 | } 77 | if ($extension[1] == 'jpeg') { 78 | $extension[1] = 'jpg'; 79 | } 80 | $path = $this->getFilePath($file, $extension[1]); 81 | if (file_put_contents($path, base64_decode($file), true)) { 82 | $this->showMessage(1, '上传成功', str_ireplace($this->save_path, $this->http_path, $path)); 83 | } else { 84 | $this->showMessage(0, '文件写入失败,请检查目录权限'); 85 | } 86 | } 87 | } 88 | 89 | $upload = new Upload(); 90 | $upload->start(); --------------------------------------------------------------------------------