source: trunk/modules/Gecko/paraHandlerBest.js @ 694

Last change on this file since 694 was 694, checked in by gogo, 12 years ago

Merge from branches/688-fixup to complete the fix of changeset:688 to
have history. See ticket:10 for info.

For reference, this is how I fixed it...

  1. Checkout
  2. Rolled back the bad revision (svn merge -c -688) in the WC
  3. Copied that WC to a branch to work on (branches/688-fixup)
  4. Checkout copy of the branch
  5. In branch, use svn mv and svn cp to shift the files properly
  6. Checkout a copy of the trunk
  7. Apply any remaining differences from the trunk to the branch (manually, just copy the files across)
  8. Checkin the branch
  9. Checkout 2nd copy of trunk as merge_WC
  10. svn merge trunk_WC@688 branch_WC@HEAD merge_WC
  11. Verify that there are no differences in the files between the

three working copies (the only difference now should be history)

  1. checkin the merge_WC (this commit)

As you can see, it's a complete pain to fixup these, so please everybody
be careful to use the correct subversion commands when you move (rename)
and copy files.

  • Property svn:eol-style set to native
  • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
File size: 24.0 KB
Line 
1// tabs 2
2
3/**
4* @fileoverview By Adam Wright, for The University of Western Australia
5*
6* Distributed under the same terms as Xinha itself.
7* This notice MUST stay intact for use (see license.txt).
8*
9* Heavily modified by Yermo Lamers of DTLink, LLC, College Park, Md., USA.
10* For more info see http://www.areaedit.com
11*/
12
13/**
14* plugin Info
15*/
16
17EnterParagraphs._pluginInfo =
18{
19  name          : "EnterParagraphs",
20  version       : "1.0",
21  developer     : "Adam Wright",
22  developer_url : "http://www.hipikat.org/",
23  sponsor       : "The University of Western Australia",
24  sponsor_url   : "http://www.uwa.edu.au/",
25  license       : "htmlArea"
26};
27
28// ------------------------------------------------------------------
29
30// "constants"
31
32/**
33* Whitespace Regex
34*/
35
36EnterParagraphs.prototype._whiteSpace = /^\s*$/;
37
38/**
39* The pragmatic list of which elements a paragraph may not contain
40*/
41
42EnterParagraphs.prototype._pExclusions = /^(address|blockquote|body|dd|div|dl|dt|fieldset|form|h1|h2|h3|h4|h5|h6|hr|li|noscript|ol|p|pre|table|ul)$/i;
43
44/**
45* elements which may contain a paragraph
46*/
47
48EnterParagraphs.prototype._pContainers = /^(body|del|div|fieldset|form|ins|map|noscript|object|td|th)$/i;
49
50/**
51* Elements which may not contain paragraphs, and would prefer a break to being split
52*/
53
54EnterParagraphs.prototype._pBreak = /^(address|pre|blockquote)$/i;
55
56/**
57* Elements which may not contain children
58*/
59
60EnterParagraphs.prototype._permEmpty = /^(area|base|basefont|br|col|frame|hr|img|input|isindex|link|meta|param)$/i;
61
62/**
63* Elements which count as content, as distinct from whitespace or containers
64*/
65
66EnterParagraphs.prototype._elemSolid = /^(applet|br|button|hr|img|input|table)$/i;
67
68/**
69* Elements which should get a new P, before or after, when enter is pressed at either end
70*/
71
72EnterParagraphs.prototype._pifySibling = /^(address|blockquote|del|div|dl|fieldset|form|h1|h2|h3|h4|h5|h6|hr|ins|map|noscript|object|ol|p|pre|table|ul|)$/i;
73EnterParagraphs.prototype._pifyForced = /^(ul|ol|dl|table)$/i;
74
75/**
76* Elements which should get a new P, before or after a close parent, when enter is pressed at either end
77*/
78
79EnterParagraphs.prototype._pifyParent = /^(dd|dt|li|td|th|tr)$/i;
80
81// ---------------------------------------------------------------------
82
83/**
84* EnterParagraphs Constructor
85*/
86
87function EnterParagraphs(editor)
88{
89 
90  this.editor = editor;
91 
92  // hook into the event handler to intercept key presses if we are using
93  // gecko (Mozilla/FireFox)
94  if (Xinha.is_gecko)
95  {
96    this.onKeyPress = this.__onKeyPress;
97  }
98 
99}       // end of constructor.
100
101// ------------------------------------------------------------------
102
103/**
104* name member for debugging
105*
106* This member is used to identify objects of this class in debugging
107* messages.
108*/
109EnterParagraphs.prototype.name = "EnterParagraphs";
110
111/**
112* Gecko's a bit lacking in some odd ways...
113*/
114EnterParagraphs.prototype.insertAdjacentElement = function(ref,pos,el)
115{
116  if ( pos == 'BeforeBegin' )
117  {
118    ref.parentNode.insertBefore(el,ref);
119  }
120  else if ( pos == 'AfterEnd' )
121  {
122    ref.nextSibling ? ref.parentNode.insertBefore(el,ref.nextSibling) : ref.parentNode.appendChild(el);
123  }
124  else if ( pos == 'AfterBegin' && ref.firstChild )
125  {
126    ref.insertBefore(el,ref.firstChild);
127  }
128  else if ( pos == 'BeforeEnd' || pos == 'AfterBegin' )
129  {
130    ref.appendChild(el);
131  }
132 
133};      // end of insertAdjacentElement()
134
135// ----------------------------------------------------------------
136
137/**
138* Passes a global parent node or document fragment to forEachNode
139*
140* @param root node root node to start search from.
141* @param mode string function to apply to each node.
142* @param direction string traversal direction "ltr" (left to right) or "rtl" (right_to_left)
143* @param init boolean
144*/
145
146EnterParagraphs.prototype.forEachNodeUnder = function ( root, mode, direction, init )
147{
148 
149  // Identify the first and last nodes to deal with
150  var start, end;
151 
152  // nodeType 11 is DOCUMENT_FRAGMENT_NODE which is a container.
153  if ( root.nodeType == 11 && root.firstChild )
154  {
155    start = root.firstChild;
156    end = root.lastChild;
157  }
158  else
159  {
160    start = end = root;
161  }
162  // traverse down the right hand side of the tree getting the last child of the last
163  // child in each level until we reach bottom.
164  while ( end.lastChild )
165  {
166    end = end.lastChild;
167  }
168 
169  return this.forEachNode( start, end, mode, direction, init);
170 
171};      // end of forEachNodeUnder()
172
173// -----------------------------------------------------------------------
174
175/**
176* perform a depth first descent in the direction requested.
177*
178* @param left_node node "start node"
179* @param right_node node "end node"
180* @param mode string function to apply to each node. cullids or emptyset.
181* @param direction string traversal direction "ltr" (left to right) or "rtl" (right_to_left)
182* @param init boolean or object.
183*/
184
185EnterParagraphs.prototype.forEachNode = function (left_node, right_node, mode, direction, init)
186{
187 
188  // returns "Brother" node either left or right.
189  var getSibling = function(elem, direction)
190        {
191    return ( direction == "ltr" ? elem.nextSibling : elem.previousSibling );
192        };
193 
194  var getChild = function(elem, direction)
195        {
196    return ( direction == "ltr" ? elem.firstChild : elem.lastChild );
197        };
198 
199  var walk, lookup, fnReturnVal;
200 
201  // FIXME: init is a boolean in the emptyset case and an object in
202  // the cullids case. Used inconsistently.
203 
204  var next_node = init;
205 
206  // used to flag having reached the last node.
207 
208  var done_flag = false;
209 
210  // loop ntil we've hit the last node in the given direction.
211  // if we're going left to right that's the right_node and visa-versa.
212 
213  while ( walk != direction == "ltr" ? right_node : left_node )
214  {
215   
216    // on first entry, walk here is null. So this is how
217    // we prime the loop with the first node.
218   
219    if ( !walk )
220    {
221      walk = direction == "ltr" ? left_node : right_node;
222    }
223    else
224    {
225     
226      // is there a child node?
227     
228      if ( getChild(walk,direction) )
229      {
230       
231        // descend down into the child.
232       
233        walk = getChild(walk,direction);
234       
235      }
236      else
237      {
238       
239        // is there a sibling node on this level?
240       
241        if ( getSibling(walk,direction) )
242        {
243          // move to the sibling.
244          walk = getSibling(walk,direction);
245        }
246        else
247        {
248          lookup = walk;
249         
250          // climb back up the tree until we find a level where we are not the end
251          // node on the level (i.e. that we have a sibling in the direction
252            // we are searching) or until we reach the end.
253         
254          while ( !getSibling(lookup,direction) && lookup != (direction == "ltr" ? right_node : left_node) )
255          {
256            lookup = lookup.parentNode;
257          }
258         
259          // did we find a level with a sibling?
260         
261          // walk = ( lookup.nextSibling ? lookup.nextSibling : lookup ) ;
262         
263          walk = ( getSibling(lookup,direction) ? getSibling(lookup,direction) : lookup ) ;
264         
265        }
266      }
267     
268    }   // end of else walk.
269   
270    // have we reached the end? either as a result of the top while loop or climbing
271    // back out above.
272   
273    done_flag = (walk==( direction == "ltr" ? right_node : left_node));
274   
275    // call the requested function on the current node. Functions
276    // return an array.
277    //
278    // Possible functions are _fenCullIds, _fenEmptySet
279    //
280    // The situation is complicated by the fact that sometimes we want to
281    // return the base node and sometimes we do not.
282    //
283    // next_node can be an object (this.takenIds), a node (text, el, etc) or false.
284   
285    switch( mode )
286    {
287     
288    case "cullids":
289     
290      fnReturnVal = this._fenCullIds(walk, next_node );
291      break;
292     
293    case "find_fill":
294     
295      fnReturnVal = this._fenEmptySet(walk, next_node, mode, done_flag);
296      break;
297     
298    case "find_cursorpoint":
299     
300      fnReturnVal = this._fenEmptySet(walk, next_node, mode, done_flag);
301      break;
302     
303    }
304   
305    // If this node wants us to return, return next_node
306   
307    if ( fnReturnVal[0] )
308    {
309      return fnReturnVal[1];
310    }
311   
312    // are we done with the loop?
313   
314    if ( done_flag )
315    {
316      break;
317    }
318   
319    // Otherwise, pass to the next node
320   
321    if ( fnReturnVal[1] )
322    {
323      next_node = fnReturnVal[1];
324    }
325   
326  }     // end of while loop
327 
328  return false;
329 
330};      // end of forEachNode()
331
332// -------------------------------------------------------------------
333
334/**
335* Find a post-insertion node, only if all nodes are empty, or the first content
336*
337* @param node node current node beinge examined.
338* @param next_node node next node to be examined.
339* @param node string "find_fill" or "find_cursorpoint"
340* @param last_flag boolean is this the last node?
341*/
342
343EnterParagraphs.prototype._fenEmptySet = function( node, next_node, mode, last_flag)
344{
345 
346  // Mark this if it's the first base
347 
348  if ( !next_node && !node.firstChild )
349  {
350    next_node = node;
351  }
352 
353  // Is it an element node and is it considered content? (br, hr, etc)
354  // or is it a text node that is not just whitespace?
355  // or is it not an element node and not a text node?
356 
357  if ( (node.nodeType == 1 && this._elemSolid.test(node.nodeName)) ||
358    (node.nodeType == 3 && !this._whiteSpace.test(node.nodeValue)) ||
359  (node.nodeType != 1 && node.nodeType != 3) )
360  {
361   
362    switch( mode )
363    {
364     
365    case "find_fill":
366     
367      // does not return content.
368     
369      return new Array(true, false );
370      break;
371     
372    case "find_cursorpoint":
373     
374      // returns content
375     
376      return new Array(true, node );
377      break;
378     
379    }
380   
381  }
382 
383  // In either case (fill or findcursor) we return the base node. The avoids
384  // problems in terminal cases (beginning or end of document or container tags)
385 
386  if ( last_flag )
387  {
388    return new Array( true, next_node );
389  }
390 
391  return new Array( false, next_node );
392 
393};      // end of _fenEmptySet()
394
395// ------------------------------------------------------------------------------
396
397/**
398* remove duplicate Id's.
399*
400* @param ep_ref enterparagraphs reference to enterparagraphs object
401*/
402
403EnterParagraphs.prototype._fenCullIds = function ( ep_ref, node, pong )
404{
405 
406  // Check for an id, blast it if it's in the store, otherwise add it
407 
408  if ( node.id )
409  {
410   
411    pong[node.id] ? node.id = '' : pong[node.id] = true;
412  }
413 
414  return new Array(false,pong);
415 
416};
417
418// ---------------------------------------------------------------------------------
419
420/**
421* Grabs a range suitable for paragraph stuffing
422*
423* @param rng Range
424* @param search_direction string "left" or "right"
425*
426* @todo check blank node issue in roaming loop.
427*/
428
429EnterParagraphs.prototype.processSide = function( rng, search_direction)
430{
431 
432  var next = function(element, search_direction)
433        {
434    return ( search_direction == "left" ? element.previousSibling : element.nextSibling );
435        };
436 
437  var node = search_direction == "left" ? rng.startContainer : rng.endContainer;
438  var offset = search_direction == "left" ? rng.startOffset : rng.endOffset;
439  var roam, start = node;
440 
441  // Never start with an element, because then the first roaming node might
442  // be on the exclusion list and we wouldn't know until it was too late
443 
444  while ( start.nodeType == 1 && !this._permEmpty.test(start.nodeName) )
445  {
446    start = ( offset ? start.lastChild : start.firstChild );
447  }
448 
449  // Climb the tree, left or right, until our course of action presents itself
450  //
451  // if roam is NULL try start.
452  // if roam is NOT NULL, try next node in our search_direction
453  // If that node is NULL, get our parent node.
454  //
455  // If all the above turns out NULL end the loop.
456  //
457  // FIXME: gecko (firefox 1.0.3) - enter "test" into an empty document and press enter.
458  // sometimes this loop finds a blank text node, sometimes it doesn't.
459 
460  while ( roam = roam ? ( next(roam,search_direction) ? next(roam,search_direction) : roam.parentNode ) : start )
461  {
462   
463    // next() is an inline function defined above that returns the next node depending
464    // on the direction we're searching.
465   
466    if ( next(roam,search_direction) )
467    {
468     
469      // If the next sibling's on the exclusion list, stop before it
470     
471      if ( this._pExclusions.test(next(roam,search_direction).nodeName) )
472      {
473       
474        return this.processRng(rng, search_direction, roam, next(roam,search_direction), (search_direction == "left"?'AfterEnd':'BeforeBegin'), true, false);
475      }
476    }
477    else
478    {
479     
480      // If our parent's on the container list, stop inside it
481     
482      if (this._pContainers.test(roam.parentNode.nodeName))
483      {
484       
485        return this.processRng(rng, search_direction, roam, roam.parentNode, (search_direction == "left"?'AfterBegin':'BeforeEnd'), true, false);
486      }
487      else if (this._pExclusions.test(roam.parentNode.nodeName))
488      {
489       
490        // chop without wrapping
491       
492        if (this._pBreak.test(roam.parentNode.nodeName))
493        {
494         
495          return this.processRng(rng, search_direction, roam, roam.parentNode,
496            (search_direction == "left"?'AfterBegin':'BeforeEnd'), false, (search_direction == "left" ?true:false));
497        }
498        else
499        {
500         
501          // the next(roam,search_direction) in this call is redundant since we know it's false
502          // because of the "if next(roam,search_direction)" above.
503          //
504          // the final false prevents this range from being wrapped in <p>'s most likely
505          // because it's already wrapped.
506         
507          return this.processRng(rng,
508            search_direction,
509            (roam = roam.parentNode),
510            (next(roam,search_direction) ? next(roam,search_direction) : roam.parentNode),
511            (next(roam,search_direction) ? (search_direction == "left"?'AfterEnd':'BeforeBegin') : (search_direction == "left"?'AfterBegin':'BeforeEnd')),
512            false,
513            false);
514        }
515      }
516    }
517  }
518 
519};      // end of processSide()
520
521// ------------------------------------------------------------------------------
522
523/**
524* processRng - process Range.
525*
526* Neighbour and insertion identify where the new node, roam, needs to enter
527* the document; landmarks in our selection will be deleted before insertion
528*
529* @param rn Range original selected range
530* @param search_direction string Direction to search in.
531* @param roam node
532* @param insertion string may be AfterBegin of BeforeEnd
533* @return array
534*/
535
536EnterParagraphs.prototype.processRng = function(rng, search_direction, roam, neighbour, insertion, pWrap, preBr)
537{
538  var node = search_direction == "left" ? rng.startContainer : rng.endContainer;
539  var offset = search_direction == "left" ? rng.startOffset : rng.endOffset;
540 
541  // Define the range to cut, and extend the selection range to the same boundary
542 
543  var editor = this.editor;
544  var newRng = editor._doc.createRange();
545 
546  newRng.selectNode(roam);
547  // extend the range in the given direction.
548 
549  if ( search_direction == "left")
550  {
551    newRng.setEnd(node, offset);
552    rng.setStart(newRng.startContainer, newRng.startOffset);
553  }
554  else if ( search_direction == "right" )
555  {
556   
557    newRng.setStart(node, offset);
558    rng.setEnd(newRng.endContainer, newRng.endOffset);
559  }
560  // Clone the range and remove duplicate ids it would otherwise produce
561 
562  var cnt = newRng.cloneContents();
563 
564  // in this case "init" is an object not a boolen.
565 
566  this.forEachNodeUnder( cnt, "cullids", "ltr", this.takenIds, false, false);
567 
568  // Special case, for inserting paragraphs before some blocks when caret is at
569  // their zero offset.
570  //
571  // Used to "open up space" in front of a list, table. Usefull if the list is at
572  // the top of the document. (otherwise you'd have no way of "moving it down").
573 
574  var pify, pifyOffset, fill;
575  pify = search_direction == "left" ? (newRng.endContainer.nodeType == 3 ? true:false) : (newRng.startContainer.nodeType == 3 ? false:true);
576  pifyOffset = pify ? newRng.startOffset : newRng.endOffset;
577  pify = pify ? newRng.startContainer : newRng.endContainer;
578 
579  if ( this._pifyParent.test(pify.nodeName) && pify.parentNode.childNodes.item(0) == pify )
580  {
581    while ( !this._pifySibling.test(pify.nodeName) )
582    {
583      pify = pify.parentNode;
584    }
585  }
586 
587  // NODE TYPE 11 is DOCUMENT_FRAGMENT NODE
588  // I do not profess to understand any of this, simply applying a patch that others say is good - ticket:446
589  if ( cnt.nodeType == 11 && !cnt.firstChild)
590  {     
591    if (pify.nodeName != "BODY" || (pify.nodeName == "BODY" && pifyOffset != 0))
592    { //WKR: prevent body tag in empty doc
593      cnt.appendChild(editor._doc.createElement(pify.nodeName));
594    }
595  }
596 
597  // YmL: Added additional last parameter for fill case to work around logic
598  // error in forEachNode()
599 
600  fill = this.forEachNodeUnder(cnt, "find_fill", "ltr", false );
601 
602  if ( fill &&
603    this._pifySibling.test(pify.nodeName) &&
604  ( (pifyOffset == 0) || ( pifyOffset == 1 && this._pifyForced.test(pify.nodeName) ) ) )
605  {
606   
607    roam = editor._doc.createElement( 'p' );
608    roam.innerHTML = "&nbsp;";
609   
610    // roam = editor._doc.createElement('p');
611    // roam.appendChild(editor._doc.createElement('br'));
612   
613    // for these cases, if we are processing the left hand side we want it to halt
614    // processing instead of doing the right hand side. (Avoids adding another <p>&nbsp</p>
615      // after the list etc.
616     
617      if ((search_direction == "left" ) && pify.previousSibling)
618      {
619       
620        return new Array(pify.previousSibling, 'AfterEnd', roam);
621      }
622      else if (( search_direction == "right") && pify.nextSibling)
623      {
624       
625        return new Array(pify.nextSibling, 'BeforeBegin', roam);
626      }
627      else
628      {
629       
630        return new Array(pify.parentNode, (search_direction == "left"?'AfterBegin':'BeforeEnd'), roam);
631      }
632     
633  }
634 
635  // If our cloned contents are 'content'-less, shove a break in them
636 
637  if ( fill )
638  {
639   
640    // Ill-concieved?
641    //
642    // 3 is a TEXT node and it should be empty.
643    //
644   
645    if ( fill.nodeType == 3 )
646    {
647      // fill = fill.parentNode;
648     
649      fill = editor._doc.createDocumentFragment();
650    }
651   
652    if ( (fill.nodeType == 1 && !this._elemSolid.test()) || fill.nodeType == 11 )
653    {
654     
655      // FIXME:/CHECKME: When Xinha is switched from WYSIWYG to text mode
656      // Xinha.getHTMLWrapper() will strip out the trailing br. Not sure why.
657     
658      // fill.appendChild(editor._doc.createElement('br'));
659     
660      var pterminator = editor._doc.createElement( 'p' );
661      pterminator.innerHTML = "&nbsp;";
662     
663      fill.appendChild( pterminator );
664     
665    }
666    else
667    {
668     
669      // fill.parentNode.insertBefore(editor._doc.createElement('br'),fill);
670     
671      var pterminator = editor._doc.createElement( 'p' );
672      pterminator.innerHTML = "&nbsp;";
673     
674      fill.parentNode.insertBefore(parentNode,fill);
675     
676    }
677  }
678 
679  // YmL: If there was no content replace with fill
680  // (previous code did not use fill and we ended up with the
681    // <p>test</p><p></p> because Gecko was finding two empty text nodes
682    // when traversing on the right hand side of an empty document.
683   
684    if ( fill )
685    {
686     
687      roam = fill;
688    }
689    else
690    {
691      // And stuff a shiny new object with whatever contents we have
692     
693      roam = (pWrap || (cnt.nodeType == 11 && !cnt.firstChild)) ? editor._doc.createElement('p') : editor._doc.createDocumentFragment();
694      roam.appendChild(cnt);
695    }
696   
697    if (preBr)
698    {
699      roam.appendChild(editor._doc.createElement('br'));
700    }
701    // Return the nearest relative, relative insertion point and fragment to insert
702   
703    return new Array(neighbour, insertion, roam);
704   
705};      // end of processRng()
706
707// ----------------------------------------------------------------------------------
708
709/**
710* are we an <li> that should be handled by the browser?
711*
712* there is no good way to "get out of" ordered or unordered lists from Javascript.
713* We have to pass the onKeyPress 13 event to the browser so it can take care of
714* getting us "out of" the list.
715*
716* The Gecko engine does a good job of handling all the normal <li> cases except the "press
717* enter at the first position" where we want a <p>&nbsp</p> inserted before the list. The
718* built-in behavior is to open up a <li> before the current entry (not good).
719*
720* @param rng Range range.
721*/
722
723EnterParagraphs.prototype.isNormalListItem = function(rng)
724{
725 
726  var node, listNode;
727 
728  node = rng.startContainer;
729 
730  if (( typeof node.nodeName != 'undefined') &&
731    ( node.nodeName.toLowerCase() == 'li' ))
732  {
733   
734    // are we a list item?
735   
736    listNode = node;
737  }
738  else if (( typeof node.parentNode != 'undefined' ) &&
739    ( typeof node.parentNode.nodeName != 'undefined' ) &&
740  ( node.parentNode.nodeName.toLowerCase() == 'li' ))
741  {
742   
743    // our parent is a list item.
744   
745    listNode = node.parentNode;
746   
747  }
748  else
749  {
750    // neither we nor our parent are a list item. this is not a normal
751    // li case.
752   
753    return false;
754  }
755 
756  // at this point we have a listNode. Is it the first list item?
757 
758  if ( ! listNode.previousSibling )
759  {
760    // are we on the first character of the first li?
761   
762    if ( rng.startOffset == 0 )
763    {
764      return false;
765    }
766  }
767  return true;
768 
769};      // end of isNormalListItem()
770
771// ----------------------------------------------------------------------------------
772/**
773* Called when a key is pressed in the editor
774*/
775
776EnterParagraphs.prototype.__onKeyPress = function(ev)
777{
778 
779  // If they've hit enter and shift is not pressed, handle it
780 
781  if (ev.keyCode == 13 && !ev.shiftKey && this.editor._iframe.contentWindow.getSelection)
782  {
783    return this.handleEnter(ev);
784  }
785 
786};      // end of _onKeyPress()
787
788// -----------------------------------------------------------------------------------
789
790/**
791* Handles the pressing of an unshifted enter for Gecko
792*/
793
794EnterParagraphs.prototype.handleEnter = function(ev)
795{
796 
797  var cursorNode;
798 
799  // Grab the selection and associated range
800 
801  var sel = this.editor.getSelection();
802  var rng = this.editor.createRange(sel);
803 
804  // if we are at the end of a list and the node is empty let the browser handle
805  // it to get us out of the list.
806 
807  if ( this.isNormalListItem(rng) )
808  {
809   
810    return true;
811  }
812 
813  // as far as I can tell this isn't actually used.
814 
815  this.takenIds = new Object();
816 
817  // Grab ranges for document re-stuffing, if appropriate
818  //
819  // pStart and pEnd are arrays consisting of
820  // [0] neighbor node
821  // [1] insertion type
822  // [2] roam
823 
824  var pStart = this.processSide(rng, "left");
825 
826  var pEnd = this.processSide(rng, "right");
827 
828  // used to position the cursor after insertion.
829 
830  cursorNode = pEnd[2];
831 
832  // Get rid of everything local to the selection
833 
834  sel.removeAllRanges();
835  rng.deleteContents();
836 
837  // Grab a node we'll have after insertion, since fragments will be lost
838  //
839  // we'll use this to position the cursor.
840 
841  var holdEnd = this.forEachNodeUnder( cursorNode, "find_cursorpoint", "ltr", false, true);
842 
843  if ( ! holdEnd )
844  {
845    alert( "INTERNAL ERROR - could not find place to put cursor after ENTER" );
846  }
847 
848  // Insert our carefully chosen document fragments
849 
850  if ( pStart )
851  {
852   
853    this.insertAdjacentElement(pStart[0], pStart[1], pStart[2]);
854  }
855 
856  if ( pEnd && pEnd.nodeType != 1)
857  {
858   
859    this.insertAdjacentElement(pEnd[0], pEnd[1], pEnd[2]);
860  }
861 
862  // Move the caret in front of the first good text element
863 
864  if ((holdEnd) && (this._permEmpty.test(holdEnd.nodeName) ))
865  {
866   
867    var prodigal = 0;
868    while ( holdEnd.parentNode.childNodes.item(prodigal) != holdEnd )
869    {
870      prodigal++;
871    }
872   
873    sel.collapse( holdEnd.parentNode, prodigal);
874  }
875  else
876  {
877   
878    // holdEnd might be false.
879   
880    try
881    {
882      sel.collapse(holdEnd, 0);
883     
884      // interestingly, scrollToElement() scroll so the top if holdEnd is a text node.
885     
886      if ( holdEnd.nodeType == 3 )
887      {
888        holdEnd = holdEnd.parentNode;
889      }
890     
891      this.editor.scrollToElement(holdEnd);
892    }
893    catch (e)
894    {
895      // we could try to place the cursor at the end of the document.
896    }
897  }
898 
899  this.editor.updateToolbar();
900 
901  Xinha._stopEvent(ev);
902 
903  return true;
904 
905};      // end of handleEnter()
906
907// END
Note: See TracBrowser for help on using the repository browser.