Editor.js 54 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664
  1. /**
  2. * 编辑器主类,包含编辑器提供的大部分公用接口
  3. * @file
  4. * @module UE
  5. * @class Editor
  6. * @since 1.2.6.1
  7. */
  8. /**
  9. * UEditor公用空间,UEditor所有的功能都挂载在该空间下
  10. * @unfile
  11. * @module UE
  12. */
  13. /**
  14. * UEditor的核心类,为用户提供与编辑器交互的接口。
  15. * @unfile
  16. * @module UE
  17. * @class Editor
  18. */
  19. (function() {
  20. var uid = 0,
  21. _selectionChangeTimer;
  22. /**
  23. * 获取编辑器的html内容,赋值到编辑器所在表单的textarea文本域里面
  24. * @private
  25. * @method setValue
  26. * @param { UE.Editor } editor 编辑器事例
  27. */
  28. function setValue(form, editor) {
  29. var textarea;
  30. if (editor.options.textarea) {
  31. if (utils.isString(editor.options.textarea)) {
  32. for (
  33. var i = 0, ti, tis = domUtils.getElementsByTagName(form, "textarea");
  34. (ti = tis[i++]);
  35. ) {
  36. if (ti.id == "ueditor_textarea_" + editor.options.textarea) {
  37. textarea = ti;
  38. break;
  39. }
  40. }
  41. } else {
  42. textarea = editor.textarea;
  43. }
  44. }
  45. if (!textarea) {
  46. form.appendChild(
  47. (textarea = domUtils.createElement(document, "textarea", {
  48. name: editor.options.textarea,
  49. id: "ueditor_textarea_" + editor.options.textarea,
  50. style: "display:none"
  51. }))
  52. );
  53. //不要产生多个textarea
  54. editor.textarea = textarea;
  55. }
  56. !textarea.getAttribute("name") &&
  57. textarea.setAttribute("name", editor.options.textarea);
  58. textarea.value = editor.hasContents()
  59. ? editor.options.allHtmlEnabled
  60. ? editor.getAllHtml()
  61. : editor.getContent(null, null, true)
  62. : "";
  63. }
  64. function loadPlugins(me) {
  65. //初始化插件
  66. for (var pi in UE.plugins) {
  67. UE.plugins[pi].call(me);
  68. }
  69. }
  70. function checkCurLang(I18N) {
  71. for (var lang in I18N) {
  72. return lang;
  73. }
  74. }
  75. function langReadied(me) {
  76. me.langIsReady = true;
  77. me.fireEvent("langReady");
  78. }
  79. /**
  80. * 编辑器准备就绪后会触发该事件
  81. * @module UE
  82. * @class Editor
  83. * @event ready
  84. * @remind render方法执行完成之后,会触发该事件
  85. * @remind
  86. * @example
  87. * ```javascript
  88. * editor.addListener( 'ready', function( editor ) {
  89. * editor.execCommand( 'focus' ); //编辑器家在完成后,让编辑器拿到焦点
  90. * } );
  91. * ```
  92. */
  93. /**
  94. * 执行destroy方法,会触发该事件
  95. * @module UE
  96. * @class Editor
  97. * @event destroy
  98. * @see UE.Editor:destroy()
  99. */
  100. /**
  101. * 执行reset方法,会触发该事件
  102. * @module UE
  103. * @class Editor
  104. * @event reset
  105. * @see UE.Editor:reset()
  106. */
  107. /**
  108. * 执行focus方法,会触发该事件
  109. * @module UE
  110. * @class Editor
  111. * @event focus
  112. * @see UE.Editor:focus(Boolean)
  113. */
  114. /**
  115. * 语言加载完成会触发该事件
  116. * @module UE
  117. * @class Editor
  118. * @event langReady
  119. */
  120. /**
  121. * 运行命令之后会触发该命令
  122. * @module UE
  123. * @class Editor
  124. * @event beforeExecCommand
  125. */
  126. /**
  127. * 运行命令之后会触发该命令
  128. * @module UE
  129. * @class Editor
  130. * @event afterExecCommand
  131. */
  132. /**
  133. * 运行命令之前会触发该命令
  134. * @module UE
  135. * @class Editor
  136. * @event firstBeforeExecCommand
  137. */
  138. /**
  139. * 在getContent方法执行之前会触发该事件
  140. * @module UE
  141. * @class Editor
  142. * @event beforeGetContent
  143. * @see UE.Editor:getContent()
  144. */
  145. /**
  146. * 在getContent方法执行之后会触发该事件
  147. * @module UE
  148. * @class Editor
  149. * @event afterGetContent
  150. * @see UE.Editor:getContent()
  151. */
  152. /**
  153. * 在getAllHtml方法执行时会触发该事件
  154. * @module UE
  155. * @class Editor
  156. * @event getAllHtml
  157. * @see UE.Editor:getAllHtml()
  158. */
  159. /**
  160. * 在setContent方法执行之前会触发该事件
  161. * @module UE
  162. * @class Editor
  163. * @event beforeSetContent
  164. * @see UE.Editor:setContent(String)
  165. */
  166. /**
  167. * 在setContent方法执行之后会触发该事件
  168. * @module UE
  169. * @class Editor
  170. * @event afterSetContent
  171. * @see UE.Editor:setContent(String)
  172. */
  173. /**
  174. * 每当编辑器内部选区发生改变时,将触发该事件
  175. * @event selectionchange
  176. * @warning 该事件的触发非常频繁,不建议在该事件的处理过程中做重量级的处理
  177. * @example
  178. * ```javascript
  179. * editor.addListener( 'selectionchange', function( editor ) {
  180. * console.log('选区发生改变');
  181. * }
  182. */
  183. /**
  184. * 在所有selectionchange的监听函数执行之前,会触发该事件
  185. * @module UE
  186. * @class Editor
  187. * @event beforeSelectionChange
  188. * @see UE.Editor:selectionchange
  189. */
  190. /**
  191. * 在所有selectionchange的监听函数执行完之后,会触发该事件
  192. * @module UE
  193. * @class Editor
  194. * @event afterSelectionChange
  195. * @see UE.Editor:selectionchange
  196. */
  197. /**
  198. * 编辑器内容发生改变时会触发该事件
  199. * @module UE
  200. * @class Editor
  201. * @event contentChange
  202. */
  203. /**
  204. * 以默认参数构建一个编辑器实例
  205. * @constructor
  206. * @remind 通过 改构造方法实例化的编辑器,不带ui层.需要render到一个容器,编辑器实例才能正常渲染到页面
  207. * @example
  208. * ```javascript
  209. * var editor = new UE.Editor();
  210. * editor.execCommand('blod');
  211. * ```
  212. * @see UE.Config
  213. */
  214. /**
  215. * 以给定的参数集合创建一个编辑器实例,对于未指定的参数,将应用默认参数。
  216. * @constructor
  217. * @remind 通过 改构造方法实例化的编辑器,不带ui层.需要render到一个容器,编辑器实例才能正常渲染到页面
  218. * @param { Object } setting 创建编辑器的参数
  219. * @example
  220. * ```javascript
  221. * var editor = new UE.Editor();
  222. * editor.execCommand('blod');
  223. * ```
  224. * @see UE.Config
  225. */
  226. var Editor = (UE.Editor = function(options) {
  227. var me = this;
  228. me.uid = uid++;
  229. EventBase.call(me);
  230. me.commands = {};
  231. me.options = utils.extend(utils.clone(options || {}), UEDITOR_CONFIG, true);
  232. me.shortcutkeys = {};
  233. me.inputRules = [];
  234. me.outputRules = [];
  235. //设置默认的常用属性
  236. me.setOpt(Editor.defaultOptions(me));
  237. /* 尝试异步加载后台配置 */
  238. me.loadServerConfig();
  239. if (!utils.isEmptyObject(UE.I18N)) {
  240. //修改默认的语言类型
  241. me.options.lang = checkCurLang(UE.I18N);
  242. UE.plugin.load(me);
  243. langReadied(me);
  244. } else {
  245. utils.loadFile(
  246. document,
  247. {
  248. src:
  249. me.options.langPath +
  250. me.options.lang +
  251. "/" +
  252. me.options.lang +
  253. ".js",
  254. tag: "script",
  255. type: "text/javascript",
  256. defer: "defer"
  257. },
  258. function() {
  259. UE.plugin.load(me);
  260. langReadied(me);
  261. }
  262. );
  263. }
  264. UE.instants["ueditorInstant" + me.uid] = me;
  265. });
  266. Editor.prototype = {
  267. registerCommand: function(name, obj) {
  268. this.commands[name] = obj;
  269. },
  270. /**
  271. * 编辑器对外提供的监听ready事件的接口, 通过调用该方法,达到的效果与监听ready事件是一致的
  272. * @method ready
  273. * @param { Function } fn 编辑器ready之后所执行的回调, 如果在注册事件之前编辑器已经ready,将会
  274. * 立即触发该回调。
  275. * @remind 需要等待编辑器加载完成后才能执行的代码,可以使用该方法传入
  276. * @example
  277. * ```javascript
  278. * editor.ready( function( editor ) {
  279. * editor.setContent('初始化完毕');
  280. * } );
  281. * ```
  282. * @see UE.Editor.event:ready
  283. */
  284. ready: function(fn) {
  285. var me = this;
  286. if (fn) {
  287. me.isReady ? fn.apply(me) : me.addListener("ready", fn);
  288. }
  289. },
  290. /**
  291. * 该方法是提供给插件里面使用,设置配置项默认值
  292. * @method setOpt
  293. * @warning 三处设置配置项的优先级: 实例化时传入参数 > setOpt()设置 > config文件里设置
  294. * @warning 该方法仅供编辑器插件内部和编辑器初始化时调用,其他地方不能调用。
  295. * @param { String } key 编辑器的可接受的选项名称
  296. * @param { * } val 该选项可接受的值
  297. * @example
  298. * ```javascript
  299. * editor.setOpt( 'initContent', '欢迎使用编辑器' );
  300. * ```
  301. */
  302. /**
  303. * 该方法是提供给插件里面使用,以{key:value}集合的方式设置插件内用到的配置项默认值
  304. * @method setOpt
  305. * @warning 三处设置配置项的优先级: 实例化时传入参数 > setOpt()设置 > config文件里设置
  306. * @warning 该方法仅供编辑器插件内部和编辑器初始化时调用,其他地方不能调用。
  307. * @param { Object } options 将要设置的选项的键值对对象
  308. * @example
  309. * ```javascript
  310. * editor.setOpt( {
  311. * 'initContent': '欢迎使用编辑器'
  312. * } );
  313. * ```
  314. */
  315. setOpt: function(key, val) {
  316. var obj = {};
  317. if (utils.isString(key)) {
  318. obj[key] = val;
  319. } else {
  320. obj = key;
  321. }
  322. utils.extend(this.options, obj, true);
  323. },
  324. getOpt: function(key) {
  325. return this.options[key];
  326. },
  327. /**
  328. * 销毁编辑器实例,使用textarea代替
  329. * @method destroy
  330. * @example
  331. * ```javascript
  332. * editor.destroy();
  333. * ```
  334. */
  335. destroy: function() {
  336. var me = this;
  337. me.fireEvent("destroy");
  338. var container = me.container.parentNode;
  339. var textarea = me.textarea;
  340. if (!textarea) {
  341. textarea = document.createElement("textarea");
  342. container.parentNode.insertBefore(textarea, container);
  343. } else {
  344. textarea.style.display = "";
  345. }
  346. textarea.style.width = me.iframe.offsetWidth + "px";
  347. textarea.style.height = me.iframe.offsetHeight + "px";
  348. textarea.value = me.getContent();
  349. textarea.id = me.key;
  350. container.innerHTML = "";
  351. domUtils.remove(container);
  352. var key = me.key;
  353. //trace:2004
  354. for (var p in me) {
  355. if (me.hasOwnProperty(p)) {
  356. delete this[p];
  357. }
  358. }
  359. UE.delEditor(key);
  360. },
  361. /**
  362. * 渲染编辑器的DOM到指定容器
  363. * @method render
  364. * @param { String } containerId 指定一个容器ID
  365. * @remind 执行该方法,会触发ready事件
  366. * @warning 必须且只能调用一次
  367. */
  368. /**
  369. * 渲染编辑器的DOM到指定容器
  370. * @method render
  371. * @param { Element } containerDom 直接指定容器对象
  372. * @remind 执行该方法,会触发ready事件
  373. * @warning 必须且只能调用一次
  374. */
  375. render: function(container) {
  376. var me = this,
  377. options = me.options,
  378. getStyleValue = function(attr) {
  379. return parseInt(domUtils.getComputedStyle(container, attr));
  380. };
  381. if (utils.isString(container)) {
  382. container = document.getElementById(container);
  383. }
  384. if (container) {
  385. if (options.initialFrameWidth) {
  386. options.minFrameWidth = options.initialFrameWidth;
  387. } else {
  388. options.minFrameWidth = options.initialFrameWidth =
  389. container.offsetWidth;
  390. }
  391. if (options.initialFrameHeight) {
  392. options.minFrameHeight = options.initialFrameHeight;
  393. } else {
  394. options.initialFrameHeight = options.minFrameHeight =
  395. container.offsetHeight;
  396. }
  397. container.style.width = /%$/.test(options.initialFrameWidth)
  398. ? "100%"
  399. : options.initialFrameWidth -
  400. getStyleValue("padding-left") -
  401. getStyleValue("padding-right") +
  402. "px";
  403. container.style.height = /%$/.test(options.initialFrameHeight)
  404. ? "100%"
  405. : options.initialFrameHeight -
  406. getStyleValue("padding-top") -
  407. getStyleValue("padding-bottom") +
  408. "px";
  409. container.style.zIndex = options.zIndex;
  410. var html =
  411. (ie && browser.version < 9 ? "" : "<!DOCTYPE html>") +
  412. "<html xmlns='http://www.w3.org/1999/xhtml' class='view' >" +
  413. "<head>" +
  414. "<style type='text/css'>" +
  415. //设置四周的留边
  416. ".view{padding:0;word-wrap:break-word;cursor:text;height:90%;}\n" +
  417. //设置默认字体和字号
  418. //font-family不能呢随便改,在safari下fillchar会有解析问题
  419. "body{margin:8px;font-family:sans-serif;font-size:16px;}" +
  420. //设置段落间距
  421. "p{margin:5px 0;}</style>" +
  422. (options.iframeCssUrl
  423. ? "<link rel='stylesheet' type='text/css' href='" +
  424. utils.unhtml(options.iframeCssUrl) +
  425. "'/>"
  426. : "") +
  427. (options.initialStyle
  428. ? "<style>" + options.initialStyle + "</style>"
  429. : "") +
  430. "</head>" +
  431. "<body class='view' ></body>" +
  432. "<script type='text/javascript' " +
  433. (ie ? "defer='defer'" : "") +
  434. " id='_initialScript'>" +
  435. "setTimeout(function(){editor = window.parent.UE.instants['ueditorInstant" +
  436. me.uid +
  437. "'];editor._setup(document);},0);" +
  438. "var _tmpScript = document.getElementById('_initialScript');_tmpScript.parentNode.removeChild(_tmpScript);" +
  439. "</script>" +
  440. (options.iframeJsUrl
  441. ? "<script type='text/javascript' src='" +
  442. utils.unhtml(options.iframeJsUrl) +
  443. "'></script>"
  444. : "") +
  445. "</html>";
  446. container.appendChild(
  447. domUtils.createElement(document, "iframe", {
  448. id: "ueditor_" + me.uid,
  449. width: "100%",
  450. height: "100%",
  451. frameborder: "0",
  452. //先注释掉了,加的原因忘记了,但开启会直接导致全屏模式下内容多时不会出现滚动条
  453. // scrolling :'no',
  454. src:
  455. "javascript:void(function(){document.open();" +
  456. (options.customDomain && document.domain != location.hostname
  457. ? 'document.domain="' + document.domain + '";'
  458. : "") +
  459. 'document.write("' +
  460. html +
  461. '");document.close();}())'
  462. })
  463. );
  464. container.style.overflow = "hidden";
  465. //解决如果是给定的百分比,会导致高度算不对的问题
  466. setTimeout(function() {
  467. if (/%$/.test(options.initialFrameWidth)) {
  468. options.minFrameWidth = options.initialFrameWidth =
  469. container.offsetWidth;
  470. //如果这里给定宽度,会导致ie在拖动窗口大小时,编辑区域不随着变化
  471. // container.style.width = options.initialFrameWidth + 'px';
  472. }
  473. if (/%$/.test(options.initialFrameHeight)) {
  474. options.minFrameHeight = options.initialFrameHeight =
  475. container.offsetHeight;
  476. container.style.height = options.initialFrameHeight + "px";
  477. }
  478. });
  479. }
  480. },
  481. /**
  482. * 编辑器初始化
  483. * @method _setup
  484. * @private
  485. * @param { Element } doc 编辑器Iframe中的文档对象
  486. */
  487. _setup: function(doc) {
  488. var me = this,
  489. options = me.options;
  490. if (ie) {
  491. doc.body.disabled = true;
  492. doc.body.contentEditable = true;
  493. doc.body.disabled = false;
  494. } else {
  495. doc.body.contentEditable = true;
  496. }
  497. doc.body.spellcheck = false;
  498. me.document = doc;
  499. me.window = doc.defaultView || doc.parentWindow;
  500. me.iframe = me.window.frameElement;
  501. me.body = doc.body;
  502. me.selection = new dom.Selection(doc);
  503. //gecko初始化就能得到range,无法判断isFocus了
  504. var geckoSel;
  505. if (browser.gecko && (geckoSel = this.selection.getNative())) {
  506. geckoSel.removeAllRanges();
  507. }
  508. this._initEvents();
  509. //为form提交提供一个隐藏的textarea
  510. for (
  511. var form = this.iframe.parentNode;
  512. !domUtils.isBody(form);
  513. form = form.parentNode
  514. ) {
  515. if (form.tagName == "FORM") {
  516. me.form = form;
  517. if (me.options.autoSyncData) {
  518. domUtils.on(me.window, "blur", function() {
  519. setValue(form, me);
  520. });
  521. } else {
  522. domUtils.on(form, "submit", function() {
  523. setValue(this, me);
  524. });
  525. }
  526. break;
  527. }
  528. }
  529. if (options.initialContent) {
  530. if (options.autoClearinitialContent) {
  531. var oldExecCommand = me.execCommand;
  532. me.execCommand = function() {
  533. me.fireEvent("firstBeforeExecCommand");
  534. return oldExecCommand.apply(me, arguments);
  535. };
  536. this._setDefaultContent(options.initialContent);
  537. } else this.setContent(options.initialContent, false, true);
  538. }
  539. //编辑器不能为空内容
  540. if (domUtils.isEmptyNode(me.body)) {
  541. me.body.innerHTML = "<p>" + (browser.ie ? "" : "<br/>") + "</p>";
  542. }
  543. //如果要求focus, 就把光标定位到内容开始
  544. if (options.focus) {
  545. setTimeout(function() {
  546. me.focus(me.options.focusInEnd);
  547. //如果自动清除开着,就不需要做selectionchange;
  548. !me.options.autoClearinitialContent && me._selectionChange();
  549. }, 0);
  550. }
  551. if (!me.container) {
  552. me.container = this.iframe.parentNode;
  553. }
  554. if (options.fullscreen && me.ui) {
  555. me.ui.setFullScreen(true);
  556. }
  557. try {
  558. me.document.execCommand("2D-position", false, false);
  559. } catch (e) {}
  560. try {
  561. me.document.execCommand("enableInlineTableEditing", false, false);
  562. } catch (e) {}
  563. try {
  564. me.document.execCommand("enableObjectResizing", false, false);
  565. } catch (e) {}
  566. //挂接快捷键
  567. me._bindshortcutKeys();
  568. me.isReady = 1;
  569. me.fireEvent("ready");
  570. options.onready && options.onready.call(me);
  571. if (!browser.ie9below) {
  572. domUtils.on(me.window, ["blur", "focus"], function(e) {
  573. //chrome下会出现alt+tab切换时,导致选区位置不对
  574. if (e.type == "blur") {
  575. me._bakRange = me.selection.getRange();
  576. try {
  577. me._bakNativeRange = me.selection.getNative().getRangeAt(0);
  578. me.selection.getNative().removeAllRanges();
  579. } catch (e) {
  580. me._bakNativeRange = null;
  581. }
  582. } else {
  583. try {
  584. me._bakRange && me._bakRange.select();
  585. } catch (e) {}
  586. }
  587. });
  588. }
  589. //trace:1518 ff3.6body不够寛,会导致点击空白处无法获得焦点
  590. if (browser.gecko && browser.version <= 10902) {
  591. //修复ff3.6初始化进来,不能点击获得焦点
  592. me.body.contentEditable = false;
  593. setTimeout(function() {
  594. me.body.contentEditable = true;
  595. }, 100);
  596. setInterval(function() {
  597. me.body.style.height = me.iframe.offsetHeight - 20 + "px";
  598. }, 100);
  599. }
  600. !options.isShow && me.setHide();
  601. options.readonly && me.setDisabled();
  602. },
  603. /**
  604. * 同步数据到编辑器所在的form
  605. * 从编辑器的容器节点向上查找form元素,若找到,就同步编辑内容到找到的form里,为提交数据做准备,主要用于是手动提交的情况
  606. * 后台取得数据的键值,使用你容器上的name属性,如果没有就使用参数里的textarea项
  607. * @method sync
  608. * @example
  609. * ```javascript
  610. * editor.sync();
  611. * form.sumbit(); //form变量已经指向了form元素
  612. * ```
  613. */
  614. /**
  615. * 根据传入的formId,在页面上查找要同步数据的表单,若找到,就同步编辑内容到找到的form里,为提交数据做准备
  616. * 后台取得数据的键值,该键值默认使用给定的编辑器容器的name属性,如果没有name属性则使用参数项里给定的“textarea”项
  617. * @method sync
  618. * @param { String } formID 指定一个要同步数据的form的id,编辑器的数据会同步到你指定form下
  619. */
  620. sync: function(formId) {
  621. var me = this,
  622. form = formId
  623. ? document.getElementById(formId)
  624. : domUtils.findParent(
  625. me.iframe.parentNode,
  626. function(node) {
  627. return node.tagName == "FORM";
  628. },
  629. true
  630. );
  631. form && setValue(form, me);
  632. },
  633. /**
  634. * 设置编辑器高度
  635. * @method setHeight
  636. * @remind 当配置项autoHeightEnabled为真时,该方法无效
  637. * @param { Number } number 设置的高度值,纯数值,不带单位
  638. * @example
  639. * ```javascript
  640. * editor.setHeight(number);
  641. * ```
  642. */
  643. setHeight: function(height, notSetHeight) {
  644. if (height !== parseInt(this.iframe.parentNode.style.height)) {
  645. this.iframe.parentNode.style.height = height + "px";
  646. }
  647. !notSetHeight &&
  648. (this.options.minFrameHeight = this.options.initialFrameHeight = height);
  649. this.body.style.height = height + "px";
  650. !notSetHeight && this.trigger("setHeight");
  651. },
  652. /**
  653. * 为编辑器的编辑命令提供快捷键
  654. * 这个接口是为插件扩展提供的接口,主要是为新添加的插件,如果需要添加快捷键,所提供的接口
  655. * @method addshortcutkey
  656. * @param { Object } keyset 命令名和快捷键键值对对象,多个按钮的快捷键用“+”分隔
  657. * @example
  658. * ```javascript
  659. * editor.addshortcutkey({
  660. * "Bold" : "ctrl+66",//^B
  661. * "Italic" : "ctrl+73", //^I
  662. * });
  663. * ```
  664. */
  665. /**
  666. * 这个接口是为插件扩展提供的接口,主要是为新添加的插件,如果需要添加快捷键,所提供的接口
  667. * @method addshortcutkey
  668. * @param { String } cmd 触发快捷键时,响应的命令
  669. * @param { String } keys 快捷键的字符串,多个按钮用“+”分隔
  670. * @example
  671. * ```javascript
  672. * editor.addshortcutkey("Underline", "ctrl+85"); //^U
  673. * ```
  674. */
  675. addshortcutkey: function(cmd, keys) {
  676. var obj = {};
  677. if (keys) {
  678. obj[cmd] = keys;
  679. } else {
  680. obj = cmd;
  681. }
  682. utils.extend(this.shortcutkeys, obj);
  683. },
  684. /**
  685. * 对编辑器设置keydown事件监听,绑定快捷键和命令,当快捷键组合触发成功,会响应对应的命令
  686. * @method _bindshortcutKeys
  687. * @private
  688. */
  689. _bindshortcutKeys: function() {
  690. var me = this,
  691. shortcutkeys = this.shortcutkeys;
  692. me.addListener("keydown", function(type, e) {
  693. var keyCode = e.keyCode || e.which;
  694. for (var i in shortcutkeys) {
  695. var tmp = shortcutkeys[i].split(",");
  696. for (var t = 0, ti; (ti = tmp[t++]); ) {
  697. ti = ti.split(":");
  698. var key = ti[0],
  699. param = ti[1];
  700. if (
  701. /^(ctrl)(\+shift)?\+(\d+)$/.test(key.toLowerCase()) ||
  702. /^(\d+)$/.test(key)
  703. ) {
  704. if (
  705. ((RegExp.$1 == "ctrl" ? e.ctrlKey || e.metaKey : 0) &&
  706. (RegExp.$2 != "" ? e[RegExp.$2.slice(1) + "Key"] : 1) &&
  707. keyCode == RegExp.$3) ||
  708. keyCode == RegExp.$1
  709. ) {
  710. if (me.queryCommandState(i, param) != -1)
  711. me.execCommand(i, param);
  712. domUtils.preventDefault(e);
  713. }
  714. }
  715. }
  716. }
  717. });
  718. },
  719. /**
  720. * 获取编辑器的内容
  721. * @method getContent
  722. * @warning 该方法获取到的是经过编辑器内置的过滤规则进行过滤后得到的内容
  723. * @return { String } 编辑器的内容字符串, 如果编辑器的内容为空,或者是空的标签内容(如:”&lt;p&gt;&lt;br/&gt;&lt;/p&gt;“), 则返回空字符串
  724. * @example
  725. * ```javascript
  726. * //编辑器html内容:<p>1<strong>2<em>34</em>5</strong>6</p>
  727. * var content = editor.getContent(); //返回值:<p>1<strong>2<em>34</em>5</strong>6</p>
  728. * ```
  729. */
  730. /**
  731. * 获取编辑器的内容。 可以通过参数定义编辑器内置的判空规则
  732. * @method getContent
  733. * @param { Function } fn 自定的判空规则, 要求该方法返回一个boolean类型的值,
  734. * 代表当前编辑器的内容是否空,
  735. * 如果返回true, 则该方法将直接返回空字符串;如果返回false,则编辑器将返回
  736. * 经过内置过滤规则处理后的内容。
  737. * @remind 该方法在处理包含有初始化内容的时候能起到很好的作用。
  738. * @warning 该方法获取到的是经过编辑器内置的过滤规则进行过滤后得到的内容
  739. * @return { String } 编辑器的内容字符串
  740. * @example
  741. * ```javascript
  742. * // editor 是一个编辑器的实例
  743. * var content = editor.getContent( function ( editor ) {
  744. * return editor.body.innerHTML === '欢迎使用UEditor'; //返回空字符串
  745. * } );
  746. * ```
  747. */
  748. getContent: function(cmd, fn, notSetCursor, ignoreBlank, formatter) {
  749. var me = this;
  750. if (cmd && utils.isFunction(cmd)) {
  751. fn = cmd;
  752. cmd = "";
  753. }
  754. if (fn ? !fn() : !this.hasContents()) {
  755. return "";
  756. }
  757. me.fireEvent("beforegetcontent");
  758. var root = UE.htmlparser(me.body.innerHTML, ignoreBlank);
  759. me.filterOutputRule(root);
  760. me.fireEvent("aftergetcontent", cmd, root);
  761. return root.toHtml(formatter);
  762. },
  763. /**
  764. * 取得完整的html代码,可以直接显示成完整的html文档
  765. * @method getAllHtml
  766. * @return { String } 编辑器的内容html文档字符串
  767. * @eaxmple
  768. * ```javascript
  769. * editor.getAllHtml(); //返回格式大致是: <html><head>...</head><body>...</body></html>
  770. * ```
  771. */
  772. getAllHtml: function() {
  773. var me = this,
  774. headHtml = [],
  775. html = "";
  776. me.fireEvent("getAllHtml", headHtml);
  777. if (browser.ie && browser.version > 8) {
  778. var headHtmlForIE9 = "";
  779. utils.each(me.document.styleSheets, function(si) {
  780. headHtmlForIE9 += si.href
  781. ? '<link rel="stylesheet" type="text/css" href="' + si.href + '" />'
  782. : "<style>" + si.cssText + "</style>";
  783. });
  784. utils.each(me.document.getElementsByTagName("script"), function(si) {
  785. headHtmlForIE9 += si.outerHTML;
  786. });
  787. }
  788. return (
  789. "<html><head>" +
  790. (me.options.charset
  791. ? '<meta http-equiv="Content-Type" content="text/html; charset=' +
  792. me.options.charset +
  793. '"/>'
  794. : "") +
  795. (headHtmlForIE9 ||
  796. me.document.getElementsByTagName("head")[0].innerHTML) +
  797. headHtml.join("\n") +
  798. "</head>" +
  799. "<body " +
  800. (ie && browser.version < 9 ? 'class="view"' : "") +
  801. ">" +
  802. me.getContent(null, null, true) +
  803. "</body></html>"
  804. );
  805. },
  806. /**
  807. * 得到编辑器的纯文本内容,但会保留段落格式
  808. * @method getPlainTxt
  809. * @return { String } 编辑器带段落格式的纯文本内容字符串
  810. * @example
  811. * ```javascript
  812. * //编辑器html内容:<p><strong>1</strong></p><p><strong>2</strong></p>
  813. * console.log(editor.getPlainTxt()); //输出:"1\n2\n
  814. * ```
  815. */
  816. getPlainTxt: function() {
  817. var reg = new RegExp(domUtils.fillChar, "g"),
  818. html = this.body.innerHTML.replace(/[\n\r]/g, ""); //ie要先去了\n在处理
  819. html = html
  820. .replace(/<(p|div)[^>]*>(<br\/?>|&nbsp;)<\/\1>/gi, "\n")
  821. .replace(/<br\/?>/gi, "\n")
  822. .replace(/<[^>/]+>/g, "")
  823. .replace(/(\n)?<\/([^>]+)>/g, function(a, b, c) {
  824. return dtd.$block[c] ? "\n" : b ? b : "";
  825. });
  826. //取出来的空格会有c2a0会变成乱码,处理这种情况\u00a0
  827. return html
  828. .replace(reg, "")
  829. .replace(/\u00a0/g, " ")
  830. .replace(/&nbsp;/g, " ");
  831. },
  832. /**
  833. * 获取编辑器中的纯文本内容,没有段落格式
  834. * @method getContentTxt
  835. * @return { String } 编辑器不带段落格式的纯文本内容字符串
  836. * @example
  837. * ```javascript
  838. * //编辑器html内容:<p><strong>1</strong></p><p><strong>2</strong></p>
  839. * console.log(editor.getPlainTxt()); //输出:"12
  840. * ```
  841. */
  842. getContentTxt: function() {
  843. var reg = new RegExp(domUtils.fillChar, "g");
  844. //取出来的空格会有c2a0会变成乱码,处理这种情况\u00a0
  845. return this.body[browser.ie ? "innerText" : "textContent"]
  846. .replace(reg, "")
  847. .replace(/\u00a0/g, " ");
  848. },
  849. /**
  850. * 设置编辑器的内容,可修改编辑器当前的html内容
  851. * @method setContent
  852. * @warning 通过该方法插入的内容,是经过编辑器内置的过滤规则进行过滤后得到的内容
  853. * @warning 该方法会触发selectionchange事件
  854. * @param { String } html 要插入的html内容
  855. * @example
  856. * ```javascript
  857. * editor.getContent('<p>test</p>');
  858. * ```
  859. */
  860. /**
  861. * 设置编辑器的内容,可修改编辑器当前的html内容
  862. * @method setContent
  863. * @warning 通过该方法插入的内容,是经过编辑器内置的过滤规则进行过滤后得到的内容
  864. * @warning 该方法会触发selectionchange事件
  865. * @param { String } html 要插入的html内容
  866. * @param { Boolean } isAppendTo 若传入true,不清空原来的内容,在最后插入内容,否则,清空内容再插入
  867. * @example
  868. * ```javascript
  869. * //假设设置前的编辑器内容是 <p>old text</p>
  870. * editor.setContent('<p>new text</p>', true); //插入的结果是<p>old text</p><p>new text</p>
  871. * ```
  872. */
  873. setContent: function(html, isAppendTo, notFireSelectionchange) {
  874. var me = this;
  875. me.fireEvent("beforesetcontent", html);
  876. var root = UE.htmlparser(html);
  877. me.filterInputRule(root);
  878. html = root.toHtml();
  879. me.body.innerHTML = (isAppendTo ? me.body.innerHTML : "") + html;
  880. function isCdataDiv(node) {
  881. return node.tagName == "DIV" && node.getAttribute("cdata_tag");
  882. }
  883. //给文本或者inline节点套p标签
  884. if (me.options.enterTag == "p") {
  885. var child = this.body.firstChild,
  886. tmpNode;
  887. if (
  888. !child ||
  889. (child.nodeType == 1 &&
  890. (dtd.$cdata[child.tagName] ||
  891. isCdataDiv(child) ||
  892. domUtils.isCustomeNode(child)) &&
  893. child === this.body.lastChild)
  894. ) {
  895. this.body.innerHTML =
  896. "<p>" +
  897. (browser.ie ? "&nbsp;" : "<br/>") +
  898. "</p>" +
  899. this.body.innerHTML;
  900. } else {
  901. var p = me.document.createElement("p");
  902. while (child) {
  903. while (
  904. child &&
  905. (child.nodeType == 3 ||
  906. (child.nodeType == 1 &&
  907. dtd.p[child.tagName] &&
  908. !dtd.$cdata[child.tagName]))
  909. ) {
  910. tmpNode = child.nextSibling;
  911. p.appendChild(child);
  912. child = tmpNode;
  913. }
  914. if (p.firstChild) {
  915. if (!child) {
  916. me.body.appendChild(p);
  917. break;
  918. } else {
  919. child.parentNode.insertBefore(p, child);
  920. p = me.document.createElement("p");
  921. }
  922. }
  923. child = child.nextSibling;
  924. }
  925. }
  926. }
  927. me.fireEvent("aftersetcontent");
  928. me.fireEvent("contentchange");
  929. !notFireSelectionchange && me._selectionChange();
  930. //清除保存的选区
  931. me._bakRange = me._bakIERange = me._bakNativeRange = null;
  932. //trace:1742 setContent后gecko能得到焦点问题
  933. var geckoSel;
  934. if (browser.gecko && (geckoSel = this.selection.getNative())) {
  935. geckoSel.removeAllRanges();
  936. }
  937. if (me.options.autoSyncData) {
  938. me.form && setValue(me.form, me);
  939. }
  940. },
  941. /**
  942. * 让编辑器获得焦点,默认focus到编辑器头部
  943. * @method focus
  944. * @example
  945. * ```javascript
  946. * editor.focus()
  947. * ```
  948. */
  949. /**
  950. * 让编辑器获得焦点,toEnd确定focus位置
  951. * @method focus
  952. * @param { Boolean } toEnd 默认focus到编辑器头部,toEnd为true时focus到内容尾部
  953. * @example
  954. * ```javascript
  955. * editor.focus(true)
  956. * ```
  957. */
  958. focus: function(toEnd) {
  959. try {
  960. var me = this,
  961. rng = me.selection.getRange();
  962. if (toEnd) {
  963. var node = me.body.lastChild;
  964. if (node && node.nodeType == 1 && !dtd.$empty[node.tagName]) {
  965. if (domUtils.isEmptyBlock(node)) {
  966. rng.setStartAtFirst(node);
  967. } else {
  968. rng.setStartAtLast(node);
  969. }
  970. rng.collapse(true);
  971. }
  972. rng.setCursor(true);
  973. } else {
  974. if (
  975. !rng.collapsed &&
  976. domUtils.isBody(rng.startContainer) &&
  977. rng.startOffset == 0
  978. ) {
  979. var node = me.body.firstChild;
  980. if (node && node.nodeType == 1 && !dtd.$empty[node.tagName]) {
  981. rng.setStartAtFirst(node).collapse(true);
  982. }
  983. }
  984. rng.select(true);
  985. }
  986. this.fireEvent("focus selectionchange");
  987. } catch (e) {}
  988. },
  989. isFocus: function() {
  990. return this.selection.isFocus();
  991. },
  992. blur: function() {
  993. var sel = this.selection.getNative();
  994. if (sel.empty && browser.ie) {
  995. var nativeRng = document.body.createTextRange();
  996. nativeRng.moveToElementText(document.body);
  997. nativeRng.collapse(true);
  998. nativeRng.select();
  999. sel.empty();
  1000. } else {
  1001. sel.removeAllRanges();
  1002. }
  1003. //this.fireEvent('blur selectionchange');
  1004. },
  1005. /**
  1006. * 初始化UE事件及部分事件代理
  1007. * @method _initEvents
  1008. * @private
  1009. */
  1010. _initEvents: function() {
  1011. var me = this,
  1012. doc = me.document,
  1013. win = me.window;
  1014. me._proxyDomEvent = utils.bind(me._proxyDomEvent, me);
  1015. domUtils.on(
  1016. doc,
  1017. [
  1018. "click",
  1019. "contextmenu",
  1020. "mousedown",
  1021. "keydown",
  1022. "keyup",
  1023. "keypress",
  1024. "mouseup",
  1025. "mouseover",
  1026. "mouseout",
  1027. "selectstart"
  1028. ],
  1029. me._proxyDomEvent
  1030. );
  1031. domUtils.on(win, ["focus", "blur"], me._proxyDomEvent);
  1032. domUtils.on(me.body, "drop", function(e) {
  1033. //阻止ff下默认的弹出新页面打开图片
  1034. if (browser.gecko && e.stopPropagation) {
  1035. e.stopPropagation();
  1036. }
  1037. me.fireEvent("contentchange");
  1038. });
  1039. domUtils.on(doc, ["mouseup", "keydown"], function(evt) {
  1040. //特殊键不触发selectionchange
  1041. if (
  1042. evt.type == "keydown" &&
  1043. (evt.ctrlKey || evt.metaKey || evt.shiftKey || evt.altKey)
  1044. ) {
  1045. return;
  1046. }
  1047. if (evt.button == 2) return;
  1048. me._selectionChange(250, evt);
  1049. });
  1050. },
  1051. /**
  1052. * 触发事件代理
  1053. * @method _proxyDomEvent
  1054. * @private
  1055. * @return { * } fireEvent的返回值
  1056. * @see UE.EventBase:fireEvent(String)
  1057. */
  1058. _proxyDomEvent: function(evt) {
  1059. if (
  1060. this.fireEvent("before" + evt.type.replace(/^on/, "").toLowerCase()) ===
  1061. false
  1062. ) {
  1063. return false;
  1064. }
  1065. if (this.fireEvent(evt.type.replace(/^on/, ""), evt) === false) {
  1066. return false;
  1067. }
  1068. return this.fireEvent(
  1069. "after" + evt.type.replace(/^on/, "").toLowerCase()
  1070. );
  1071. },
  1072. /**
  1073. * 变化选区
  1074. * @method _selectionChange
  1075. * @private
  1076. */
  1077. _selectionChange: function(delay, evt) {
  1078. var me = this;
  1079. //有光标才做selectionchange 为了解决未focus时点击source不能触发更改工具栏状态的问题(source命令notNeedUndo=1)
  1080. // if ( !me.selection.isFocus() ){
  1081. // return;
  1082. // }
  1083. var hackForMouseUp = false;
  1084. var mouseX, mouseY;
  1085. if (browser.ie && browser.version < 9 && evt && evt.type == "mouseup") {
  1086. var range = this.selection.getRange();
  1087. if (!range.collapsed) {
  1088. hackForMouseUp = true;
  1089. mouseX = evt.clientX;
  1090. mouseY = evt.clientY;
  1091. }
  1092. }
  1093. clearTimeout(_selectionChangeTimer);
  1094. _selectionChangeTimer = setTimeout(function() {
  1095. if (!me.selection || !me.selection.getNative()) {
  1096. return;
  1097. }
  1098. //修复一个IE下的bug: 鼠标点击一段已选择的文本中间时,可能在mouseup后的一段时间内取到的range是在selection的type为None下的错误值.
  1099. //IE下如果用户是拖拽一段已选择文本,则不会触发mouseup事件,所以这里的特殊处理不会对其有影响
  1100. var ieRange;
  1101. if (hackForMouseUp && me.selection.getNative().type == "None") {
  1102. ieRange = me.document.body.createTextRange();
  1103. try {
  1104. ieRange.moveToPoint(mouseX, mouseY);
  1105. } catch (ex) {
  1106. ieRange = null;
  1107. }
  1108. }
  1109. var bakGetIERange;
  1110. if (ieRange) {
  1111. bakGetIERange = me.selection.getIERange;
  1112. me.selection.getIERange = function() {
  1113. return ieRange;
  1114. };
  1115. }
  1116. me.selection.cache();
  1117. if (bakGetIERange) {
  1118. me.selection.getIERange = bakGetIERange;
  1119. }
  1120. if (me.selection._cachedRange && me.selection._cachedStartElement) {
  1121. me.fireEvent("beforeselectionchange");
  1122. // 第二个参数causeByUi为true代表由用户交互造成的selectionchange.
  1123. me.fireEvent("selectionchange", !!evt);
  1124. me.fireEvent("afterselectionchange");
  1125. me.selection.clear();
  1126. }
  1127. }, delay || 50);
  1128. },
  1129. /**
  1130. * 执行编辑命令
  1131. * @method _callCmdFn
  1132. * @private
  1133. * @param { String } fnName 函数名称
  1134. * @param { * } args 传给命令函数的参数
  1135. * @return { * } 返回命令函数运行的返回值
  1136. */
  1137. _callCmdFn: function(fnName, args) {
  1138. var cmdName = args[0].toLowerCase(),
  1139. cmd,
  1140. cmdFn;
  1141. cmd = this.commands[cmdName] || UE.commands[cmdName];
  1142. cmdFn = cmd && cmd[fnName];
  1143. //没有querycommandstate或者没有command的都默认返回0
  1144. if ((!cmd || !cmdFn) && fnName == "queryCommandState") {
  1145. return 0;
  1146. } else if (cmdFn) {
  1147. return cmdFn.apply(this, args);
  1148. }
  1149. },
  1150. /**
  1151. * 执行编辑命令cmdName,完成富文本编辑效果
  1152. * @method execCommand
  1153. * @param { String } cmdName 需要执行的命令
  1154. * @remind 具体命令的使用请参考<a href="#COMMAND.LIST">命令列表</a>
  1155. * @return { * } 返回命令函数运行的返回值
  1156. * @example
  1157. * ```javascript
  1158. * editor.execCommand(cmdName);
  1159. * ```
  1160. */
  1161. execCommand: function(cmdName) {
  1162. cmdName = cmdName.toLowerCase();
  1163. var me = this,
  1164. result,
  1165. cmd = me.commands[cmdName] || UE.commands[cmdName];
  1166. if (!cmd || !cmd.execCommand) {
  1167. return null;
  1168. }
  1169. if (!cmd.notNeedUndo && !me.__hasEnterExecCommand) {
  1170. me.__hasEnterExecCommand = true;
  1171. if (me.queryCommandState.apply(me, arguments) != -1) {
  1172. me.fireEvent("saveScene");
  1173. me.fireEvent.apply(
  1174. me,
  1175. ["beforeexeccommand", cmdName].concat(arguments)
  1176. );
  1177. result = this._callCmdFn("execCommand", arguments);
  1178. //保存场景时,做了内容对比,再看是否进行contentchange触发,这里多触发了一次,去掉
  1179. // (!cmd.ignoreContentChange && !me._ignoreContentChange) && me.fireEvent('contentchange');
  1180. me.fireEvent.apply(
  1181. me,
  1182. ["afterexeccommand", cmdName].concat(arguments)
  1183. );
  1184. me.fireEvent("saveScene");
  1185. }
  1186. me.__hasEnterExecCommand = false;
  1187. } else {
  1188. result = this._callCmdFn("execCommand", arguments);
  1189. !me.__hasEnterExecCommand &&
  1190. !cmd.ignoreContentChange &&
  1191. !me._ignoreContentChange &&
  1192. me.fireEvent("contentchange");
  1193. }
  1194. !me.__hasEnterExecCommand &&
  1195. !cmd.ignoreContentChange &&
  1196. !me._ignoreContentChange &&
  1197. me._selectionChange();
  1198. return result;
  1199. },
  1200. /**
  1201. * 根据传入的command命令,查选编辑器当前的选区,返回命令的状态
  1202. * @method queryCommandState
  1203. * @param { String } cmdName 需要查询的命令名称
  1204. * @remind 具体命令的使用请参考<a href="#COMMAND.LIST">命令列表</a>
  1205. * @return { Number } number 返回放前命令的状态,返回值三种情况:(-1|0|1)
  1206. * @example
  1207. * ```javascript
  1208. * editor.queryCommandState(cmdName) => (-1|0|1)
  1209. * ```
  1210. * @see COMMAND.LIST
  1211. */
  1212. queryCommandState: function(cmdName) {
  1213. return this._callCmdFn("queryCommandState", arguments);
  1214. },
  1215. /**
  1216. * 根据传入的command命令,查选编辑器当前的选区,根据命令返回相关的值
  1217. * @method queryCommandValue
  1218. * @param { String } cmdName 需要查询的命令名称
  1219. * @remind 具体命令的使用请参考<a href="#COMMAND.LIST">命令列表</a>
  1220. * @remind 只有部分插件有此方法
  1221. * @return { * } 返回每个命令特定的当前状态值
  1222. * @grammar editor.queryCommandValue(cmdName) => {*}
  1223. * @see COMMAND.LIST
  1224. */
  1225. queryCommandValue: function(cmdName) {
  1226. return this._callCmdFn("queryCommandValue", arguments);
  1227. },
  1228. /**
  1229. * 检查编辑区域中是否有内容
  1230. * @method hasContents
  1231. * @remind 默认有文本内容,或者有以下节点都不认为是空
  1232. * table,ul,ol,dl,iframe,area,base,col,hr,img,embed,input,link,meta,param
  1233. * @return { Boolean } 检查有内容返回true,否则返回false
  1234. * @example
  1235. * ```javascript
  1236. * editor.hasContents()
  1237. * ```
  1238. */
  1239. /**
  1240. * 检查编辑区域中是否有内容,若包含参数tags中的节点类型,直接返回true
  1241. * @method hasContents
  1242. * @param { Array } tags 传入数组判断时用到的节点类型
  1243. * @return { Boolean } 若文档中包含tags数组里对应的tag,返回true,否则返回false
  1244. * @example
  1245. * ```javascript
  1246. * editor.hasContents(['span']);
  1247. * ```
  1248. */
  1249. hasContents: function(tags) {
  1250. if (tags) {
  1251. for (var i = 0, ci; (ci = tags[i++]); ) {
  1252. if (this.document.getElementsByTagName(ci).length > 0) {
  1253. return true;
  1254. }
  1255. }
  1256. }
  1257. if (!domUtils.isEmptyBlock(this.body)) {
  1258. return true;
  1259. }
  1260. //随时添加,定义的特殊标签如果存在,不能认为是空
  1261. tags = ["div"];
  1262. for (i = 0; (ci = tags[i++]); ) {
  1263. var nodes = domUtils.getElementsByTagName(this.document, ci);
  1264. for (var n = 0, cn; (cn = nodes[n++]); ) {
  1265. if (domUtils.isCustomeNode(cn)) {
  1266. return true;
  1267. }
  1268. }
  1269. }
  1270. return false;
  1271. },
  1272. /**
  1273. * 重置编辑器,可用来做多个tab使用同一个编辑器实例
  1274. * @method reset
  1275. * @remind 此方法会清空编辑器内容,清空回退列表,会触发reset事件
  1276. * @example
  1277. * ```javascript
  1278. * editor.reset()
  1279. * ```
  1280. */
  1281. reset: function() {
  1282. this.fireEvent("reset");
  1283. },
  1284. /**
  1285. * 设置当前编辑区域可以编辑
  1286. * @method setEnabled
  1287. * @example
  1288. * ```javascript
  1289. * editor.setEnabled()
  1290. * ```
  1291. */
  1292. setEnabled: function() {
  1293. var me = this,
  1294. range;
  1295. if (me.body.contentEditable == "false") {
  1296. me.body.contentEditable = true;
  1297. range = me.selection.getRange();
  1298. //有可能内容丢失了
  1299. try {
  1300. range.moveToBookmark(me.lastBk);
  1301. delete me.lastBk;
  1302. } catch (e) {
  1303. range.setStartAtFirst(me.body).collapse(true);
  1304. }
  1305. range.select(true);
  1306. if (me.bkqueryCommandState) {
  1307. me.queryCommandState = me.bkqueryCommandState;
  1308. delete me.bkqueryCommandState;
  1309. }
  1310. if (me.bkqueryCommandValue) {
  1311. me.queryCommandValue = me.bkqueryCommandValue;
  1312. delete me.bkqueryCommandValue;
  1313. }
  1314. me.fireEvent("selectionchange");
  1315. }
  1316. },
  1317. enable: function() {
  1318. return this.setEnabled();
  1319. },
  1320. /** 设置当前编辑区域不可编辑
  1321. * @method setDisabled
  1322. */
  1323. /** 设置当前编辑区域不可编辑,except中的命令除外
  1324. * @method setDisabled
  1325. * @param { String } except 例外命令的字符串
  1326. * @remind 即使设置了disable,此处配置的例外命令仍然可以执行
  1327. * @example
  1328. * ```javascript
  1329. * editor.setDisabled('bold'); //禁用工具栏中除加粗之外的所有功能
  1330. * ```
  1331. */
  1332. /** 设置当前编辑区域不可编辑,except中的命令除外
  1333. * @method setDisabled
  1334. * @param { Array } except 例外命令的字符串数组,数组中的命令仍然可以执行
  1335. * @remind 即使设置了disable,此处配置的例外命令仍然可以执行
  1336. * @example
  1337. * ```javascript
  1338. * editor.setDisabled(['bold','insertimage']); //禁用工具栏中除加粗和插入图片之外的所有功能
  1339. * ```
  1340. */
  1341. setDisabled: function(except) {
  1342. var me = this;
  1343. except = except ? (utils.isArray(except) ? except : [except]) : [];
  1344. if (me.body.contentEditable == "true") {
  1345. if (!me.lastBk) {
  1346. me.lastBk = me.selection.getRange().createBookmark(true);
  1347. }
  1348. me.body.contentEditable = false;
  1349. me.bkqueryCommandState = me.queryCommandState;
  1350. me.bkqueryCommandValue = me.queryCommandValue;
  1351. me.queryCommandState = function(type) {
  1352. if (utils.indexOf(except, type) != -1) {
  1353. return me.bkqueryCommandState.apply(me, arguments);
  1354. }
  1355. return -1;
  1356. };
  1357. me.queryCommandValue = function(type) {
  1358. if (utils.indexOf(except, type) != -1) {
  1359. return me.bkqueryCommandValue.apply(me, arguments);
  1360. }
  1361. return null;
  1362. };
  1363. me.fireEvent("selectionchange");
  1364. }
  1365. },
  1366. disable: function(except) {
  1367. return this.setDisabled(except);
  1368. },
  1369. /**
  1370. * 设置默认内容
  1371. * @method _setDefaultContent
  1372. * @private
  1373. * @param { String } cont 要存入的内容
  1374. */
  1375. _setDefaultContent: (function() {
  1376. function clear() {
  1377. var me = this;
  1378. if (me.document.getElementById("initContent")) {
  1379. me.body.innerHTML = "<p>" + (ie ? "" : "<br/>") + "</p>";
  1380. me.removeListener("firstBeforeExecCommand focus", clear);
  1381. setTimeout(function() {
  1382. me.focus();
  1383. me._selectionChange();
  1384. }, 0);
  1385. }
  1386. }
  1387. return function(cont) {
  1388. var me = this;
  1389. me.body.innerHTML = '<p id="initContent">' + cont + "</p>";
  1390. me.addListener("firstBeforeExecCommand focus", clear);
  1391. };
  1392. })(),
  1393. /**
  1394. * 显示编辑器
  1395. * @method setShow
  1396. * @example
  1397. * ```javascript
  1398. * editor.setShow()
  1399. * ```
  1400. */
  1401. setShow: function() {
  1402. var me = this,
  1403. range = me.selection.getRange();
  1404. if (me.container.style.display == "none") {
  1405. //有可能内容丢失了
  1406. try {
  1407. range.moveToBookmark(me.lastBk);
  1408. delete me.lastBk;
  1409. } catch (e) {
  1410. range.setStartAtFirst(me.body).collapse(true);
  1411. }
  1412. //ie下focus实效,所以做了个延迟
  1413. setTimeout(function() {
  1414. range.select(true);
  1415. }, 100);
  1416. me.container.style.display = "";
  1417. }
  1418. },
  1419. show: function() {
  1420. return this.setShow();
  1421. },
  1422. /**
  1423. * 隐藏编辑器
  1424. * @method setHide
  1425. * @example
  1426. * ```javascript
  1427. * editor.setHide()
  1428. * ```
  1429. */
  1430. setHide: function() {
  1431. var me = this;
  1432. if (!me.lastBk) {
  1433. me.lastBk = me.selection.getRange().createBookmark(true);
  1434. }
  1435. me.container.style.display = "none";
  1436. },
  1437. hide: function() {
  1438. return this.setHide();
  1439. },
  1440. /**
  1441. * 根据指定的路径,获取对应的语言资源
  1442. * @method getLang
  1443. * @param { String } path 路径根据的是lang目录下的语言文件的路径结构
  1444. * @return { Object | String } 根据路径返回语言资源的Json格式对象或者语言字符串
  1445. * @example
  1446. * ```javascript
  1447. * editor.getLang('contextMenu.delete'); //如果当前是中文,那返回是的是'删除'
  1448. * ```
  1449. */
  1450. getLang: function(path) {
  1451. var lang = UE.I18N[this.options.lang];
  1452. if (!lang) {
  1453. throw Error("not import language file");
  1454. }
  1455. path = (path || "").split(".");
  1456. for (var i = 0, ci; (ci = path[i++]); ) {
  1457. lang = lang[ci];
  1458. if (!lang) break;
  1459. }
  1460. return lang;
  1461. },
  1462. /**
  1463. * 计算编辑器html内容字符串的长度
  1464. * @method getContentLength
  1465. * @return { Number } 返回计算的长度
  1466. * @example
  1467. * ```javascript
  1468. * //编辑器html内容<p><strong>132</strong></p>
  1469. * editor.getContentLength() //返回27
  1470. * ```
  1471. */
  1472. /**
  1473. * 计算编辑器当前纯文本内容的长度
  1474. * @method getContentLength
  1475. * @param { Boolean } ingoneHtml 传入true时,只按照纯文本来计算
  1476. * @return { Number } 返回计算的长度,内容中有hr/img/iframe标签,长度加1
  1477. * @example
  1478. * ```javascript
  1479. * //编辑器html内容<p><strong>132</strong></p>
  1480. * editor.getContentLength() //返回3
  1481. * ```
  1482. */
  1483. getContentLength: function(ingoneHtml, tagNames) {
  1484. var count = this.getContent(false, false, true).length;
  1485. if (ingoneHtml) {
  1486. tagNames = (tagNames || []).concat(["hr", "img", "iframe"]);
  1487. count = this.getContentTxt().replace(/[\t\r\n]+/g, "").length;
  1488. for (var i = 0, ci; (ci = tagNames[i++]); ) {
  1489. count += this.document.getElementsByTagName(ci).length;
  1490. }
  1491. }
  1492. return count;
  1493. },
  1494. /**
  1495. * 注册输入过滤规则
  1496. * @method addInputRule
  1497. * @param { Function } rule 要添加的过滤规则
  1498. * @example
  1499. * ```javascript
  1500. * editor.addInputRule(function(root){
  1501. * $.each(root.getNodesByTagName('div'),function(i,node){
  1502. * node.tagName="p";
  1503. * });
  1504. * });
  1505. * ```
  1506. */
  1507. addInputRule: function(rule) {
  1508. this.inputRules.push(rule);
  1509. },
  1510. /**
  1511. * 执行注册的过滤规则
  1512. * @method filterInputRule
  1513. * @param { UE.uNode } root 要过滤的uNode节点
  1514. * @remind 执行editor.setContent方法和执行'inserthtml'命令后,会运行该过滤函数
  1515. * @example
  1516. * ```javascript
  1517. * editor.filterInputRule(editor.body);
  1518. * ```
  1519. * @see UE.Editor:addInputRule
  1520. */
  1521. filterInputRule: function(root) {
  1522. for (var i = 0, ci; (ci = this.inputRules[i++]); ) {
  1523. ci.call(this, root);
  1524. }
  1525. },
  1526. /**
  1527. * 注册输出过滤规则
  1528. * @method addOutputRule
  1529. * @param { Function } rule 要添加的过滤规则
  1530. * @example
  1531. * ```javascript
  1532. * editor.addOutputRule(function(root){
  1533. * $.each(root.getNodesByTagName('p'),function(i,node){
  1534. * node.tagName="div";
  1535. * });
  1536. * });
  1537. * ```
  1538. */
  1539. addOutputRule: function(rule) {
  1540. this.outputRules.push(rule);
  1541. },
  1542. /**
  1543. * 根据输出过滤规则,过滤编辑器内容
  1544. * @method filterOutputRule
  1545. * @remind 执行editor.getContent方法的时候,会先运行该过滤函数
  1546. * @param { UE.uNode } root 要过滤的uNode节点
  1547. * @example
  1548. * ```javascript
  1549. * editor.filterOutputRule(editor.body);
  1550. * ```
  1551. * @see UE.Editor:addOutputRule
  1552. */
  1553. filterOutputRule: function(root) {
  1554. for (var i = 0, ci; (ci = this.outputRules[i++]); ) {
  1555. ci.call(this, root);
  1556. }
  1557. },
  1558. /**
  1559. * 根据action名称获取请求的路径
  1560. * @method getActionUrl
  1561. * @remind 假如没有设置serverUrl,会根据imageUrl设置默认的controller路径
  1562. * @param { String } action action名称
  1563. * @example
  1564. * ```javascript
  1565. * editor.getActionUrl('config'); //返回 "/ueditor/php/controller.php?action=config"
  1566. * editor.getActionUrl('image'); //返回 "/ueditor/php/controller.php?action=uplaodimage"
  1567. * editor.getActionUrl('scrawl'); //返回 "/ueditor/php/controller.php?action=uplaodscrawl"
  1568. * editor.getActionUrl('imageManager'); //返回 "/ueditor/php/controller.php?action=listimage"
  1569. * ```
  1570. */
  1571. getActionUrl: function(action) {
  1572. var actionName = this.getOpt(action) || action,
  1573. imageUrl = this.getOpt("imageUrl"),
  1574. serverUrl = this.getOpt("serverUrl");
  1575. if (!serverUrl && imageUrl) {
  1576. serverUrl = imageUrl.replace(/^(.*[\/]).+([\.].+)$/, "$1controller$2");
  1577. }
  1578. if (serverUrl) {
  1579. serverUrl =
  1580. serverUrl +
  1581. (serverUrl.indexOf("?") == -1 ? "?" : "&") +
  1582. "action=" +
  1583. (actionName || "");
  1584. return utils.formatUrl(serverUrl);
  1585. } else {
  1586. return "";
  1587. }
  1588. }
  1589. };
  1590. utils.inherits(Editor, EventBase);
  1591. })();