Editor.js 42.4 KB
Newer Older
C
campaign 已提交
1 2 3 4 5 6 7 8
/**
 * @file
 * @name UE.Editor
 * @short Editor
 * @import editor.js,core/utils.js,core/EventBase.js,core/browser.js,core/dom/dtd.js,core/dom/domUtils.js,core/dom/Range.js,core/dom/Selection.js,plugins/serialize.js
 * @desc 编辑器主类,包含编辑器提供的大部分公用接口
 */
(function () {
C
campaign 已提交
9 10
    var uid = 0, _selectionChangeTimer;

C
campaign 已提交
11 12 13 14 15 16
    /**
     * @private
     * @ignore
     * @param form  编辑器所在的form元素
     * @param editor  编辑器实例对象
     */
C
campaign 已提交
17
    function setValue(form, editor) {
C
campaign 已提交
18
        var textarea;
C
campaign 已提交
19 20 21 22
        if (editor.textarea) {
            if (utils.isString(editor.textarea)) {
                for (var i = 0, ti, tis = domUtils.getElementsByTagName(form, 'textarea'); ti = tis[i++];) {
                    if (ti.id == 'ueditor_textarea_' + editor.options.textarea) {
C
campaign 已提交
23 24 25 26 27 28 29 30
                        textarea = ti;
                        break;
                    }
                }
            } else {
                textarea = editor.textarea;
            }
        }
C
campaign 已提交
31 32
        if (!textarea) {
            form.appendChild(textarea = domUtils.createElement(document, 'textarea', {
C
campaign 已提交
33 34 35
                'name': editor.options.textarea,
                'id': 'ueditor_textarea_' + editor.options.textarea,
                'style': "display:none"
C
campaign 已提交
36
            }));
C
campaign 已提交
37 38 39 40
            //不要产生多个textarea
            editor.textarea = textarea;
        }
        textarea.value = editor.hasContents() ?
C
campaign 已提交
41
            (editor.options.allHtmlEnabled ? editor.getAllHtml() : editor.getContent(null, null, true)) :
C
campaign 已提交
42 43 44 45 46 47 48 49 50 51 52 53 54 55
            ''
    }

    /**
     * UEditor编辑器类
     * @name Editor
     * @desc 创建一个跟编辑器实例
     * - ***container*** 编辑器容器对象
     * - ***iframe*** 编辑区域所在的iframe对象
     * - ***window*** 编辑区域所在的window
     * - ***document*** 编辑区域所在的document对象
     * - ***body*** 编辑区域所在的body对象
     * - ***selection*** 编辑区域的选区对象
     */
C
campaign 已提交
56
    var Editor = UE.Editor = function (options) {
C
campaign 已提交
57 58
        var me = this;
        me.uid = uid++;
C
campaign 已提交
59
        EventBase.call(me);
C
campaign 已提交
60
        me.commands = {};
C
campaign 已提交
61
        me.options = utils.extend(utils.clone(options || {}), UEDITOR_CONFIG, true);
C
campaign 已提交
62
        me.shortcutkeys = {};
C
campaign 已提交
63 64
        me.inputRules = [];
        me.outputRules = [];
C
campaign 已提交
65
        //设置默认的常用属性
C
campaign 已提交
66
        me.setOpt({
C
campaign 已提交
67
            isShow: true,
C
campaign 已提交
68
            initialContent: '',
C
campaign 已提交
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
            autoClearinitialContent: false,
            iframeCssUrl: me.options.UEDITOR_HOME_URL + 'themes/iframe.css',
            textarea: 'editorValue',
            focus: false,
            focusInEnd: true,
            autoClearEmptyNode: true,
            fullscreen: false,
            readonly: false,
            zIndex: 999,
            imagePopup: true,
            enterTag: 'p',
            customDomain: false,
            lang: 'zh-cn',
            langPath: me.options.UEDITOR_HOME_URL + 'lang/',
            theme: 'default',
            themePath: me.options.UEDITOR_HOME_URL + 'themes/',
            allHtmlEnabled: false,
            scaleEnabled: false,
C
campaign 已提交
87 88
            tableNativeEditInFF: false,
            autoSyncData : true
C
campaign 已提交
89
        });
C
campaign 已提交
90

C
campaign 已提交
91
        utils.loadFile(document, {
C
campaign 已提交
92 93 94 95
            src: me.options.langPath + me.options.lang + "/" + me.options.lang + ".js",
            tag: "script",
            type: "text/javascript",
            defer: "defer"
C
campaign 已提交
96 97
        }, function () {
            //初始化插件
C
campaign 已提交
98 99
            for (var pi in UE.plugins) {
                UE.plugins[pi].call(me);
C
campaign 已提交
100 101 102
            }
            me.langIsReady = true;

C
campaign 已提交
103
            me.fireEvent("langReady");
C
campaign 已提交
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
        });
        UE.instants['ueditorInstant' + me.uid] = me;
    };
    Editor.prototype = {
        /**
         * 当编辑器ready后执行传入的fn,如果编辑器已经完成ready,就马上执行fn,fn的中的this是编辑器实例。
         * 大部分的实例接口都需要放在该方法内部执行,否则在IE下可能会报错。
         * @name ready
         * @grammar editor.ready(fn) fn是当编辑器渲染好后执行的function
         * @example
         * var editor = new UE.ui.Editor();
         * editor.render("myEditor");
         * editor.ready(function(){
         *     editor.setContent("欢迎使用UEditor!");
         * })
         */
C
campaign 已提交
120
        ready: function (fn) {
C
campaign 已提交
121
            var me = this;
C
campaign 已提交
122 123
            if (fn) {
                me.isReady ? fn.apply(me) : me.addListener('ready', fn);
C
campaign 已提交
124 125 126 127 128 129 130
            }
        },
        /**
         * 为编辑器设置默认参数值。若用户配置为空,则以默认配置为准
         * @grammar editor.setOpt(key,value);      //传入一个键、值对
         * @grammar editor.setOpt({ key:value});   //传入一个json对象
         */
C
campaign 已提交
131
        setOpt: function (key, val) {
C
campaign 已提交
132
            var obj = {};
C
campaign 已提交
133
            if (utils.isString(key)) {
C
campaign 已提交
134 135 136 137
                obj[key] = val
            } else {
                obj = key;
            }
C
campaign 已提交
138
            utils.extend(this.options, obj, true);
C
campaign 已提交
139 140 141 142 143 144
        },
        /**
         * 销毁编辑器实例对象
         * @name destroy
         * @grammar editor.destroy();
         */
C
campaign 已提交
145
        destroy: function () {
C
campaign 已提交
146 147

            var me = this;
C
campaign 已提交
148
            me.fireEvent('destroy');
C
campaign 已提交
149 150
            var container = me.container.parentNode;
            var textarea = me.textarea;
C
campaign 已提交
151
            if (!textarea) {
C
campaign 已提交
152
                textarea = document.createElement('textarea');
C
campaign 已提交
153 154
                container.parentNode.insertBefore(textarea, container);
            } else {
C
campaign 已提交
155 156 157 158 159 160 161
                textarea.style.display = ''
            }
            textarea.style.width = container.offsetWidth + 'px';
            textarea.style.height = container.offsetHeight + 'px';
            textarea.value = me.getContent();
            textarea.id = me.key;
            container.innerHTML = '';
C
campaign 已提交
162
            domUtils.remove(container);
C
campaign 已提交
163 164
            var key = me.key;
            //trace:2004
C
campaign 已提交
165 166
            for (var p in me) {
                if (me.hasOwnProperty(p)) {
C
campaign 已提交
167 168 169 170 171 172 173 174 175 176 177
                    delete this[p];
                }
            }
            UE.delEditor(key);
        },
        /**
         * 渲染编辑器的DOM到指定容器,必须且只能调用一次
         * @name render
         * @grammar editor.render(containerId);    //可以指定一个容器ID
         * @grammar editor.render(containerDom);   //也可以直接指定容器对象
         */
C
campaign 已提交
178
        render: function (container,holder) {
C
campaign 已提交
179
            var me = this, options = me.options;
C
campaign 已提交
180 181
            if (utils.isString(container)) {
                container = document.getElementById(container);
C
campaign 已提交
182
            }
C
campaign 已提交
183
            if (container) {
C
campaign 已提交
184
                var useBodyAsViewport = ie && browser.version < 9,
C
campaign 已提交
185 186 187 188 189 190 191 192 193 194 195 196 197 198
                    html = ( ie && browser.version < 9 ? '' : '<!DOCTYPE html>') +
                        '<html xmlns=\'http://www.w3.org/1999/xhtml\'' + (!useBodyAsViewport ? ' class=\'view\'' : '') + '><head>' +
                        ( options.iframeCssUrl ? '<link rel=\'stylesheet\' type=\'text/css\' href=\'' + utils.unhtml(options.iframeCssUrl) + '\'/>' : '' ) +
                        '<style type=\'text/css\'>' +
                        //设置四周的留边
                        '.view{padding:0;word-wrap:break-word;cursor:text;height:100%;}\n' +
                        //设置默认字体和字号
                        //font-family不能呢随便改,在safari下fillchar会有解析问题
                        'body{margin:8px;font-family:sans-serif;font-size:16px;}' +
                        //设置段落间距
                        'p{margin:5px 0;}'
                        + ( options.initialStyle || '' ) +
                        '</style></head><body' + (useBodyAsViewport ? ' class=\'view\'' : '') + '></body>';
                if (options.customDomain && document.domain != location.hostname) {
C
campaign 已提交
199
                    html += '<script>window.parent.UE.instants[\'ueditorInstant' + me.uid + '\']._setup(document);</script></html>';
C
campaign 已提交
200
                    container.appendChild(domUtils.createElement(document, 'iframe', {
C
campaign 已提交
201
                        id: 'ueditor_' + me.uid,
C
campaign 已提交
202 203 204 205
                        width: "100%",
                        height: "100%",
                        frameborder: "0",
                        src: 'javascript:void(function(){document.open();document.domain="' + document.domain + '";' +
C
campaign 已提交
206 207
                            'document.write("' + html + '");document.close();}())'
                    }));
C
campaign 已提交
208
                } else {
C
campaign 已提交
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223

                    if(options.initialFrameWidth){
                        options.minFrameWidth = options.initialFrameWidth
                    }else{
                        options.minFrameWidth = options.initialFrameWidth = container.offsetWidth;
                    }
                    if(options.initialFrameHeight){
                        options.minFrameHeight = options.initialFrameHeight
                    }else{
                        options.initialFrameHeight = options.minFrameHeight = container.offsetHeight;
                    }
                    container.style.width = options.initialFrameWidth+ 'px';
                    container.style.height = options.initialFrameHeight + 'px';
                    container.style.zIndex = options.zIndex;
                    container.innerHTML = '<iframe id="' + 'ueditor_' + this.uid + '"' + 'width="100%" height="100%" scroll="no" frameborder="0" ></iframe>';
C
campaign 已提交
224 225 226
                    var doc = container.firstChild.contentWindow.document;
                    //去掉了原来的判断!browser.webkit,因为会导致onload注册的事件不触发
                    doc.open();
C
campaign 已提交
227
                    doc.write(html + '</html>');
C
campaign 已提交
228
                    doc.close();
C
campaign 已提交
229
                    me._setup(doc);
C
campaign 已提交
230 231 232 233 234 235 236 237 238 239
                }
                container.style.overflow = 'hidden';
            }
        },
        /**
         * 编辑器初始化
         * @private
         * @ignore
         * @param {Element} doc 编辑器Iframe中的文档对象
         */
C
campaign 已提交
240
        _setup: function (doc) {
C
campaign 已提交
241

C
campaign 已提交
242
            var me = this,
C
campaign 已提交
243 244
                options = me.options;
            if (ie) {
C
campaign 已提交
245 246 247 248 249 250 251 252 253 254 255
                doc.body.disabled = true;
                doc.body.contentEditable = true;
                doc.body.disabled = false;
            } else {
                doc.body.contentEditable = true;
                doc.body.spellcheck = false;
            }
            me.document = doc;
            me.window = doc.defaultView || doc.parentWindow;
            me.iframe = me.window.frameElement;
            me.body = doc.body;
C
campaign 已提交
256

C
campaign 已提交
257
            me.selection = new dom.Selection(doc);
C
campaign 已提交
258 259
            //gecko初始化就能得到range,无法判断isFocus了
            var geckoSel;
C
campaign 已提交
260
            if (browser.gecko && (geckoSel = this.selection.getNative())) {
C
campaign 已提交
261 262 263
                geckoSel.removeAllRanges();
            }
            this._initEvents();
C
campaign 已提交
264 265
            if (options.initialContent) {
                if (options.autoClearinitialContent) {
C
campaign 已提交
266 267
                    var oldExecCommand = me.execCommand;
                    me.execCommand = function () {
C
campaign 已提交
268 269
                        me.fireEvent('firstBeforeExecCommand');
                        return oldExecCommand.apply(me, arguments);
C
campaign 已提交
270
                    };
C
campaign 已提交
271
                    this._setDefaultContent(options.initialContent);
C
campaign 已提交
272
                } else
C
campaign 已提交
273
                    this.setContent(options.initialContent, false, true);
C
campaign 已提交
274 275
            }
            //为form提交提供一个隐藏的textarea
C
campaign 已提交
276 277
            for (var form = this.iframe.parentNode; !domUtils.isBody(form); form = form.parentNode) {
                if (form.tagName == 'FORM') {
C
campaign 已提交
278 279 280 281 282 283 284 285 286
                    if(me.options.autoSyncData){
                        domUtils.on(me.window,'blur',function(){
                            setValue(form,me);
                        });
                    }else{
                        domUtils.on(form, 'submit', function () {
                            setValue(this, me);
                        });
                    }
C
campaign 已提交
287 288 289 290
                    break;
                }
            }
            //编辑器不能为空内容
C
campaign 已提交
291
            if (domUtils.isEmptyNode(me.body)) {
C
campaign 已提交
292 293 294
                me.body.innerHTML = '<p>' + (browser.ie ? '' : '<br/>') + '</p>';
            }
            //如果要求focus, 就把光标定位到内容开始
C
campaign 已提交
295 296 297
            if (options.focus) {
                setTimeout(function () {
                    me.focus(me.options.focusInEnd);
C
campaign 已提交
298 299
                    //如果自动清除开着,就不需要做selectionchange;
                    !me.options.autoClearinitialContent && me._selectionChange();
C
campaign 已提交
300
                }, 0);
C
campaign 已提交
301
            }
C
campaign 已提交
302
            if (!me.container) {
C
campaign 已提交
303 304
                me.container = this.iframe.parentNode;
            }
C
campaign 已提交
305 306
            if (options.fullscreen && me.ui) {
                me.ui.setFullScreen(true);
C
campaign 已提交
307
            }
C
campaign 已提交
308

C
campaign 已提交
309
            try {
C
campaign 已提交
310 311 312
                me.document.execCommand('2D-position', false, false);
            } catch (e) {
            }
C
campaign 已提交
313
            try {
C
campaign 已提交
314 315 316
                me.document.execCommand('enableInlineTableEditing', false, false);
            } catch (e) {
            }
C
campaign 已提交
317
            try {
C
campaign 已提交
318 319
                me.document.execCommand('enableObjectResizing', false, false);
            } catch (e) {
C
campaign 已提交
320 321 322
//                domUtils.on(me.body,browser.ie ? 'resizestart' : 'resize', function( evt ) {
//                    domUtils.preventDefault(evt)
//                });
C
campaign 已提交
323
            }
C
campaign 已提交
324
            me._bindshortcutKeys();
C
campaign 已提交
325
            me.isReady = 1;
C
campaign 已提交
326
            me.fireEvent('ready');
C
campaign 已提交
327
            options.onready && options.onready.call(me);
C
campaign 已提交
328 329
            if (!browser.ie) {
                domUtils.on(me.window, ['blur', 'focus'], function (e) {
C
campaign 已提交
330
                    //chrome下会出现alt+tab切换时,导致选区位置不对
C
campaign 已提交
331
                    if (e.type == 'blur') {
C
campaign 已提交
332
                        me._bakRange = me.selection.getRange();
C
campaign 已提交
333
                        try {
C
campaign 已提交
334
                            me.selection.getNative().removeAllRanges();
C
campaign 已提交
335 336
                        } catch (e) {
                        }
C
campaign 已提交
337 338 339 340

                    } else {
                        try {
                            me._bakRange && me._bakRange.select();
C
campaign 已提交
341
                        } catch (e) {
C
campaign 已提交
342 343
                        }
                    }
C
campaign 已提交
344
                });
C
campaign 已提交
345 346
            }
            //trace:1518 ff3.6body不够寛,会导致点击空白处无法获得焦点
C
campaign 已提交
347
            if (browser.gecko && browser.version <= 10902) {
C
campaign 已提交
348 349
                //修复ff3.6初始化进来,不能点击获得焦点
                me.body.contentEditable = false;
C
campaign 已提交
350
                setTimeout(function () {
C
campaign 已提交
351
                    me.body.contentEditable = true;
C
campaign 已提交
352 353
                }, 100);
                setInterval(function () {
C
campaign 已提交
354
                    me.body.style.height = me.iframe.offsetHeight - 20 + 'px'
C
campaign 已提交
355
                }, 100)
C
campaign 已提交
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
            }
            !options.isShow && me.setHide();
            options.readonly && me.setDisabled();
        },
        /**
         * 同步编辑器的数据,为提交数据做准备,主要用于你是手动提交的情况
         * @name sync
         * @grammar editor.sync(); //从编辑器的容器向上查找,如果找到就同步数据
         * @grammar editor.sync(formID); //formID制定一个要同步数据的form的id,编辑器的数据会同步到你指定form下
         * @desc
         * 后台取得数据得键值使用你容器上得''name''属性,如果没有就使用参数传入的''textarea''
         * @example
         * editor.sync();
         * form.sumbit(); //form变量已经指向了form元素
         *
         */
C
campaign 已提交
372
        sync: function (formId) {
C
campaign 已提交
373
            var me = this,
C
campaign 已提交
374 375 376 377 378
                form = formId ? document.getElementById(formId) :
                    domUtils.findParent(me.iframe.parentNode, function (node) {
                        return node.tagName == 'FORM'
                    }, true);
            form && setValue(form, me);
C
campaign 已提交
379 380 381 382 383 384
        },
        /**
         * 设置编辑器高度
         * @name setHeight
         * @grammar editor.setHeight(number);  //纯数值,不带单位
         */
C
campaign 已提交
385
        setHeight: function (height) {
C
campaign 已提交
386
            if (height !== parseInt(this.iframe.parentNode.style.height)) {
C
campaign 已提交
387 388 389 390 391
                this.iframe.parentNode.style.height = height + 'px';
            }
            this.document.body.style.height = height - 20 + 'px';
        },

C
campaign 已提交
392
        addshortcutkey: function (cmd, keys) {
C
campaign 已提交
393
            var obj = {};
C
campaign 已提交
394
            if (keys) {
C
campaign 已提交
395
                obj[cmd] = keys
C
campaign 已提交
396
            } else {
C
campaign 已提交
397 398
                obj = cmd;
            }
C
campaign 已提交
399
            utils.extend(this.shortcutkeys, obj)
C
campaign 已提交
400
        },
C
campaign 已提交
401
        _bindshortcutKeys: function () {
C
campaign 已提交
402 403
            var me = this, shortcutkeys = this.shortcutkeys;
            me.addListener('keydown', function (type, e) {
C
campaign 已提交
404
                var keyCode = e.keyCode || e.which;
C
campaign 已提交
405
                for (var i in shortcutkeys) {
C
campaign 已提交
406
                    var tmp = shortcutkeys[i].split(',');
C
campaign 已提交
407
                    for (var t = 0, ti; ti = tmp[t++];) {
C
campaign 已提交
408
                        ti = ti.split(':');
C
campaign 已提交
409 410 411
                        var key = ti[0], param = ti[1];
                        if (/^(ctrl)(\+shift)?\+(\d+)$/.test(key.toLowerCase()) || /^(\d+)$/.test(key)) {
                            if (( (RegExp.$1 == 'ctrl' ? (e.ctrlKey || e.metaKey) : 0)
C
campaign 已提交
412 413 414 415
                                && (RegExp.$2 != "" ? e[RegExp.$2.slice(1) + "Key"] : 1)
                                && keyCode == RegExp.$3
                                ) ||
                                keyCode == RegExp.$1
C
campaign 已提交
416 417
                                ) {
                                me.execCommand(i, param);
C
campaign 已提交
418 419
                                domUtils.preventDefault(e);
                            }
C
campaign 已提交
420 421
                        }
                    }
C
campaign 已提交
422

C
campaign 已提交
423 424 425
                }
            });
        },
C
campaign 已提交
426 427 428 429 430 431 432 433 434 435 436 437
        /**
         * 获取编辑器内容
         * @name getContent
         * @grammar editor.getContent()  => String //若编辑器中只包含字符"&lt;p&gt;&lt;br /&gt;&lt;/p/&gt;"会返回空。
         * @grammar editor.getContent(fn)  => String
         * @example
         * getContent默认是会现调用hasContents来判断编辑器是否为空,如果是,就直接返回空字符串
         * 你也可以传入一个fn来接替hasContents的工作,定制判断的规则
         * editor.getContent(function(){
         *     return false //编辑器没有内容 ,getContent直接返回空
         * })
         */
C
campaign 已提交
438
        getContent: function (cmd, fn,notSetCursor,ignoreBlank,formatter) {
C
campaign 已提交
439
            var me = this;
C
campaign 已提交
440
            if (cmd && utils.isFunction(cmd)) {
C
campaign 已提交
441 442 443
                fn = cmd;
                cmd = '';
            }
C
campaign 已提交
444
            if (fn ? !fn() : !this.hasContents()) {
C
campaign 已提交
445 446
                return '';
            }
C
campaign 已提交
447
            me.fireEvent('beforegetcontent');
C
campaign 已提交
448
            var root = UE.htmlparser(me.body.innerHTML,ignoreBlank);
C
campaign 已提交
449
            me.filterOutputRule(root);
C
campaign 已提交
450
            me.fireEvent('aftergetcontent', cmd);
C
campaign 已提交
451
            return  root.toHtml(formatter);
C
campaign 已提交
452 453 454 455 456 457
        },
        /**
         * 取得完整的html代码,可以直接显示成完整的html文档
         * @name getAllHtml
         * @grammar editor.getAllHtml()  => String
         */
C
campaign 已提交
458
        getAllHtml: function () {
C
campaign 已提交
459
            var me = this,
C
campaign 已提交
460 461 462 463 464 465 466
                headHtml = [],
                html = '';
            me.fireEvent('getAllHtml', headHtml);
            if (browser.ie && browser.version > 8) {
                var headHtmlForIE9 = '';
                utils.each(me.document.styleSheets, function (si) {
                    headHtmlForIE9 += ( si.href ? '<link rel="stylesheet" type="text/css" href="' + si.href + '" />' : '<style>' + si.cssText + '</style>');
C
campaign 已提交
467
                });
C
campaign 已提交
468
                utils.each(me.document.getElementsByTagName('script'), function (si) {
C
campaign 已提交
469 470 471
                    headHtmlForIE9 += si.outerHTML;
                });

C
campaign 已提交
472
            }
C
campaign 已提交
473
            return '<html><head>' + (me.options.charset ? '<meta http-equiv="Content-Type" content="text/html; charset=' + me.options.charset + '"/>' : '')
C
campaign 已提交
474 475
                + (headHtmlForIE9 || me.document.getElementsByTagName('head')[0].innerHTML) + headHtml.join('\n') + '</head>'
                + '<body ' + (ie && browser.version < 9 ? 'class="view"' : '') + '>' + me.getContent(null, null, true) + '</body></html>';
C
campaign 已提交
476 477 478 479 480 481
        },
        /**
         * 得到编辑器的纯文本内容,但会保留段落格式
         * @name getPlainTxt
         * @grammar editor.getPlainTxt()  => String
         */
C
campaign 已提交
482
        getPlainTxt: function () {
C
campaign 已提交
483 484 485 486 487 488 489 490
            var reg = new RegExp(domUtils.fillChar, 'g'),
                html = this.body.innerHTML.replace(/[\n\r]/g, '');//ie要先去了\n在处理
            html = html.replace(/<(p|div)[^>]*>(<br\/?>|&nbsp;)<\/\1>/gi, '\n')
                .replace(/<br\/?>/gi, '\n')
                .replace(/<[^>/]+>/g, '')
                .replace(/(\n)?<\/([^>]+)>/g, function (a, b, c) {
                    return dtd.$block[c] ? '\n' : b ? b : '';
                });
C
campaign 已提交
491
            //取出来的空格会有c2a0会变成乱码,处理这种情况\u00a0
C
campaign 已提交
492
            return html.replace(reg, '').replace(/\u00a0/g, ' ').replace(/&nbsp;/g, ' ');
C
campaign 已提交
493 494 495 496 497 498 499
        },

        /**
         * 获取编辑器中的纯文本内容,没有段落格式
         * @name getContentTxt
         * @grammar editor.getContentTxt()  => String
         */
C
campaign 已提交
500
        getContentTxt: function () {
C
campaign 已提交
501
            var reg = new RegExp(domUtils.fillChar, 'g');
C
campaign 已提交
502
            //取出来的空格会有c2a0会变成乱码,处理这种情况\u00a0
C
campaign 已提交
503
            return this.body[browser.ie ? 'innerText' : 'textContent'].replace(reg, '').replace(/\u00a0/g, ' ');
C
campaign 已提交
504 505 506 507 508 509 510 511 512 513 514 515 516
        },

        /**
         * 将html设置到编辑器中, 如果是用于初始化时给编辑器赋初值,则必须放在ready方法内部执行
         * @name setContent
         * @grammar editor.setContent(html)
         * @example
         * var editor = new UE.ui.Editor()
         * editor.ready(function(){
         *     //需要ready后执行,否则可能报错
         *     editor.setContent("欢迎使用UEditor!");
         * })
         */
C
campaign 已提交
517
        setContent: function (html, isAppendTo, notFireSelectionchange) {
C
campaign 已提交
518
            var me = this;
C
campaign 已提交
519

C
campaign 已提交
520
            me.fireEvent('beforesetcontent', html);
C
campaign 已提交
521 522 523 524
            var root = UE.htmlparser(html);
            me.filterInputRule(root);
            html = root.toHtml();

C
campaign 已提交
525

C
campaign 已提交
526 527
            me.body.innerHTML = (isAppendTo ? me.body.innerHTML : '') + html;

C
campaign 已提交
528

C
campaign 已提交
529

C
campaign 已提交
530
            //给文本或者inline节点套p标签
C
campaign 已提交
531
            if (me.options.enterTag == 'p') {
C
campaign 已提交
532 533

                var child = this.body.firstChild, tmpNode;
C
campaign 已提交
534 535 536 537 538
                if (!child || child.nodeType == 1 &&
                    (dtd.$cdata[child.tagName] ||
                        domUtils.isCustomeNode(child)
                        )
                    && child === this.body.lastChild) {
C
campaign 已提交
539 540 541
                    this.body.innerHTML = '<p>' + (browser.ie ? '&nbsp;' : '<br/>') + '</p>' + this.body.innerHTML;

                } else {
C
campaign 已提交
542 543 544
                    var p = me.document.createElement('p');
                    while (child) {
                        while (child && (child.nodeType == 3 || child.nodeType == 1 && dtd.p[child.tagName] && !dtd.$cdata[child.tagName])) {
C
campaign 已提交
545
                            tmpNode = child.nextSibling;
C
campaign 已提交
546
                            p.appendChild(child);
C
campaign 已提交
547 548
                            child = tmpNode;
                        }
C
campaign 已提交
549 550 551
                        if (p.firstChild) {
                            if (!child) {
                                me.body.appendChild(p);
C
campaign 已提交
552 553
                                break;
                            } else {
C
campaign 已提交
554 555
                                child.parentNode.insertBefore(p, child);
                                p = me.document.createElement('p');
C
campaign 已提交
556 557 558 559 560 561
                            }
                        }
                        child = child.nextSibling;
                    }
                }
            }
C
campaign 已提交
562
            me.fireEvent('aftersetcontent');
C
campaign 已提交
563 564
            me.fireEvent('contentchange');

C
campaign 已提交
565 566 567 568 569
            !notFireSelectionchange && me._selectionChange();
            //清除保存的选区
            me._bakRange = me._bakIERange = null;
            //trace:1742 setContent后gecko能得到焦点问题
            var geckoSel;
C
campaign 已提交
570
            if (browser.gecko && (geckoSel = this.selection.getNative())) {
C
campaign 已提交
571 572 573 574 575 576 577 578 579
                geckoSel.removeAllRanges();
            }
        },

        /**
         * 让编辑器获得焦点,toEnd确定focus位置
         * @name focus
         * @grammar editor.focus([toEnd])   //默认focus到编辑器头部,toEnd为true时focus到内容尾部
         */
C
campaign 已提交
580
        focus: function (toEnd) {
C
campaign 已提交
581 582
            try {
                var me = this,
C
campaign 已提交
583 584 585
                    rng = me.selection.getRange();
                if (toEnd) {
                    rng.setStartAtLast(me.body.lastChild).setCursor(false, true);
C
campaign 已提交
586
                } else {
C
campaign 已提交
587
                    rng.select(true);
C
campaign 已提交
588
                }
C
campaign 已提交
589
            } catch (e) {
C
campaign 已提交
590 591 592 593 594 595 596 597
            }
        },

        /**
         * 初始化UE事件及部分事件代理
         * @private
         * @ignore
         */
C
campaign 已提交
598
        _initEvents: function () {
C
campaign 已提交
599
            var me = this,
C
campaign 已提交
600 601 602 603 604 605
                doc = me.document,
                win = me.window;
            me._proxyDomEvent = utils.bind(me._proxyDomEvent, me);
            domUtils.on(doc, ['click', 'contextmenu', 'mousedown', 'keydown', 'keyup', 'keypress', 'mouseup', 'mouseover', 'mouseout', 'selectstart'], me._proxyDomEvent);
            domUtils.on(win, ['focus', 'blur'], me._proxyDomEvent);
            domUtils.on(doc, ['mouseup', 'keydown'], function (evt) {
C
campaign 已提交
606
                //特殊键不触发selectionchange
C
campaign 已提交
607
                if (evt.type == 'keydown' && (evt.ctrlKey || evt.metaKey || evt.shiftKey || evt.altKey)) {
C
campaign 已提交
608 609
                    return;
                }
C
campaign 已提交
610 611 612
                if (evt.button == 2)return;
                me._selectionChange(250, evt);
            });
C
campaign 已提交
613 614 615 616
            //处理拖拽
            //ie ff不能从外边拖入
            //chrome只针对从外边拖入的内容过滤
            var innerDrag = 0, source = browser.ie ? me.body : me.document, dragoverHandler;
C
campaign 已提交
617
            domUtils.on(source, 'dragstart', function () {
C
campaign 已提交
618
                innerDrag = 1;
C
campaign 已提交
619 620
            });
            domUtils.on(source, browser.webkit ? 'dragover' : 'drop', function () {
C
campaign 已提交
621
                return browser.webkit ?
C
campaign 已提交
622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640
                    function () {
                        clearTimeout(dragoverHandler);
                        dragoverHandler = setTimeout(function () {
                            if (!innerDrag) {
                                var sel = me.selection,
                                    range = sel.getRange();
                                if (range) {
                                    var common = range.getCommonAncestor();
                                    if (common && me.serialize) {
                                        var f = me.serialize,
                                            node =
                                                f.filter(
                                                    f.transformInput(
                                                        f.parseHTML(
                                                            f.word(common.innerHTML)
                                                        )
                                                    )
                                                );
                                        common.innerHTML = f.toHTML(node);
C
campaign 已提交
641 642 643 644
                                    }
                                }
                            }
                            innerDrag = 0;
C
campaign 已提交
645 646 647 648 649
                        }, 200);
                    } :
                    function (e) {
                        if (!innerDrag) {
                            e.preventDefault ? e.preventDefault() : (e.returnValue = false);
C
campaign 已提交
650
                        }
C
campaign 已提交
651 652 653
                        innerDrag = 0;
                    }
            }());
C
campaign 已提交
654 655 656 657 658 659
        },
        /**
         * 触发事件代理
         * @private
         * @ignore
         */
C
campaign 已提交
660
        _proxyDomEvent: function (evt) {
C
campaign 已提交
661
            return this.fireEvent(evt.type.replace(/^on/, ''), evt);
C
campaign 已提交
662 663 664 665 666 667
        },
        /**
         * 变化选区
         * @private
         * @ignore
         */
C
campaign 已提交
668
        _selectionChange: function (delay, evt) {
C
campaign 已提交
669 670 671 672 673 674 675
            var me = this;
            //有光标才做selectionchange 为了解决未focus时点击source不能触发更改工具栏状态的问题(source命令notNeedUndo=1)
//            if ( !me.selection.isFocus() ){
//                return;
//            }
            var hackForMouseUp = false;
            var mouseX, mouseY;
C
campaign 已提交
676
            if (browser.ie && browser.version < 9 && evt && evt.type == 'mouseup') {
C
campaign 已提交
677
                var range = this.selection.getRange();
C
campaign 已提交
678
                if (!range.collapsed) {
C
campaign 已提交
679 680 681 682 683
                    hackForMouseUp = true;
                    mouseX = evt.clientX;
                    mouseY = evt.clientY;
                }
            }
C
campaign 已提交
684 685 686
            clearTimeout(_selectionChangeTimer);
            _selectionChangeTimer = setTimeout(function () {
                if (!me.selection.getNative()) {
C
campaign 已提交
687 688 689 690 691
                    return;
                }
                //修复一个IE下的bug: 鼠标点击一段已选择的文本中间时,可能在mouseup后的一段时间内取到的range是在selection的type为None下的错误值.
                //IE下如果用户是拖拽一段已选择文本,则不会触发mouseup事件,所以这里的特殊处理不会对其有影响
                var ieRange;
C
campaign 已提交
692
                if (hackForMouseUp && me.selection.getNative().type == 'None') {
C
campaign 已提交
693 694
                    ieRange = me.document.body.createTextRange();
                    try {
C
campaign 已提交
695 696
                        ieRange.moveToPoint(mouseX, mouseY);
                    } catch (ex) {
C
campaign 已提交
697 698 699 700
                        ieRange = null;
                    }
                }
                var bakGetIERange;
C
campaign 已提交
701
                if (ieRange) {
C
campaign 已提交
702 703 704 705 706 707
                    bakGetIERange = me.selection.getIERange;
                    me.selection.getIERange = function () {
                        return ieRange;
                    };
                }
                me.selection.cache();
C
campaign 已提交
708
                if (bakGetIERange) {
C
campaign 已提交
709 710
                    me.selection.getIERange = bakGetIERange;
                }
C
campaign 已提交
711 712
                if (me.selection._cachedRange && me.selection._cachedStartElement) {
                    me.fireEvent('beforeselectionchange');
C
campaign 已提交
713
                    // 第二个参数causeByUi为true代表由用户交互造成的selectionchange.
C
campaign 已提交
714 715
                    me.fireEvent('selectionchange', !!evt);
                    me.fireEvent('afterselectionchange');
C
campaign 已提交
716 717
                    me.selection.clear();
                }
C
campaign 已提交
718
            }, delay || 50);
C
campaign 已提交
719
        },
C
campaign 已提交
720
        _callCmdFn: function (fnName, args) {
C
campaign 已提交
721
            var cmdName = args[0].toLowerCase(),
C
campaign 已提交
722
                cmd, cmdFn;
C
campaign 已提交
723 724 725
            cmd = this.commands[cmdName] || UE.commands[cmdName];
            cmdFn = cmd && cmd[fnName];
            //没有querycommandstate或者没有command的都默认返回0
C
campaign 已提交
726
            if ((!cmd || !cmdFn) && fnName == 'queryCommandState') {
C
campaign 已提交
727
                return 0;
C
campaign 已提交
728 729
            } else if (cmdFn) {
                return cmdFn.apply(this, args);
C
campaign 已提交
730 731 732 733 734 735 736 737
            }
        },

        /**
         * 执行编辑命令cmdName,完成富文本编辑效果
         * @name execCommand
         * @grammar editor.execCommand(cmdName)   => {*}
         */
C
campaign 已提交
738
        execCommand: function (cmdName) {
C
campaign 已提交
739 740
            cmdName = cmdName.toLowerCase();
            var me = this,
C
campaign 已提交
741 742 743
                result,
                cmd = me.commands[cmdName] || UE.commands[cmdName];
            if (!cmd || !cmd.execCommand) {
C
campaign 已提交
744 745
                return null;
            }
C
campaign 已提交
746
            if (!cmd.notNeedUndo && !me.__hasEnterExecCommand) {
C
campaign 已提交
747
                me.__hasEnterExecCommand = true;
C
campaign 已提交
748
                if (me.queryCommandState.apply(me,arguments) != -1) {
C
campaign 已提交
749 750
                    me.fireEvent('beforeexeccommand', cmdName);
                    result = this._callCmdFn('execCommand', arguments);
C
campaign 已提交
751
                    !me._ignoreContentChange && me.fireEvent('contentchange');
C
campaign 已提交
752
                    me.fireEvent('afterexeccommand', cmdName);
C
campaign 已提交
753 754 755
                }
                me.__hasEnterExecCommand = false;
            } else {
C
campaign 已提交
756
                result = this._callCmdFn('execCommand', arguments);
C
campaign 已提交
757
                !me._ignoreContentChange && me.fireEvent('contentchange')
C
campaign 已提交
758
            }
C
campaign 已提交
759
            !me._ignoreContentChange && me._selectionChange();
C
campaign 已提交
760 761 762 763 764 765 766 767 768 769 770
            return result;
        },
        /**
         * 根据传入的command命令,查选编辑器当前的选区,返回命令的状态
         * @name  queryCommandState
         * @grammar editor.queryCommandState(cmdName)  => (-1|0|1)
         * @desc
         * * ''-1'' 当前命令不可用
         * * ''0'' 当前命令可用
         * * ''1'' 当前命令已经执行过了
         */
C
campaign 已提交
771
        queryCommandState: function (cmdName) {
C
campaign 已提交
772
            return this._callCmdFn('queryCommandState', arguments);
C
campaign 已提交
773 774 775 776 777 778 779
        },

        /**
         * 根据传入的command命令,查选编辑器当前的选区,根据命令返回相关的值
         * @name  queryCommandValue
         * @grammar editor.queryCommandValue(cmdName)  =>  {*}
         */
C
campaign 已提交
780
        queryCommandValue: function (cmdName) {
C
campaign 已提交
781
            return this._callCmdFn('queryCommandValue', arguments);
C
campaign 已提交
782 783 784 785 786 787 788 789 790 791 792 793
        },
        /**
         * 检查编辑区域中是否有内容,若包含tags中的节点类型,直接返回true
         * @name  hasContents
         * @desc
         * 默认有文本内容,或者有以下节点都不认为是空
         * <code>{table:1,ul:1,ol:1,dl:1,iframe:1,area:1,base:1,col:1,hr:1,img:1,embed:1,input:1,link:1,meta:1,param:1}</code>
         * @grammar editor.hasContents()  => (true|false)
         * @grammar editor.hasContents(tags)  =>  (true|false)  //若文档中包含tags数组里对应的tag,直接返回true
         * @example
         * editor.hasContents(['span']) //如果编辑器里有这些,不认为是空
         */
C
campaign 已提交
794
        hasContents: function (tags) {
C
campaign 已提交
795 796 797
            if (tags) {
                for (var i = 0, ci; ci = tags[i++];) {
                    if (this.document.getElementsByTagName(ci).length > 0) {
C
campaign 已提交
798 799 800 801
                        return true;
                    }
                }
            }
C
campaign 已提交
802
            if (!domUtils.isEmptyBlock(this.body)) {
C
campaign 已提交
803 804 805 806
                return true
            }
            //随时添加,定义的特殊标签如果存在,不能认为是空
            tags = ['div'];
C
campaign 已提交
807 808 809 810
            for (i = 0; ci = tags[i++];) {
                var nodes = domUtils.getElementsByTagName(this.document, ci);
                for (var n = 0, cn; cn = nodes[n++];) {
                    if (domUtils.isCustomeNode(cn)) {
C
campaign 已提交
811 812 813 814 815 816 817 818 819 820 821 822 823 824
                        return true;
                    }
                }
            }
            return false;
        },
        /**
         * 重置编辑器,可用来做多个tab使用同一个编辑器实例
         * @name  reset
         * @desc
         * * 清空编辑器内容
         * * 清空回退列表
         * @grammar editor.reset()
         */
C
campaign 已提交
825
        reset: function () {
C
campaign 已提交
826
            this.fireEvent('reset');
C
campaign 已提交
827
        },
C
campaign 已提交
828
        setEnabled: function () {
C
campaign 已提交
829
            var me = this, range;
C
campaign 已提交
830
            if (me.body.contentEditable == 'false') {
C
campaign 已提交
831 832 833 834
                me.body.contentEditable = true;
                range = me.selection.getRange();
                //有可能内容丢失了
                try {
C
campaign 已提交
835
                    range.moveToBookmark(me.lastBk);
C
campaign 已提交
836
                    delete me.lastBk
C
campaign 已提交
837 838
                } catch (e) {
                    range.setStartAtFirst(me.body).collapse(true)
C
campaign 已提交
839
                }
C
campaign 已提交
840 841
                range.select(true);
                if (me.bkqueryCommandState) {
C
campaign 已提交
842 843 844
                    me.queryCommandState = me.bkqueryCommandState;
                    delete me.bkqueryCommandState;
                }
C
campaign 已提交
845
                me.fireEvent('selectionchange');
C
campaign 已提交
846 847 848 849 850 851 852
            }
        },
        /**
         * 设置当前编辑区域可以编辑
         * @name enable
         * @grammar editor.enable()
         */
C
campaign 已提交
853
        enable: function () {
C
campaign 已提交
854 855
            return this.setEnabled();
        },
C
campaign 已提交
856
        setDisabled: function (except) {
C
campaign 已提交
857
            var me = this;
C
campaign 已提交
858 859 860 861
            except = except ? utils.isArray(except) ? except : [except] : [];
            if (me.body.contentEditable == 'true') {
                if (!me.lastBk) {
                    me.lastBk = me.selection.getRange().createBookmark(true);
C
campaign 已提交
862 863 864
                }
                me.body.contentEditable = false;
                me.bkqueryCommandState = me.queryCommandState;
C
campaign 已提交
865 866 867
                me.queryCommandState = function (type) {
                    if (utils.indexOf(except, type) != -1) {
                        return me.bkqueryCommandState.apply(me, arguments);
C
campaign 已提交
868 869 870
                    }
                    return -1;
                };
C
campaign 已提交
871
                me.fireEvent('selectionchange');
C
campaign 已提交
872 873 874 875 876 877 878 879 880
            }
        },
        /** 设置当前编辑区域不可编辑,except中的命令除外
         * @name disable
         * @grammar editor.disable()
         * @grammar editor.disable(except)  //例外的命令,也即即使设置了disable,此处配置的命令仍然可以执行
         * @example
         * //禁用工具栏中除加粗和插入图片之外的所有功能
         * editor.disable(['bold','insertimage']);//可以是单一的String,也可以是Array
C
campaign 已提交
881
         */
C
campaign 已提交
882
        disable: function (except) {
C
campaign 已提交
883 884 885 886 887 888 889 890
            return this.setDisabled(except);
        },
        /**
         * 设置默认内容
         * @ignore
         * @private
         * @param  {String} cont 要存入的内容
         */
C
campaign 已提交
891
        _setDefaultContent: function () {
C
campaign 已提交
892 893
            function clear() {
                var me = this;
C
campaign 已提交
894
                if (me.document.getElementById('initContent')) {
C
campaign 已提交
895
                    me.body.innerHTML = '<p>' + (ie ? '' : '<br/>') + '</p>';
C
campaign 已提交
896 897
                    me.removeListener('firstBeforeExecCommand focus', clear);
                    setTimeout(function () {
C
campaign 已提交
898 899
                        me.focus();
                        me._selectionChange();
C
campaign 已提交
900
                    }, 0)
C
campaign 已提交
901 902
                }
            }
C
campaign 已提交
903 904

            return function (cont) {
C
campaign 已提交
905 906
                var me = this;
                me.body.innerHTML = '<p id="initContent">' + cont + '</p>';
C
campaign 已提交
907

C
campaign 已提交
908
                me.addListener('firstBeforeExecCommand focus', clear);
C
campaign 已提交
909 910 911 912 913 914 915
            }
        }(),
        /**
         * show方法的兼容版本
         * @private
         * @ignore
         */
C
campaign 已提交
916
        setShow: function () {
C
campaign 已提交
917 918
            var me = this, range = me.selection.getRange();
            if (me.container.style.display == 'none') {
C
campaign 已提交
919 920
                //有可能内容丢失了
                try {
C
campaign 已提交
921
                    range.moveToBookmark(me.lastBk);
C
campaign 已提交
922
                    delete me.lastBk
C
campaign 已提交
923 924
                } catch (e) {
                    range.setStartAtFirst(me.body).collapse(true)
C
campaign 已提交
925 926
                }
                //ie下focus实效,所以做了个延迟
C
campaign 已提交
927 928 929
                setTimeout(function () {
                    range.select(true);
                }, 100);
C
campaign 已提交
930 931 932 933 934 935 936 937 938
                me.container.style.display = '';
            }

        },
        /**
         * 显示编辑器
         * @name show
         * @grammar editor.show()
         */
C
campaign 已提交
939
        show: function () {
C
campaign 已提交
940 941 942 943 944 945 946
            return this.setShow();
        },
        /**
         * hide方法的兼容版本
         * @private
         * @ignore
         */
C
campaign 已提交
947
        setHide: function () {
C
campaign 已提交
948
            var me = this;
C
campaign 已提交
949 950
            if (!me.lastBk) {
                me.lastBk = me.selection.getRange().createBookmark(true);
C
campaign 已提交
951 952 953 954 955 956 957 958
            }
            me.container.style.display = 'none'
        },
        /**
         * 隐藏编辑器
         * @name hide
         * @grammar editor.hide()
         */
C
campaign 已提交
959
        hide: function () {
C
campaign 已提交
960 961 962 963 964 965 966 967 968
            return this.setHide();
        },
        /**
         * 根据制定的路径,获取对应的语言资源
         * @name  getLang
         * @grammar editor.getLang(path)  =>  (JSON|String) 路径根据的是lang目录下的语言文件的路径结构
         * @example
         * editor.getLang('contextMenu.delete') //如果当前是中文,那返回是的是删除
         */
C
campaign 已提交
969
        getLang: function (path) {
C
campaign 已提交
970
            var lang = UE.I18N[this.options.lang];
C
campaign 已提交
971
            if (!lang) {
C
campaign 已提交
972 973
                throw Error("not import language file");
            }
C
campaign 已提交
974 975
            path = (path || "").split(".");
            for (var i = 0, ci; ci = path[i++];) {
C
campaign 已提交
976
                lang = lang[ci];
C
campaign 已提交
977
                if (!lang)break;
C
campaign 已提交
978 979
            }
            return lang;
C
campaign 已提交
980 981 982
        },
        /**
         * 计算编辑器当前内容的长度
C
campaign 已提交
983 984
         * @name  getContentLength
         * @grammar editor.getContentLength(ingoneHtml,tagNames)  =>
C
campaign 已提交
985
         * @example
C
campaign 已提交
986
         * editor.getLang(true)
C
campaign 已提交
987
         */
C
campaign 已提交
988 989
        getContentLength: function (ingoneHtml, tagNames) {
            var count = this.getContent(false,false,true).length;
C
campaign 已提交
990 991 992 993
            if (ingoneHtml) {
                tagNames = (tagNames || []).concat([ 'hr', 'img', 'iframe']);
                count = this.getContentTxt().replace(/[\t\r\n]+/g, '').length;
                for (var i = 0, ci; ci = tagNames[i++];) {
C
campaign 已提交
994 995 996 997
                    count += this.document.getElementsByTagName(ci).length;
                }
            }
            return count;
C
campaign 已提交
998
        },
C
campaign 已提交
999
        addInputRule: function (rule) {
C
campaign 已提交
1000 1001
            this.inputRules.push(rule);
        },
C
campaign 已提交
1002
        filterInputRule: function (root) {
C
campaign 已提交
1003 1004 1005 1006
            for (var i = 0, ci; ci = this.inputRules[i++];) {
                ci.call(this, root)
            }
        },
C
campaign 已提交
1007
        addOutputRule: function (rule) {
C
campaign 已提交
1008 1009
            this.outputRules.push(rule)
        },
C
campaign 已提交
1010
        filterOutputRule: function (root) {
C
campaign 已提交
1011 1012 1013
            for (var i = 0, ci; ci = this.outputRules[i++];) {
                ci.call(this, root)
            }
C
campaign 已提交
1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024
        }
        /**
         * 得到dialog实例对象
         * @name getDialog
         * @grammar editor.getDialog(dialogName) => Object
         * @example
         * var dialog = editor.getDialog("insertimage");
         * dialog.open();   //打开dialog
         * dialog.close();  //关闭dialog
         */
    };
C
campaign 已提交
1025
    utils.inherits(Editor, EventBase);
C
campaign 已提交
1026
})();