/source/Plug-in/fck/editor/_source/internals/fck_gecko.js

http://prosporous.googlecode.com/ · JavaScript · 465 lines · 307 code · 64 blank · 94 comment · 83 complexity · bf44d6ba84cd215112731386c9fd5359 MD5 · raw file

  1. /*
  2. * FCKeditor - The text editor for Internet - http://www.fckeditor.net
  3. * Copyright (C) 2003-2007 Frederico Caldeira Knabben
  4. *
  5. * == BEGIN LICENSE ==
  6. *
  7. * Licensed under the terms of any of the following licenses at your
  8. * choice:
  9. *
  10. * - GNU General Public License Version 2 or later (the "GPL")
  11. * http://www.gnu.org/licenses/gpl.html
  12. *
  13. * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
  14. * http://www.gnu.org/licenses/lgpl.html
  15. *
  16. * - Mozilla Public License Version 1.1 or later (the "MPL")
  17. * http://www.mozilla.org/MPL/MPL-1.1.html
  18. *
  19. * == END LICENSE ==
  20. *
  21. * Creation and initialization of the "FCK" object. This is the main
  22. * object that represents an editor instance.
  23. * (Gecko specific implementations)
  24. */
  25. FCK.Description = "FCKeditor for Gecko Browsers" ;
  26. FCK.InitializeBehaviors = function()
  27. {
  28. // When calling "SetData", the editing area IFRAME gets a fixed height. So we must recalculate it.
  29. if ( FCKBrowserInfo.IsGecko ) // Not for Safari/Opera.
  30. Window_OnResize() ;
  31. FCKFocusManager.AddWindow( this.EditorWindow ) ;
  32. this.ExecOnSelectionChange = function()
  33. {
  34. FCK.Events.FireEvent( "OnSelectionChange" ) ;
  35. }
  36. this._ExecDrop = function( evt )
  37. {
  38. if ( FCK.MouseDownFlag )
  39. {
  40. FCK.MouseDownFlag = false ;
  41. return ;
  42. }
  43. if ( FCKConfig.ForcePasteAsPlainText )
  44. {
  45. if ( evt.dataTransfer )
  46. {
  47. var text = evt.dataTransfer.getData( 'Text' ) ;
  48. text = FCKTools.HTMLEncode( text ) ;
  49. text = FCKTools.ProcessLineBreaks( window, FCKConfig, text ) ;
  50. FCK.InsertHtml( text ) ;
  51. }
  52. else if ( FCKConfig.ShowDropDialog )
  53. FCK.PasteAsPlainText() ;
  54. }
  55. else if ( FCKConfig.ShowDropDialog )
  56. FCKDialog.OpenDialog( 'FCKDialog_Paste', FCKLang.Paste, 'dialog/fck_paste.html', 400, 330, 'Security' ) ;
  57. evt.preventDefault() ;
  58. evt.stopPropagation() ;
  59. }
  60. this._ExecCheckCaret = function( evt )
  61. {
  62. if ( FCK.EditMode != FCK_EDITMODE_WYSIWYG )
  63. return ;
  64. if ( evt.type == 'keypress' )
  65. {
  66. var keyCode = evt.keyCode ;
  67. // ignore if positioning key is not pressed.
  68. // left or up arrow keys need to be processed as well, since <a> links can be expanded in Gecko's editor
  69. // when the caret moved left or up from another block element below.
  70. if ( keyCode < 33 || keyCode > 40 )
  71. return ;
  72. }
  73. var blockEmptyStop = function( node )
  74. {
  75. if ( node.nodeType != 1 )
  76. return false ;
  77. var tag = node.tagName.toLowerCase() ;
  78. return ( FCKListsLib.BlockElements[tag] || FCKListsLib.EmptyElements[tag] ) ;
  79. }
  80. var moveCursor = function()
  81. {
  82. var selection = FCK.EditorWindow.getSelection() ;
  83. var range = selection.getRangeAt(0) ;
  84. if ( ! range || ! range.collapsed )
  85. return ;
  86. var node = range.endContainer ;
  87. // only perform the patched behavior if we're at the end of a text node.
  88. if ( node.nodeType != 3 )
  89. return ;
  90. if ( node.nodeValue.length != range.endOffset )
  91. return ;
  92. // only perform the patched behavior if we're in an <a> tag, or the End key is pressed.
  93. var parentTag = node.parentNode.tagName.toLowerCase() ;
  94. if ( ! ( parentTag == 'a' ||
  95. ( ! ( FCKListsLib.BlockElements[parentTag] || FCKListsLib.NonEmptyBlockElements[parentTag] )
  96. && keyCode == 35 ) ) )
  97. return ;
  98. // our caret has moved to just after the last character of a text node under an unknown tag, how to proceed?
  99. // first, see if there are other text nodes by DFS walking from this text node.
  100. // - if the DFS has scanned all nodes under my parent, then go the next step.
  101. // - if there is a text node after me but still under my parent, then do nothing and return.
  102. var nextTextNode = FCKTools.GetNextTextNode( node, node.parentNode, blockEmptyStop ) ;
  103. if ( nextTextNode )
  104. return ;
  105. // we're pretty sure we need to move the caret forcefully from here.
  106. range = FCK.EditorDocument.createRange() ;
  107. nextTextNode = FCKTools.GetNextTextNode( node, node.parentNode.parentNode, blockEmptyStop ) ;
  108. if ( nextTextNode )
  109. {
  110. // Opera thinks the dummy empty text node we append beyond the end of <a> nodes occupies a caret
  111. // position. So if the user presses the left key and we reset the caret position here, the user
  112. // wouldn't be able to go back.
  113. if ( FCKBrowserInfo.IsOpera && keyCode == 37 )
  114. return ;
  115. // now we want to get out of our current parent node, adopt the next parent, and move the caret to
  116. // the appropriate text node under our new parent.
  117. // our new parent might be our current parent's siblings if we are lucky.
  118. range.setStart( nextTextNode, 0 ) ;
  119. range.setEnd( nextTextNode, 0 ) ;
  120. }
  121. else
  122. {
  123. // no suitable next siblings under our grandparent! what to do next?
  124. while ( node.parentNode
  125. && node.parentNode != FCK.EditorDocument.body
  126. && node.parentNode != FCK.EditorDocument.documentElement
  127. && node == node.parentNode.lastChild
  128. && ( ! FCKListsLib.BlockElements[node.parentNode.tagName.toLowerCase()]
  129. && ! FCKListsLib.NonEmptyBlockElements[node.parentNode.tagName.toLowerCase()] ) )
  130. node = node.parentNode ;
  131. if ( FCKListsLib.BlockElements[ parentTag ]
  132. || FCKListsLib.EmptyElements[ parentTag ]
  133. || node == FCK.EditorDocument.body )
  134. {
  135. // if our parent is a block node, move to the end of our parent.
  136. range.setStart( node, node.childNodes.length ) ;
  137. range.setEnd( node, node.childNodes.length ) ;
  138. }
  139. else
  140. {
  141. // things are a little bit more interesting if our parent is not a block node
  142. // due to the weired ways how Gecko's caret acts...
  143. var stopNode = node.nextSibling ;
  144. // find out the next block/empty element at our grandparent, we'll
  145. // move the caret just before it.
  146. while ( stopNode )
  147. {
  148. if ( stopNode.nodeType != 1 )
  149. {
  150. stopNode = stopNode.nextSibling ;
  151. continue ;
  152. }
  153. var stopTag = stopNode.tagName.toLowerCase() ;
  154. if ( FCKListsLib.BlockElements[stopTag] || FCKListsLib.EmptyElements[stopTag]
  155. || FCKListsLib.NonEmptyBlockElements[stopTag] )
  156. break ;
  157. stopNode = stopNode.nextSibling ;
  158. }
  159. // note that the dummy marker below is NEEDED, otherwise the caret's behavior will
  160. // be broken in Gecko.
  161. var marker = FCK.EditorDocument.createTextNode( '' ) ;
  162. if ( stopNode )
  163. node.parentNode.insertBefore( marker, stopNode ) ;
  164. else
  165. node.parentNode.appendChild( marker ) ;
  166. range.setStart( marker, 0 ) ;
  167. range.setEnd( marker, 0 ) ;
  168. }
  169. }
  170. selection.removeAllRanges() ;
  171. selection.addRange( range ) ;
  172. FCK.Events.FireEvent( "OnSelectionChange" ) ;
  173. }
  174. setTimeout( moveCursor, 1 ) ;
  175. }
  176. this.ExecOnSelectionChangeTimer = function()
  177. {
  178. if ( FCK.LastOnChangeTimer )
  179. window.clearTimeout( FCK.LastOnChangeTimer ) ;
  180. FCK.LastOnChangeTimer = window.setTimeout( FCK.ExecOnSelectionChange, 100 ) ;
  181. }
  182. this.EditorDocument.addEventListener( 'mouseup', this.ExecOnSelectionChange, false ) ;
  183. // On Gecko, firing the "OnSelectionChange" event on every key press started to be too much
  184. // slow. So, a timer has been implemented to solve performance issues when typing to quickly.
  185. this.EditorDocument.addEventListener( 'keyup', this.ExecOnSelectionChangeTimer, false ) ;
  186. this._DblClickListener = function( e )
  187. {
  188. FCK.OnDoubleClick( e.target ) ;
  189. e.stopPropagation() ;
  190. }
  191. this.EditorDocument.addEventListener( 'dblclick', this._DblClickListener, true ) ;
  192. // Record changes for the undo system when there are key down events.
  193. this.EditorDocument.addEventListener( 'keydown', this._KeyDownListener, false ) ;
  194. // Hooks for data object drops
  195. if ( FCKBrowserInfo.IsGecko )
  196. {
  197. this.EditorWindow.addEventListener( 'dragdrop', this._ExecDrop, true ) ;
  198. }
  199. else if ( FCKBrowserInfo.IsSafari )
  200. {
  201. var cancelHandler = function( evt ){ if ( ! FCK.MouseDownFlag ) evt.returnValue = false ; }
  202. this.EditorDocument.addEventListener( 'dragenter', cancelHandler, true ) ;
  203. this.EditorDocument.addEventListener( 'dragover', cancelHandler, true ) ;
  204. this.EditorDocument.addEventListener( 'drop', this._ExecDrop, true ) ;
  205. this.EditorDocument.addEventListener( 'mousedown',
  206. function( ev )
  207. {
  208. var element = ev.srcElement ;
  209. if ( element.nodeName.IEquals( 'IMG', 'HR', 'INPUT', 'TEXTAREA', 'SELECT' ) )
  210. {
  211. FCKSelection.SelectNode( element ) ;
  212. }
  213. }, true ) ;
  214. this.EditorDocument.addEventListener( 'mouseup',
  215. function( ev )
  216. {
  217. if ( ev.srcElement.nodeName.IEquals( 'INPUT', 'TEXTAREA', 'SELECT' ) )
  218. ev.preventDefault()
  219. }, true ) ;
  220. this.EditorDocument.addEventListener( 'click',
  221. function( ev )
  222. {
  223. if ( ev.srcElement.nodeName.IEquals( 'INPUT', 'TEXTAREA', 'SELECT' ) )
  224. ev.preventDefault()
  225. }, true ) ;
  226. }
  227. // Kludge for buggy Gecko caret positioning logic (Bug #393 and #1056)
  228. if ( FCKBrowserInfo.IsGecko || FCKBrowserInfo.IsOpera )
  229. {
  230. this.EditorDocument.addEventListener( 'keypress', this._ExecCheckCaret, false ) ;
  231. this.EditorDocument.addEventListener( 'click', this._ExecCheckCaret, false ) ;
  232. }
  233. // Reset the context menu.
  234. FCK.ContextMenu._InnerContextMenu.SetMouseClickWindow( FCK.EditorWindow ) ;
  235. FCK.ContextMenu._InnerContextMenu.AttachToElement( FCK.EditorDocument ) ;
  236. }
  237. FCK.MakeEditable = function()
  238. {
  239. this.EditingArea.MakeEditable() ;
  240. }
  241. // Disable the context menu in the editor (outside the editing area).
  242. function Document_OnContextMenu( e )
  243. {
  244. if ( !e.target._FCKShowContextMenu )
  245. e.preventDefault() ;
  246. }
  247. document.oncontextmenu = Document_OnContextMenu ;
  248. // GetNamedCommandState overload for Gecko.
  249. FCK._BaseGetNamedCommandState = FCK.GetNamedCommandState ;
  250. FCK.GetNamedCommandState = function( commandName )
  251. {
  252. switch ( commandName )
  253. {
  254. case 'Unlink' :
  255. return FCKSelection.HasAncestorNode('A') ? FCK_TRISTATE_OFF : FCK_TRISTATE_DISABLED ;
  256. default :
  257. return FCK._BaseGetNamedCommandState( commandName ) ;
  258. }
  259. }
  260. // Named commands to be handled by this browsers specific implementation.
  261. FCK.RedirectNamedCommands =
  262. {
  263. Print : true,
  264. Paste : true,
  265. Cut : true,
  266. Copy : true
  267. } ;
  268. // ExecuteNamedCommand overload for Gecko.
  269. FCK.ExecuteRedirectedNamedCommand = function( commandName, commandParameter )
  270. {
  271. switch ( commandName )
  272. {
  273. case 'Print' :
  274. FCK.EditorWindow.print() ;
  275. break ;
  276. case 'Paste' :
  277. try
  278. {
  279. // Force the paste dialog for Safari (#50).
  280. if ( FCKBrowserInfo.IsSafari )
  281. throw '' ;
  282. if ( FCK.Paste() )
  283. FCK.ExecuteNamedCommand( 'Paste', null, true ) ;
  284. }
  285. catch (e) { FCKDialog.OpenDialog( 'FCKDialog_Paste', FCKLang.Paste, 'dialog/fck_paste.html', 400, 330, 'Security' ) ; }
  286. break ;
  287. case 'Cut' :
  288. try { FCK.ExecuteNamedCommand( 'Cut', null, true ) ; }
  289. catch (e) { alert(FCKLang.PasteErrorCut) ; }
  290. break ;
  291. case 'Copy' :
  292. try { FCK.ExecuteNamedCommand( 'Copy', null, true ) ; }
  293. catch (e) { alert(FCKLang.PasteErrorCopy) ; }
  294. break ;
  295. default :
  296. FCK.ExecuteNamedCommand( commandName, commandParameter ) ;
  297. }
  298. }
  299. FCK._ExecPaste = function()
  300. {
  301. // Save a snapshot for undo before actually paste the text
  302. FCKUndo.SaveUndoStep() ;
  303. if ( FCKConfig.ForcePasteAsPlainText )
  304. {
  305. FCK.PasteAsPlainText() ;
  306. return false ;
  307. }
  308. /* For now, the AutoDetectPasteFromWord feature is IE only. */
  309. return true ;
  310. }
  311. //**
  312. // FCK.InsertHtml: Inserts HTML at the current cursor location. Deletes the
  313. // selected content if any.
  314. FCK.InsertHtml = function( html )
  315. {
  316. html = FCKConfig.ProtectedSource.Protect( html ) ;
  317. html = FCK.ProtectEvents( html ) ;
  318. html = FCK.ProtectUrls( html ) ;
  319. html = FCK.ProtectTags( html ) ;
  320. // Save an undo snapshot first.
  321. FCKUndo.SaveUndoStep() ;
  322. // Insert the HTML code.
  323. this.EditorDocument.execCommand( 'inserthtml', false, html ) ;
  324. this.Focus() ;
  325. // For some strange reason the SaveUndoStep() call doesn't activate the undo button at the first InsertHtml() call.
  326. this.Events.FireEvent( "OnSelectionChange" ) ;
  327. }
  328. FCK.PasteAsPlainText = function()
  329. {
  330. // TODO: Implement the "Paste as Plain Text" code.
  331. // If the function is called immediately Firefox 2 does automatically paste the contents as soon as the new dialog is created
  332. // so we run it in a Timeout and the paste event can be cancelled
  333. FCKTools.RunFunction( FCKDialog.OpenDialog, FCKDialog, ['FCKDialog_Paste', FCKLang.PasteAsText, 'dialog/fck_paste.html', 400, 330, 'PlainText'] ) ;
  334. /*
  335. var sText = FCKTools.HTMLEncode( clipboardData.getData("Text") ) ;
  336. sText = sText.replace( /\n/g, '<BR>' ) ;
  337. this.InsertHtml( sText ) ;
  338. */
  339. }
  340. /*
  341. FCK.PasteFromWord = function()
  342. {
  343. // TODO: Implement the "Paste as Plain Text" code.
  344. FCKDialog.OpenDialog( 'FCKDialog_Paste', FCKLang.PasteFromWord, 'dialog/fck_paste.html', 400, 330, 'Word' ) ;
  345. // FCK.CleanAndPaste( FCK.GetClipboardHTML() ) ;
  346. }
  347. */
  348. FCK.GetClipboardHTML = function()
  349. {
  350. return '' ;
  351. }
  352. FCK.CreateLink = function( url, noUndo )
  353. {
  354. // Creates the array that will be returned. It contains one or more created links (see #220).
  355. var aCreatedLinks = new Array() ;
  356. FCK.ExecuteNamedCommand( 'Unlink', null, false, !!noUndo ) ;
  357. if ( url.length > 0 )
  358. {
  359. // Generate a temporary name for the link.
  360. var sTempUrl = 'javascript:void(0);/*' + ( new Date().getTime() ) + '*/' ;
  361. // Use the internal "CreateLink" command to create the link.
  362. FCK.ExecuteNamedCommand( 'CreateLink', sTempUrl, false, !!noUndo ) ;
  363. // Retrieve the just created links using XPath.
  364. var oLinksInteractor = this.EditorDocument.evaluate("//a[@href='" + sTempUrl + "']", this.EditorDocument.body, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null) ;
  365. // Add all links to the returning array.
  366. for ( var i = 0 ; i < oLinksInteractor.snapshotLength ; i++ )
  367. {
  368. var oLink = oLinksInteractor.snapshotItem( i ) ;
  369. oLink.href = url ;
  370. // It may happen that the browser (aka Safari) decides to use the
  371. // URL as the link content to not leave it empty. In this case,
  372. // let's reset it.
  373. if ( sTempUrl == oLink.innerHTML )
  374. oLink.innerHTML = '' ;
  375. aCreatedLinks.push( oLink ) ;
  376. }
  377. }
  378. return aCreatedLinks ;
  379. }
  380. FCK._FillEmptyBlock = function( emptyBlockNode )
  381. {
  382. if ( ! emptyBlockNode || emptyBlockNode.nodeType != 1 )
  383. return ;
  384. var nodeTag = emptyBlockNode.tagName.toLowerCase() ;
  385. if ( nodeTag != 'p' && nodeTag != 'div' )
  386. return ;
  387. if ( emptyBlockNode.firstChild )
  388. return ;
  389. FCKTools.AppendBogusBr( emptyBlockNode ) ;
  390. }
  391. FCK._ExecCheckEmptyBlock = function()
  392. {
  393. FCK._FillEmptyBlock( FCK.EditorDocument.body.firstChild ) ;
  394. var sel = FCK.EditorWindow.getSelection() ;
  395. if ( !sel || sel.rangeCount < 1 )
  396. return ;
  397. var range = sel.getRangeAt( 0 );
  398. FCK._FillEmptyBlock( range.startContainer ) ;
  399. }