source.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. /**
  2. * 源码编辑插件
  3. * @file
  4. * @since 1.2.6.1
  5. */
  6. (function (){
  7. var sourceEditors = {
  8. textarea: function (editor, holder){
  9. var textarea = holder.ownerDocument.createElement('textarea');
  10. textarea.style.cssText = 'position:absolute;resize:none;width:100%;height:100%;border:0;padding:0;margin:0;overflow-y:auto;';
  11. // todo: IE下只有onresize属性可用... 很纠结
  12. if (browser.ie && browser.version < 8) {
  13. textarea.style.width = holder.offsetWidth + 'px';
  14. textarea.style.height = holder.offsetHeight + 'px';
  15. holder.onresize = function (){
  16. textarea.style.width = holder.offsetWidth + 'px';
  17. textarea.style.height = holder.offsetHeight + 'px';
  18. };
  19. }
  20. holder.appendChild(textarea);
  21. return {
  22. setContent: function (content){
  23. textarea.value = content;
  24. },
  25. getContent: function (){
  26. return textarea.value;
  27. },
  28. select: function (){
  29. var range;
  30. if (browser.ie) {
  31. range = textarea.createTextRange();
  32. range.collapse(true);
  33. range.select();
  34. } else {
  35. //todo: chrome下无法设置焦点
  36. textarea.setSelectionRange(0, 0);
  37. textarea.focus();
  38. }
  39. },
  40. dispose: function (){
  41. holder.removeChild(textarea);
  42. // todo
  43. holder.onresize = null;
  44. textarea = null;
  45. holder = null;
  46. }
  47. };
  48. },
  49. codemirror: function (editor, holder){
  50. var codeEditor = window.CodeMirror(holder, {
  51. mode: "text/html",
  52. tabMode: "indent",
  53. lineNumbers: true,
  54. lineWrapping:true
  55. });
  56. var dom = codeEditor.getWrapperElement();
  57. dom.style.cssText = 'position:absolute;left:0;top:0;width:100%;height:100%;font-family:consolas,"Courier new",monospace;font-size:13px;';
  58. codeEditor.getScrollerElement().style.cssText = 'position:absolute;left:0;top:0;width:100%;height:100%;';
  59. codeEditor.refresh();
  60. return {
  61. getCodeMirror:function(){
  62. return codeEditor;
  63. },
  64. setContent: function (content){
  65. codeEditor.setValue(content);
  66. },
  67. getContent: function (){
  68. return codeEditor.getValue();
  69. },
  70. select: function (){
  71. codeEditor.focus();
  72. },
  73. dispose: function (){
  74. holder.removeChild(dom);
  75. dom = null;
  76. codeEditor = null;
  77. }
  78. };
  79. }
  80. };
  81. UE.plugins['source'] = function (){
  82. var me = this;
  83. var opt = this.options;
  84. var sourceMode = false;
  85. var sourceEditor;
  86. var orgSetContent;
  87. opt.sourceEditor = browser.ie ? 'textarea' : (opt.sourceEditor || 'codemirror');
  88. me.setOpt({
  89. sourceEditorFirst:false
  90. });
  91. function createSourceEditor(holder){
  92. return sourceEditors[opt.sourceEditor == 'codemirror' && window.CodeMirror ? 'codemirror' : 'textarea'](me, holder);
  93. }
  94. var bakCssText;
  95. //解决在源码模式下getContent不能得到最新的内容问题
  96. var oldGetContent,
  97. bakAddress;
  98. /**
  99. * 切换源码模式和编辑模式
  100. * @command source
  101. * @method execCommand
  102. * @param { String } cmd 命令字符串
  103. * @example
  104. * ```javascript
  105. * editor.execCommand( 'source');
  106. * ```
  107. */
  108. /**
  109. * 查询当前编辑区域的状态是源码模式还是可视化模式
  110. * @command source
  111. * @method queryCommandState
  112. * @param { String } cmd 命令字符串
  113. * @return { int } 如果当前是源码编辑模式,返回1,否则返回0
  114. * @example
  115. * ```javascript
  116. * editor.queryCommandState( 'source' );
  117. * ```
  118. */
  119. me.commands['source'] = {
  120. execCommand: function (){
  121. sourceMode = !sourceMode;
  122. if (sourceMode) {
  123. bakAddress = me.selection.getRange().createAddress(false,true);
  124. me.undoManger && me.undoManger.save(true);
  125. if(browser.gecko){
  126. me.body.contentEditable = false;
  127. }
  128. bakCssText = me.iframe.style.cssText;
  129. me.iframe.style.cssText += 'position:absolute;left:-32768px;top:-32768px;';
  130. me.fireEvent('beforegetcontent');
  131. var root = UE.htmlparser(me.body.innerHTML);
  132. me.filterOutputRule(root);
  133. root.traversal(function (node) {
  134. if (node.type == 'element') {
  135. switch (node.tagName) {
  136. case 'td':
  137. case 'th':
  138. case 'caption':
  139. if(node.children && node.children.length == 1){
  140. if(node.firstChild().tagName == 'br' ){
  141. node.removeChild(node.firstChild())
  142. }
  143. };
  144. break;
  145. case 'pre':
  146. node.innerText(node.innerText().replace(/&nbsp;/g,' '))
  147. }
  148. }
  149. });
  150. me.fireEvent('aftergetcontent');
  151. var content = root.toHtml(true);
  152. sourceEditor = createSourceEditor(me.iframe.parentNode);
  153. sourceEditor.setContent(content);
  154. orgSetContent = me.setContent;
  155. me.setContent = function(html){
  156. //这里暂时不触发事件,防止报错
  157. var root = UE.htmlparser(html);
  158. me.filterInputRule(root);
  159. html = root.toHtml();
  160. sourceEditor.setContent(html);
  161. };
  162. setTimeout(function (){
  163. sourceEditor.select();
  164. me.addListener('fullscreenchanged', function(){
  165. try{
  166. sourceEditor.getCodeMirror().refresh()
  167. }catch(e){}
  168. });
  169. });
  170. //重置getContent,源码模式下取值也能是最新的数据
  171. oldGetContent = me.getContent;
  172. me.getContent = function (){
  173. return sourceEditor.getContent() || '<p>' + (browser.ie ? '' : '<br/>')+'</p>';
  174. };
  175. } else {
  176. me.iframe.style.cssText = bakCssText;
  177. var cont = sourceEditor.getContent() || '<p>' + (browser.ie ? '' : '<br/>')+'</p>';
  178. //处理掉block节点前后的空格,有可能会误命中,暂时不考虑
  179. cont = cont.replace(new RegExp('[\\r\\t\\n ]*<\/?(\\w+)\\s*(?:[^>]*)>','g'), function(a,b){
  180. if(b && !dtd.$inlineWithA[b.toLowerCase()]){
  181. return a.replace(/(^[\n\r\t ]*)|([\n\r\t ]*$)/g,'');
  182. }
  183. return a.replace(/(^[\n\r\t]*)|([\n\r\t]*$)/g,'')
  184. });
  185. me.setContent = orgSetContent;
  186. me.setContent(cont);
  187. sourceEditor.dispose();
  188. sourceEditor = null;
  189. //还原getContent方法
  190. me.getContent = oldGetContent;
  191. var first = me.body.firstChild;
  192. //trace:1106 都删除空了,下边会报错,所以补充一个p占位
  193. if(!first){
  194. me.body.innerHTML = '<p>'+(browser.ie?'':'<br/>')+'</p>';
  195. first = me.body.firstChild;
  196. }
  197. //要在ifm为显示时ff才能取到selection,否则报错
  198. //这里不能比较位置了
  199. me.undoManger && me.undoManger.save(true);
  200. if(browser.gecko){
  201. var input = document.createElement('input');
  202. input.style.cssText = 'position:absolute;left:0;top:-32768px';
  203. document.body.appendChild(input);
  204. me.body.contentEditable = false;
  205. setTimeout(function(){
  206. domUtils.setViewportOffset(input, { left: -32768, top: 0 });
  207. input.focus();
  208. setTimeout(function(){
  209. me.body.contentEditable = true;
  210. me.selection.getRange().moveToAddress(bakAddress).select(true);
  211. domUtils.remove(input);
  212. });
  213. });
  214. }else{
  215. //ie下有可能报错,比如在代码顶头的情况
  216. try{
  217. me.selection.getRange().moveToAddress(bakAddress).select(true);
  218. }catch(e){}
  219. }
  220. }
  221. this.fireEvent('sourcemodechanged', sourceMode);
  222. },
  223. queryCommandState: function (){
  224. return sourceMode|0;
  225. },
  226. notNeedUndo : 1
  227. };
  228. var oldQueryCommandState = me.queryCommandState;
  229. me.queryCommandState = function (cmdName){
  230. cmdName = cmdName.toLowerCase();
  231. if (sourceMode) {
  232. //源码模式下可以开启的命令
  233. return cmdName in {
  234. 'source' : 1,
  235. 'fullscreen' : 1
  236. } ? 1 : -1
  237. }
  238. return oldQueryCommandState.apply(this, arguments);
  239. };
  240. if(opt.sourceEditor == "codemirror"){
  241. me.addListener("ready",function(){
  242. utils.loadFile(document,{
  243. src : opt.codeMirrorJsUrl || opt.UEDITOR_HOME_URL + "third-party/codemirror/codemirror.js",
  244. tag : "script",
  245. type : "text/javascript",
  246. defer : "defer"
  247. },function(){
  248. if(opt.sourceEditorFirst){
  249. setTimeout(function(){
  250. me.execCommand("source");
  251. },0);
  252. }
  253. });
  254. utils.loadFile(document,{
  255. tag : "link",
  256. rel : "stylesheet",
  257. type : "text/css",
  258. href : opt.codeMirrorCssUrl || opt.UEDITOR_HOME_URL + "third-party/codemirror/codemirror.css"
  259. });
  260. });
  261. }
  262. };
  263. })();