PageRenderTime 38ms CodeModel.GetById 18ms app.highlight 15ms RepoModel.GetById 1ms app.codeStats 1ms

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