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