2 Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
3 For licensing, see LICENSE.html or http://ckeditor.com/license
12 function getState( editor
, path
)
14 var firstBlock
= path
.block
|| path
.blockLimit
;
16 if ( !firstBlock
|| firstBlock
.getName() == 'body' )
17 return CKEDITOR
.TRISTATE_OFF
;
19 // See if the first block has a blockquote parent.
20 if ( firstBlock
.getAscendant( 'blockquote', true ) )
21 return CKEDITOR
.TRISTATE_ON
;
23 return CKEDITOR
.TRISTATE_OFF
;
26 function onSelectionChange( evt
)
28 var editor
= evt
.editor
;
29 if ( editor
.readOnly
)
32 var command
= editor
.getCommand( 'blockquote' );
33 command
.state
= getState( editor
, evt
.data
.path
);
34 command
.fire( 'state' );
37 function noBlockLeft( bqBlock
)
39 for ( var i
= 0, length
= bqBlock
.getChildCount(), child
; i
< length
&& ( child
= bqBlock
.getChild( i
) ) ; i
++ )
41 if ( child
.type
== CKEDITOR
.NODE_ELEMENT
&& child
.isBlockBoundary() )
49 exec : function( editor
)
51 var state
= editor
.getCommand( 'blockquote' ).state
,
52 selection
= editor
.getSelection(),
53 range
= selection
&& selection
.getRanges( true )[0];
58 var bookmarks
= selection
.createBookmarks();
60 // Kludge for #1592: if the bookmark nodes are in the beginning of
61 // blockquote, then move them to the nearest block element in the
63 if ( CKEDITOR
.env
.ie
)
65 var bookmarkStart
= bookmarks
[0].startNode
,
66 bookmarkEnd
= bookmarks
[0].endNode
,
69 if ( bookmarkStart
&& bookmarkStart
.getParent().getName() == 'blockquote' )
71 cursor
= bookmarkStart
;
72 while ( ( cursor
= cursor
.getNext() ) )
74 if ( cursor
.type
== CKEDITOR
.NODE_ELEMENT
&&
75 cursor
.isBlockBoundary() )
77 bookmarkStart
.move( cursor
, true );
84 && bookmarkEnd
.getParent().getName() == 'blockquote' )
87 while ( ( cursor
= cursor
.getPrevious() ) )
89 if ( cursor
.type
== CKEDITOR
.NODE_ELEMENT
&&
90 cursor
.isBlockBoundary() )
92 bookmarkEnd
.move( cursor
);
99 var iterator
= range
.createIterator(),
101 iterator
.enlargeBr
= editor
.config
.enterMode
!= CKEDITOR
.ENTER_BR
;
103 if ( state
== CKEDITOR
.TRISTATE_OFF
)
106 while ( ( block
= iterator
.getNextParagraph() ) )
107 paragraphs
.push( block
);
109 // If no paragraphs, create one from the current selection position.
110 if ( paragraphs
.length
< 1 )
112 var para
= editor
.document
.createElement( editor
.config
.enterMode
== CKEDITOR
.ENTER_P
? 'p' : 'div' ),
113 firstBookmark
= bookmarks
.shift();
114 range
.insertNode( para
);
115 para
.append( new CKEDITOR
.dom
.text( '\ufeff', editor
.document
) );
116 range
.moveToBookmark( firstBookmark
);
117 range
.selectNodeContents( para
);
118 range
.collapse( true );
119 firstBookmark
= range
.createBookmark();
120 paragraphs
.push( para
);
121 bookmarks
.unshift( firstBookmark
);
124 // Make sure all paragraphs have the same parent.
125 var commonParent
= paragraphs
[0].getParent(),
127 for ( var i
= 0 ; i
< paragraphs
.length
; i
++ )
129 block
= paragraphs
[i
];
130 commonParent
= commonParent
.getCommonAncestor( block
.getParent() );
133 // The common parent must not be the following tags: table, tbody, tr, ol, ul.
134 var denyTags
= { table
: 1, tbody
: 1, tr
: 1, ol
: 1, ul
: 1 };
135 while ( denyTags
[ commonParent
.getName() ] )
136 commonParent
= commonParent
.getParent();
138 // Reconstruct the block list to be processed such that all resulting blocks
139 // satisfy parentNode.equals( commonParent ).
140 var lastBlock
= null;
141 while ( paragraphs
.length
> 0 )
143 block
= paragraphs
.shift();
144 while ( !block
.getParent().equals( commonParent
) )
145 block
= block
.getParent();
146 if ( !block
.equals( lastBlock
) )
151 // If any of the selected blocks is a blockquote, remove it to prevent
152 // nested blockquotes.
153 while ( tmp
.length
> 0 )
156 if ( block
.getName() == 'blockquote' )
158 var docFrag
= new CKEDITOR
.dom
.documentFragment( editor
.document
);
159 while ( block
.getFirst() )
161 docFrag
.append( block
.getFirst().remove() );
162 paragraphs
.push( docFrag
.getLast() );
165 docFrag
.replace( block
);
168 paragraphs
.push( block
);
171 // Now we have all the blocks to be included in a new blockquote node.
172 var bqBlock
= editor
.document
.createElement( 'blockquote' );
173 bqBlock
.insertBefore( paragraphs
[0] );
174 while ( paragraphs
.length
> 0 )
176 block
= paragraphs
.shift();
177 bqBlock
.append( block
);
180 else if ( state
== CKEDITOR
.TRISTATE_ON
)
182 var moveOutNodes
= [],
185 while ( ( block
= iterator
.getNextParagraph() ) )
189 while ( block
.getParent() )
191 if ( block
.getParent().getName() == 'blockquote' )
193 bqParent
= block
.getParent();
197 block
= block
.getParent();
200 // Remember the blocks that were recorded down in the moveOutNodes array
201 // to prevent duplicates.
202 if ( bqParent
&& bqChild
&& !bqChild
.getCustomData( 'blockquote_moveout' ) )
204 moveOutNodes
.push( bqChild
);
205 CKEDITOR
.dom
.element
.setMarker( database
, bqChild
, 'blockquote_moveout', true );
209 CKEDITOR
.dom
.element
.clearAllMarkers( database
);
212 processedBlockquoteBlocks
= [];
215 while ( moveOutNodes
.length
> 0 )
217 var node
= moveOutNodes
.shift();
218 bqBlock
= node
.getParent();
220 // If the node is located at the beginning or the end, just take it out
221 // without splitting. Otherwise, split the blockquote node and move the
222 // paragraph in between the two blockquote nodes.
223 if ( !node
.getPrevious() )
224 node
.remove().insertBefore( bqBlock
);
225 else if ( !node
.getNext() )
226 node
.remove().insertAfter( bqBlock
);
229 node
.breakParent( node
.getParent() );
230 processedBlockquoteBlocks
.push( node
.getNext() );
233 // Remember the blockquote node so we can clear it later (if it becomes empty).
234 if ( !bqBlock
.getCustomData( 'blockquote_processed' ) )
236 processedBlockquoteBlocks
.push( bqBlock
);
237 CKEDITOR
.dom
.element
.setMarker( database
, bqBlock
, 'blockquote_processed', true );
240 movedNodes
.push( node
);
243 CKEDITOR
.dom
.element
.clearAllMarkers( database
);
245 // Clear blockquote nodes that have become empty.
246 for ( i
= processedBlockquoteBlocks
.length
- 1 ; i
>= 0 ; i
-- )
248 bqBlock
= processedBlockquoteBlocks
[i
];
249 if ( noBlockLeft( bqBlock
) )
253 if ( editor
.config
.enterMode
== CKEDITOR
.ENTER_BR
)
255 var firstTime
= true;
256 while ( movedNodes
.length
)
258 node
= movedNodes
.shift();
260 if ( node
.getName() == 'div' )
262 docFrag
= new CKEDITOR
.dom
.documentFragment( editor
.document
);
263 var needBeginBr
= firstTime
&& node
.getPrevious() &&
264 !( node
.getPrevious().type
== CKEDITOR
.NODE_ELEMENT
&& node
.getPrevious().isBlockBoundary() );
266 docFrag
.append( editor
.document
.createElement( 'br' ) );
268 var needEndBr
= node
.getNext() &&
269 !( node
.getNext().type
== CKEDITOR
.NODE_ELEMENT
&& node
.getNext().isBlockBoundary() );
270 while ( node
.getFirst() )
271 node
.getFirst().remove().appendTo( docFrag
);
274 docFrag
.append( editor
.document
.createElement( 'br' ) );
276 docFrag
.replace( node
);
283 selection
.selectBookmarks( bookmarks
);
288 CKEDITOR
.plugins
.add( 'blockquote',
290 init : function( editor
)
292 editor
.addCommand( 'blockquote', commandObject
);
294 editor
.ui
.addButton( 'Blockquote',
296 label
: editor
.lang
.blockquote
,
297 command
: 'blockquote'
300 editor
.on( 'selectionChange', onSelectionChange
);
303 requires
: [ 'domiterator' ]