source.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. ///import core
  2. ///import plugins/serialize.js
  3. ///import plugins/undo.js
  4. ///commands 查看源码
  5. ///commandsName Source
  6. ///commandsTitle 查看源码
  7. (function (){
  8. function SourceFormater(config){
  9. config = config || {};
  10. this.indentChar = config.indentChar || ' ';
  11. this.breakChar = config.breakChar || '\n';
  12. this.selfClosingEnd = config.selfClosingEnd || ' />';
  13. }
  14. var unhtml1 = function (){
  15. var map = { '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' };
  16. function rep( m ){ return map[m]; }
  17. return function ( str ) {
  18. str = str + '';
  19. return str ? str.replace( /[<>"']/g, rep ) : '';
  20. };
  21. }();
  22. var inline = utils.extend({a:1,A:1},dtd.$inline,true);
  23. function printAttrs(attrs){
  24. var buff = [];
  25. for (var k in attrs) {
  26. buff.push(k + '="' + unhtml1(attrs[k]) + '"');
  27. }
  28. return buff.join(' ');
  29. }
  30. SourceFormater.prototype = {
  31. format: function (html){
  32. var node = UE.serialize.parseHTML(html);
  33. this.buff = [];
  34. this.indents = '';
  35. this.indenting = 1;
  36. this.visitNode(node);
  37. return this.buff.join('');
  38. },
  39. visitNode: function (node){
  40. if (node.type == 'fragment') {
  41. this.visitChildren(node.children);
  42. } else if (node.type == 'element') {
  43. var selfClosing = dtd.$empty[node.tag];
  44. this.visitTag(node.tag, node.attributes, selfClosing);
  45. this.visitChildren(node.children);
  46. if (!selfClosing) {
  47. this.visitEndTag(node.tag);
  48. }
  49. } else if (node.type == 'comment') {
  50. this.visitComment(node.data);
  51. } else {
  52. this.visitText(node.data,dtd.$notTransContent[node.parent.tag]);
  53. }
  54. },
  55. visitChildren: function (children){
  56. for (var i=0; i<children.length; i++) {
  57. this.visitNode(children[i]);
  58. }
  59. },
  60. visitTag: function (tag, attrs, selfClosing){
  61. if (this.indenting) {
  62. this.indent();
  63. } else if (!inline[tag]) { // todo: 去掉a, 因为dtd的inline里面没有a
  64. this.newline();
  65. this.indent();
  66. }
  67. this.buff.push('<', tag);
  68. var attrPart = printAttrs(attrs);
  69. if (attrPart) {
  70. this.buff.push(' ', attrPart);
  71. }
  72. if (selfClosing) {
  73. this.buff.push(this.selfClosingEnd);
  74. if (tag == 'br') {
  75. this.newline();
  76. }
  77. } else {
  78. this.buff.push('>');
  79. this.indents += this.indentChar;
  80. }
  81. if (!inline[tag]) {
  82. this.newline();
  83. }
  84. },
  85. indent: function (){
  86. this.buff.push(this.indents);
  87. this.indenting = 0;
  88. },
  89. newline: function (){
  90. this.buff.push(this.breakChar);
  91. this.indenting = 1;
  92. },
  93. visitEndTag: function (tag){
  94. this.indents = this.indents.slice(0, -this.indentChar.length);
  95. if (this.indenting) {
  96. this.indent();
  97. } else if (!inline[tag]) {
  98. this.newline();
  99. this.indent();
  100. }
  101. this.buff.push('</', tag, '>');
  102. },
  103. visitText: function (text,notTrans){
  104. if (this.indenting) {
  105. this.indent();
  106. }
  107. // if(!notTrans){
  108. // text = text.replace(/&nbsp;/g, ' ').replace(/[ ][ ]+/g, function (m){
  109. // return new Array(m.length + 1).join('&nbsp;');
  110. // }).replace(/(?:^ )|(?: $)/g, '&nbsp;');
  111. // }
  112. text = text.replace(/&nbsp;/g, ' ');
  113. this.buff.push(text);
  114. },
  115. visitComment: function (text){
  116. if (this.indenting) {
  117. this.indent();
  118. }
  119. this.buff.push('<!--', text, '-->');
  120. }
  121. };
  122. var sourceEditors = {
  123. textarea: function (editor, holder){
  124. var textarea = holder.ownerDocument.createElement('textarea');
  125. textarea.style.cssText = 'position:absolute;resize:none;width:100%;height:100%;border:0;padding:0;margin:0;overflow-y:auto;';
  126. // todo: IE下只有onresize属性可用... 很纠结
  127. if (browser.ie && browser.version < 8) {
  128. textarea.style.width = holder.offsetWidth + 'px';
  129. textarea.style.height = holder.offsetHeight + 'px';
  130. holder.onresize = function (){
  131. textarea.style.width = holder.offsetWidth + 'px';
  132. textarea.style.height = holder.offsetHeight + 'px';
  133. };
  134. }
  135. holder.appendChild(textarea);
  136. return {
  137. setContent: function (content){
  138. textarea.value = content;
  139. },
  140. getContent: function (){
  141. return textarea.value;
  142. },
  143. select: function (){
  144. var range;
  145. if (browser.ie) {
  146. range = textarea.createTextRange();
  147. range.collapse(true);
  148. range.select();
  149. } else {
  150. //todo: chrome下无法设置焦点
  151. textarea.setSelectionRange(0, 0);
  152. textarea.focus();
  153. }
  154. },
  155. dispose: function (){
  156. holder.removeChild(textarea);
  157. // todo
  158. holder.onresize = null;
  159. textarea = null;
  160. holder = null;
  161. }
  162. };
  163. },
  164. codemirror: function (editor, holder){
  165. var codeEditor = window.CodeMirror(holder, {
  166. mode: "text/html",
  167. tabMode: "indent",
  168. lineNumbers: true,
  169. lineWrapping:true
  170. });
  171. var dom = codeEditor.getWrapperElement();
  172. dom.style.cssText = 'position:absolute;left:0;top:0;width:100%;height:100%;font-family:consolas,"Courier new",monospace;font-size:13px;';
  173. codeEditor.getScrollerElement().style.cssText = 'position:absolute;left:0;top:0;width:100%;height:100%;';
  174. codeEditor.refresh();
  175. return {
  176. getCodeMirror:function(){
  177. return codeEditor;
  178. },
  179. setContent: function (content){
  180. codeEditor.setValue(content);
  181. },
  182. getContent: function (){
  183. return codeEditor.getValue();
  184. },
  185. select: function (){
  186. codeEditor.focus();
  187. },
  188. dispose: function (){
  189. holder.removeChild(dom);
  190. dom = null;
  191. codeEditor = null;
  192. }
  193. };
  194. }
  195. };
  196. UE.plugins['source'] = function (){
  197. var me = this;
  198. var opt = this.options;
  199. var formatter = new SourceFormater(opt.source);
  200. var sourceMode = false;
  201. var sourceEditor;
  202. opt.sourceEditor = browser.ie ? 'textarea' : (opt.sourceEditor || 'codemirror');
  203. me.setOpt({
  204. sourceEditorFirst:false
  205. });
  206. function createSourceEditor(holder){
  207. return sourceEditors[opt.sourceEditor == 'codemirror' && window.CodeMirror ? 'codemirror' : 'textarea'](me, holder);
  208. }
  209. var bakCssText;
  210. //解决在源码模式下getContent不能得到最新的内容问题
  211. var oldGetContent = me.getContent,
  212. bakAddress;
  213. me.commands['source'] = {
  214. execCommand: function (){
  215. sourceMode = !sourceMode;
  216. if (sourceMode) {
  217. bakAddress = me.selection.getRange().createAddress(false,true);
  218. me.undoManger && me.undoManger.save(true);
  219. if(browser.gecko){
  220. me.body.contentEditable = false;
  221. }
  222. bakCssText = me.iframe.style.cssText;
  223. me.iframe.style.cssText += 'position:absolute;left:-32768px;top:-32768px;';
  224. var content = formatter.format(me.hasContents() ? me.getContent() : '');
  225. sourceEditor = createSourceEditor(me.iframe.parentNode);
  226. sourceEditor.setContent(content);
  227. setTimeout(function (){
  228. sourceEditor.select();
  229. me.addListener('fullscreenchanged', function(){
  230. try{
  231. sourceEditor.getCodeMirror().refresh()
  232. }catch(e){}
  233. });
  234. });
  235. //重置getContent,源码模式下取值也能是最新的数据
  236. me.getContent = function (){
  237. var cont = sourceEditor.getContent() || '<p>' + (browser.ie ? '' : '<br/>')+'</p>';
  238. return cont.replace(/>[\n\r\t]+([ ]{4})+/g,'>').replace(/[\n\r\t]+([ ]{4})+</g,'<').replace(/>[\n\r\t]+</g,'><');
  239. };
  240. } else {
  241. me.iframe.style.cssText = bakCssText;
  242. var cont = sourceEditor.getContent() || '<p>' + (browser.ie ? '' : '<br/>')+'</p>';
  243. cont = cont.replace(/>[\n\r\t]+([ ]{4})+/g,'>').replace(/[\n\r\t]+([ ]{4})+</g,'<').replace(/>[\n\r\t]+</g,'><');
  244. me.setContent(cont);
  245. sourceEditor.dispose();
  246. sourceEditor = null;
  247. //还原getContent方法
  248. me.getContent = oldGetContent;
  249. var first = me.body.firstChild;
  250. //trace:1106 都删除空了,下边会报错,所以补充一个p占位
  251. if(!first){
  252. me.body.innerHTML = '<p>'+(browser.ie?'':'<br/>')+'</p>';
  253. first = me.body.firstChild;
  254. }
  255. //要在ifm为显示时ff才能取到selection,否则报错
  256. //这里不能比较位置了
  257. me.undoManger && me.undoManger.save(true);
  258. if(browser.gecko){
  259. var input = document.createElement('input');
  260. input.style.cssText = 'position:absolute;left:0;top:-32768px';
  261. document.body.appendChild(input);
  262. me.body.contentEditable = false;
  263. setTimeout(function(){
  264. domUtils.setViewportOffset(input, { left: -32768, top: 0 });
  265. input.focus();
  266. setTimeout(function(){
  267. me.body.contentEditable = true;
  268. me.selection.getRange().moveToAddress(bakAddress).select();
  269. domUtils.remove(input);
  270. });
  271. });
  272. }else{
  273. //ie下有可能报错,比如在代码顶头的情况
  274. try{
  275. me.selection.getRange().moveToAddress(bakAddress).select();
  276. }catch(e){}
  277. }
  278. }
  279. this.fireEvent('sourcemodechanged', sourceMode);
  280. },
  281. queryCommandState: function (){
  282. return sourceMode|0;
  283. },
  284. notNeedUndo : 1
  285. };
  286. var oldQueryCommandState = me.queryCommandState;
  287. me.queryCommandState = function (cmdName){
  288. cmdName = cmdName.toLowerCase();
  289. if (sourceMode) {
  290. //源码模式下可以开启的命令
  291. return cmdName in {
  292. 'source' : 1,
  293. 'fullscreen' : 1
  294. } ? 1 : -1
  295. }
  296. return oldQueryCommandState.apply(this, arguments);
  297. };
  298. if(opt.sourceEditor == "codemirror"){
  299. me.addListener("ready",function(){
  300. utils.loadFile(document,{
  301. src : opt.codeMirrorJsUrl || opt.UEDITOR_HOME_URL + "third-party/codemirror/codemirror.js",
  302. tag : "script",
  303. type : "text/javascript",
  304. defer : "defer"
  305. },function(){
  306. if(opt.sourceEditorFirst){
  307. setTimeout(function(){
  308. me.execCommand("source");
  309. },0);
  310. }
  311. });
  312. utils.loadFile(document,{
  313. tag : "link",
  314. rel : "stylesheet",
  315. type : "text/css",
  316. href : opt.codeMirrorCssUrl || opt.UEDITOR_HOME_URL + "third-party/codemirror/codemirror.css"
  317. });
  318. });
  319. }
  320. };
  321. })();