PageRenderTime 51ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/addons/SixApartEditor.plugin/static/js/archetype_editor.js

http://github.com/openmelody/melody
JavaScript | 440 lines | 305 code | 115 blank | 20 comment | 56 complexity | bb52ede28dc8538c616dd5a21b7867ab MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.0, LGPL-2.1
  1. /*
  2. # Movable Type (r) Open Source (C) 2001-2010 Six Apart, Ltd.
  3. # This program is distributed under the terms of the
  4. # GNU General Public License, version 2.
  5. #
  6. # $Id$
  7. */
  8. App.singletonConstructor =
  9. MT.App = new Class( MT.App, {
  10. initEditor: function() {
  11. if ( this.constructor.Editor && DOM.getElement( "editor-content" ) ) {
  12. var mode = DOM.getElement( "convert_breaks" );
  13. DOM.addEventListener( mode, "change", this.getIndirectEventListener( "setTextareaMode" ) );
  14. /* special case */
  15. window.cur_text_format = mode.value;
  16. this.editorMode = ( mode.value == "richtext" ) ? "iframe" : "textarea";
  17. this.editor = this.addComponent( new MT.App.Editor( "editor-content", this.editorMode ) );
  18. this.editor.textarea.setTextMode( mode.value );
  19. this.editorInput = {
  20. content: DOM.getElement( "editor-input-content" ),
  21. extended: DOM.getElement( "editor-input-extended" )
  22. };
  23. if ( this.editorInput.content.value )
  24. this.editor.setHTML( this.editorInput.content.value );
  25. }
  26. }
  27. } );
  28. MT.App.Editor = new Class( Editor, {
  29. setChanged: function() {
  30. this.changed = true;
  31. log('changed');
  32. app.setDirty();
  33. }
  34. } );
  35. MT.App.Editor.Toolbar = new Class( Editor.Toolbar, {
  36. eventClick: function( event ) {
  37. var command = this.getMouseEventCommand( event );
  38. if ( !command )
  39. return event.stop();
  40. switch( command ) {
  41. case "insertEmail":
  42. var link = this.editor.getSelectedLink();
  43. if ( link )
  44. this.editEmail( link );
  45. else
  46. this.createEmailLink();
  47. break;
  48. case "openDialog":
  49. app.openDialog( event.commandElement.getAttribute( "mt:dialog-params" ) );
  50. break;
  51. case "openFlyout":
  52. var name = event.commandElement.getAttribute( "mt:flyout" );
  53. var el = DOM.getElement( name );
  54. if ( !defined( el ) )
  55. return;
  56. app.closeFlyouts( event.target );
  57. DOM.removeClassName( el, "hidden" );
  58. app.targetElement = event.target;
  59. app.applyAutolayouts( el );
  60. app.targetElement = null;
  61. app.openFlyouts.add( name );
  62. break;
  63. default:
  64. return arguments.callee.applySuper( this, arguments );
  65. }
  66. return event.stop();
  67. },
  68. editEmail: function( linkElement ) {
  69. this.createEmailLink( linkElement.href, true, linkElement );
  70. },
  71. mailtoRegexp: /^mailto:/i,
  72. createEmailLink: function( url, textSelected, anchor ) {
  73. var linkedText = "";
  74. if( !textSelected )
  75. textSelected = this.editor.isTextSelected();
  76. if( !url )
  77. url = "";
  78. url = url.replace( this.mailtoRegexp, "" );
  79. url = prompt( Editor.strings.enterEmailAddress, url );
  80. if( !url )
  81. return false;
  82. if( !textSelected )
  83. linkedText = prompt( Editor.strings.enterTextToLinkTo, "" );
  84. this.insertLink( { url: "mailto:" + url, linkedText: linkedText, anchor: anchor } );
  85. }
  86. } );
  87. MT.App.Editor.Textarea = new Class( Editor.Textarea, {
  88. currentTextMode: "_DEFAULT_",
  89. getHTML: function() {
  90. /* we can refocus the last selected element,
  91. * because the superClass getHTML will focus the editor (if IE) */
  92. var refocus;
  93. if ( document.activeElement )
  94. refocus = document.activeElement;
  95. var html = arguments.callee.applySuper( this, arguments );
  96. try { if ( refocus ) refocus.focus(); } catch(e) { };
  97. return html;
  98. },
  99. setTextMode: function( mode ) {
  100. var editorContent = DOM.getElement( "editor-content" );
  101. DOM.removeClassName( editorContent, /^editor-textmode-.*/ );
  102. if ( this[ mode + "Command" ] )
  103. DOM.addClassName( editorContent, "editor-textmode-" + mode.replace( /_/g, "-" ) );
  104. this.currentTextMode = mode;
  105. },
  106. execCommand: function( command, userInterface, argument ) {
  107. if ( this.currentTextMode && this[ this.currentTextMode + "Command" ] ) {
  108. log('executeing command: ' + command + ' in mode: '+this.currentTextMode );
  109. this[ this.currentTextMode + "Command" ].apply( this, arguments );
  110. return;
  111. }
  112. var text = this.getSelectedText();
  113. if ( !defined( text ) )
  114. text = '';
  115. switch ( command ) {
  116. case "fontSizeSmaller":
  117. this.setSelection( "<small>" + text + "</small>" );
  118. break;
  119. case "fontSizeLarger":
  120. this.setSelection( "<big>" + text + "</big>" );
  121. break;
  122. default:
  123. arguments.callee.applySuper( this, arguments );
  124. break;
  125. }
  126. },
  127. "markdown_with_smartypantsCommand": function() {
  128. this.markdownCommand.apply( this, arguments );
  129. },
  130. markdownCommand: function( command, userInterface, argument ) {
  131. var text = this.getSelectedText();
  132. if ( !defined( text ) )
  133. text = '';
  134. switch ( command ) {
  135. case "bold":
  136. this.setSelection( "**" + text + "**" );
  137. break;
  138. case "italic":
  139. this.setSelection( "*" + text + "*" );
  140. break;
  141. case "createLink":
  142. this.setSelection( "[" + text + "](" + argument + ")" );
  143. break;
  144. case "indent":
  145. var list = text.split( /\r?\n/ );
  146. for ( var i = 0; i < list.length; i++ )
  147. list[ i ] = "> " + list[ i ];
  148. this.setSelection( list.join( "\n" ) );
  149. break;
  150. case "insertUnorderedList":
  151. case "insertOrderedList":
  152. var list = text.split( /\r?\n/ );
  153. var ordered = ( command == "insertOrderedList" ) ? true : false;
  154. for ( var i = 0; i < list.length; i++ )
  155. list[ i ] = " " + ( ordered ? ( ( i + 1 ) + ". " ) : "-" ) + " " + list[ i ];
  156. this.setSelection( "\n" + list.join( "\n" ) + "\n" );
  157. break;
  158. }
  159. },
  160. "textile_2Command": function( command, userInterface, argument ) {
  161. var text = this.getSelectedText();
  162. if ( !defined( text ) )
  163. text = '';
  164. switch ( command ) {
  165. case "bold":
  166. this.setSelection( "**" + text + "**" );
  167. break;
  168. case "italic":
  169. this.setSelection( "_" + text + "_" );
  170. break;
  171. case "strikethrough":
  172. this.setSelection( "-" + text + "-" );
  173. break;
  174. case "createLink":
  175. this.setSelection( '"' + text + '":' + argument );
  176. break;
  177. case "indent":
  178. this.setSelection( "bq. " + text );
  179. break;
  180. case "underline":
  181. this.setSelection( "<u>" + text + "</u>" );
  182. break;
  183. case "insertUnorderedList":
  184. case "insertOrderedList":
  185. var list = text.split( /\r?\n/ );
  186. var ordered = ( command == "insertOrderedList" ) ? true : false;
  187. for ( var i = 0; i < list.length; i++ )
  188. list[ i ] = ( ordered ? "#" : "*" ) + " " + list[ i ];
  189. this.setSelection( "\n" + list.join( "\n" ) + "\n" );
  190. break;
  191. case "justifyLeft":
  192. this.setSelection( "p< " + text );
  193. break;
  194. case "justifyCenter":
  195. this.setSelection( "p= " + text );
  196. break;
  197. case "justifyRight":
  198. this.setSelection( "p> " + text );
  199. break;
  200. case "fontSizeSmaller":
  201. this.setSelection( "<small>" + text + "</small>" );
  202. break;
  203. case "fontSizeLarger":
  204. this.setSelection( "<big>" + text + "</big>" );
  205. break;
  206. }
  207. }
  208. } );
  209. MT.App.Editor.Iframe = new Class( Editor.Iframe, {
  210. initObject: function() {
  211. arguments.callee.applySuper( this, arguments );
  212. this.isWebKit = navigator.userAgent.toLowerCase().match(/webkit/);
  213. },
  214. eventFocusIn: function( event ) {
  215. this.eventFocus( event );
  216. },
  217. eventFocus: function( event ) {
  218. if ( this.editor.mode == "textarea" )
  219. this.editor.focus();
  220. },
  221. eventClick: function( event ) {
  222. /* for safari */
  223. if ( this.isWebKit && event.target.nodeName == "A" )
  224. return event.stop();
  225. return arguments.callee.applySuper( this, arguments );
  226. },
  227. eventKeyPress: function( event ) {
  228. /* safari forward delete */
  229. if ( this.isWebKit && event.keyCode == 63272 )
  230. return event.stop();
  231. },
  232. eventKeyDown: function( event ) {
  233. /* safari forward delete */
  234. if ( this.isWebKit && event.keyCode == 46 ) {
  235. this.document.execCommand( "forwardDelete", false, true );
  236. return false;
  237. }
  238. },
  239. eventKeyUp: function( event ) {
  240. /* safari always makes this event. ignore for language input method */
  241. if ( this.isWebKit ) {
  242. return false;
  243. }
  244. },
  245. extendedExecCommand: function( command, userInterface, argument ) {
  246. switch( command ) {
  247. case "fontSizeSmaller":
  248. this.changeFontSizeOfSelection( false );
  249. break;
  250. case "fontSizeLarger":
  251. this.changeFontSizeOfSelection( true );
  252. break;
  253. default:
  254. return arguments.callee.applySuper( this, arguments );
  255. }
  256. },
  257. mutateFontSize: function( element, bigger ) {
  258. // Basic settings:
  259. var goSmaller = 0.8;
  260. var goBigger = 1.25;
  261. var biggest = Math.pow( goBigger, 3 );
  262. var smallest = Math.pow( goSmaller, 3);
  263. var defaultSize = bigger ? goBigger + "em" : goSmaller + "em";
  264. // Initial detection, rejection, adjusting:
  265. var fontSize = element.style.fontSize.match( /([\d\.]+)(%|em|$)/ );
  266. if( fontSize == null || isNaN( fontSize[ 1 ] ) ) // "px" sizes are rejected.
  267. return defaultSize; // A browser problem or bad user data would lead to "NaN" fontSize.
  268. var size;
  269. if( fontSize[ 2 ] == "%" )
  270. size = fontSize[ 1 ] / 100; // Convert to "em" units.
  271. else if( fontSize[ 2 ] == "em" || fontSize[ 2 ] == "" )
  272. size = fontSize[ 1 ];
  273. // Mutation:
  274. var factor = bigger ? goBigger : goSmaller;
  275. size = size * factor;
  276. if( size > biggest )
  277. size = biggest;
  278. else if( size < smallest )
  279. size = smallest;
  280. return size + "em";
  281. },
  282. changeFontSizeOfSelection: function( bigger ) {
  283. var bogus = "-editor-proxy";
  284. this.document.execCommand( "fontName", false, bogus );
  285. var elements = this.document.getElementsByTagName( "font" );
  286. for( var i = 0; i < elements.length; i++ ) {
  287. var element = elements[ i ];
  288. if( element.face == bogus ) {
  289. element.removeAttribute( "face" );
  290. element.style.fontSize = this.mutateFontSize( element, bigger );
  291. }
  292. }
  293. },
  294. getHTML: function() {
  295. var html = this.document.body.innerHTML;
  296. // cleanup innerHTML garbage browser regurgitates
  297. // #1 - lowercase tag names (open and closing tags)
  298. html = html.replace(/<\/?[A-Z0-9]+[\s>]/g, function (m) {
  299. return m.toLowerCase();
  300. });
  301. // #2 - lowercase attribute names
  302. html = html.replace(/(<[\w\d]+\s+)([^>]+)>/g, function (x, m1, m2) {
  303. return m1 + m2.replace(/\b([\w\d:-]+)\s*=\s*(?:'([^']*?)'|"([^"]*?)"|(\S+))/g, function (x, m1, m2, m3, m4) {
  304. if ( !m2 ) m2 = ''; // for ie
  305. if ( !m3 ) m3 = ''; // for ie
  306. if ( !m4 ) m4 = ''; // for ie
  307. return m1.toLowerCase() + '="' + m2 + m3 + m4 + '"';
  308. }) + ">";
  309. });
  310. // #3 - close singlet tags for img, br, input, param, hr
  311. html = html.replace(/<(br|img|input|param)([^>]+)?([^\/])?>/g, "<$1$2$3 />");
  312. // #4 - get absolute path and delete from converted URL
  313. var path = this.document.URL;
  314. path = path.replace(/(.*)editor-content.html.*/, "$1");
  315. var regex = new RegExp(path, "g");
  316. html = html.replace(regex, "");
  317. /* XXX for save on ff */
  318. regex = new RegExp(path.replace(/~/, "%7E"), "g");
  319. html = html.replace(regex, "");
  320. return html;
  321. }
  322. } );