2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
7 function noBlockLeft( bqBlock
) {
8 for ( var i
= 0, length
= bqBlock
.getChildCount(), child
; i
< length
&& ( child
= bqBlock
.getChild( i
) ); i
++ ) {
9 if ( child
.type
== CKEDITOR
.NODE_ELEMENT
&& child
.isBlockBoundary() )
16 exec: function( editor
) {
17 var state
= editor
.getCommand( 'blockquote' ).state
,
18 selection
= editor
.getSelection(),
19 range
= selection
&& selection
.getRanges()[ 0 ];
24 var bookmarks
= selection
.createBookmarks();
26 // Kludge for #1592: if the bookmark nodes are in the beginning of
27 // blockquote, then move them to the nearest block element in the
29 if ( CKEDITOR
.env
.ie
) {
30 var bookmarkStart
= bookmarks
[ 0 ].startNode
,
31 bookmarkEnd
= bookmarks
[ 0 ].endNode
,
34 if ( bookmarkStart
&& bookmarkStart
.getParent().getName() == 'blockquote' ) {
35 cursor
= bookmarkStart
;
36 while ( ( cursor
= cursor
.getNext() ) ) {
37 if ( cursor
.type
== CKEDITOR
.NODE_ELEMENT
&& cursor
.isBlockBoundary() ) {
38 bookmarkStart
.move( cursor
, true );
44 if ( bookmarkEnd
&& bookmarkEnd
.getParent().getName() == 'blockquote' ) {
46 while ( ( cursor
= cursor
.getPrevious() ) ) {
47 if ( cursor
.type
== CKEDITOR
.NODE_ELEMENT
&& cursor
.isBlockBoundary() ) {
48 bookmarkEnd
.move( cursor
);
55 var iterator
= range
.createIterator(),
57 iterator
.enlargeBr
= editor
.config
.enterMode
!= CKEDITOR
.ENTER_BR
;
59 if ( state
== CKEDITOR
.TRISTATE_OFF
) {
61 while ( ( block
= iterator
.getNextParagraph() ) )
62 paragraphs
.push( block
);
64 // If no paragraphs, create one from the current selection position.
65 if ( paragraphs
.length
< 1 ) {
66 var para
= editor
.document
.createElement( editor
.config
.enterMode
== CKEDITOR
.ENTER_P
? 'p' : 'div' ),
67 firstBookmark
= bookmarks
.shift();
68 range
.insertNode( para
);
69 para
.append( new CKEDITOR
.dom
.text( '\ufeff', editor
.document
) );
70 range
.moveToBookmark( firstBookmark
);
71 range
.selectNodeContents( para
);
72 range
.collapse( true );
73 firstBookmark
= range
.createBookmark();
74 paragraphs
.push( para
);
75 bookmarks
.unshift( firstBookmark
);
78 // Make sure all paragraphs have the same parent.
79 var commonParent
= paragraphs
[ 0 ].getParent(),
81 for ( var i
= 0; i
< paragraphs
.length
; i
++ ) {
82 block
= paragraphs
[ i
];
83 commonParent
= commonParent
.getCommonAncestor( block
.getParent() );
86 // The common parent must not be the following tags: table, tbody, tr, ol, ul.
87 var denyTags
= { table
: 1, tbody
: 1, tr
: 1, ol
: 1, ul
: 1 };
88 while ( denyTags
[ commonParent
.getName() ] )
89 commonParent
= commonParent
.getParent();
91 // Reconstruct the block list to be processed such that all resulting blocks
92 // satisfy parentNode.equals( commonParent ).
94 while ( paragraphs
.length
> 0 ) {
95 block
= paragraphs
.shift();
96 while ( !block
.getParent().equals( commonParent
) )
97 block
= block
.getParent();
98 if ( !block
.equals( lastBlock
) )
103 // If any of the selected blocks is a blockquote, remove it to prevent
104 // nested blockquotes.
105 while ( tmp
.length
> 0 ) {
107 if ( block
.getName() == 'blockquote' ) {
108 var docFrag
= new CKEDITOR
.dom
.documentFragment( editor
.document
);
109 while ( block
.getFirst() ) {
110 docFrag
.append( block
.getFirst().remove() );
111 paragraphs
.push( docFrag
.getLast() );
114 docFrag
.replace( block
);
116 paragraphs
.push( block
);
120 // Now we have all the blocks to be included in a new blockquote node.
121 var bqBlock
= editor
.document
.createElement( 'blockquote' );
122 bqBlock
.insertBefore( paragraphs
[ 0 ] );
123 while ( paragraphs
.length
> 0 ) {
124 block
= paragraphs
.shift();
125 bqBlock
.append( block
);
127 } else if ( state
== CKEDITOR
.TRISTATE_ON
) {
128 var moveOutNodes
= [],
131 while ( ( block
= iterator
.getNextParagraph() ) ) {
134 while ( block
.getParent() ) {
135 if ( block
.getParent().getName() == 'blockquote' ) {
136 bqParent
= block
.getParent();
140 block
= block
.getParent();
143 // Remember the blocks that were recorded down in the moveOutNodes array
144 // to prevent duplicates.
145 if ( bqParent
&& bqChild
&& !bqChild
.getCustomData( 'blockquote_moveout' ) ) {
146 moveOutNodes
.push( bqChild
);
147 CKEDITOR
.dom
.element
.setMarker( database
, bqChild
, 'blockquote_moveout', true );
151 CKEDITOR
.dom
.element
.clearAllMarkers( database
);
154 processedBlockquoteBlocks
= [];
157 while ( moveOutNodes
.length
> 0 ) {
158 var node
= moveOutNodes
.shift();
159 bqBlock
= node
.getParent();
161 // If the node is located at the beginning or the end, just take it out
162 // without splitting. Otherwise, split the blockquote node and move the
163 // paragraph in between the two blockquote nodes.
164 if ( !node
.getPrevious() )
165 node
.remove().insertBefore( bqBlock
);
166 else if ( !node
.getNext() )
167 node
.remove().insertAfter( bqBlock
);
169 node
.breakParent( node
.getParent() );
170 processedBlockquoteBlocks
.push( node
.getNext() );
173 // Remember the blockquote node so we can clear it later (if it becomes empty).
174 if ( !bqBlock
.getCustomData( 'blockquote_processed' ) ) {
175 processedBlockquoteBlocks
.push( bqBlock
);
176 CKEDITOR
.dom
.element
.setMarker( database
, bqBlock
, 'blockquote_processed', true );
179 movedNodes
.push( node
);
182 CKEDITOR
.dom
.element
.clearAllMarkers( database
);
184 // Clear blockquote nodes that have become empty.
185 for ( i
= processedBlockquoteBlocks
.length
- 1; i
>= 0; i
-- ) {
186 bqBlock
= processedBlockquoteBlocks
[ i
];
187 if ( noBlockLeft( bqBlock
) )
191 if ( editor
.config
.enterMode
== CKEDITOR
.ENTER_BR
) {
192 var firstTime
= true;
193 while ( movedNodes
.length
) {
194 node
= movedNodes
.shift();
196 if ( node
.getName() == 'div' ) {
197 docFrag
= new CKEDITOR
.dom
.documentFragment( editor
.document
);
198 var needBeginBr
= firstTime
&& node
.getPrevious() && !( node
.getPrevious().type
== CKEDITOR
.NODE_ELEMENT
&& node
.getPrevious().isBlockBoundary() );
200 docFrag
.append( editor
.document
.createElement( 'br' ) );
202 var needEndBr
= node
.getNext() && !( node
.getNext().type
== CKEDITOR
.NODE_ELEMENT
&& node
.getNext().isBlockBoundary() );
203 while ( node
.getFirst() )
204 node
.getFirst().remove().appendTo( docFrag
);
207 docFrag
.append( editor
.document
.createElement( 'br' ) );
209 docFrag
.replace( node
);
216 selection
.selectBookmarks( bookmarks
);
220 refresh: function( editor
, path
) {
221 // Check if inside of blockquote.
222 var firstBlock
= path
.block
|| path
.blockLimit
;
223 this.setState( editor
.elementPath( firstBlock
).contains( 'blockquote', 1 ) ? CKEDITOR
.TRISTATE_ON
: CKEDITOR
.TRISTATE_OFF
);
226 context
: 'blockquote',
228 allowedContent
: 'blockquote',
229 requiredContent
: 'blockquote'
232 CKEDITOR
.plugins
.add( 'blockquote', {
233 // jscs:disable maximumLineLength
234 lang
: 'af,ar,bg,bn,bs,ca,cs,cy,da,de,de-ch,el,en,en-au,en-ca,en-gb,eo,es,et,eu,fa,fi,fo,fr,fr-ca,gl,gu,he,hi,hr,hu,id,is,it,ja,ka,km,ko,ku,lt,lv,mk,mn,ms,nb,nl,no,pl,pt,pt-br,ro,ru,si,sk,sl,sq,sr,sr-latn,sv,th,tr,tt,ug,uk,vi,zh,zh-cn', // %REMOVE_LINE_CORE%
235 // jscs:enable maximumLineLength
236 icons
: 'blockquote', // %REMOVE_LINE_CORE%
237 hidpi
: true, // %REMOVE_LINE_CORE%
238 init: function( editor
) {
239 if ( editor
.blockless
)
242 editor
.addCommand( 'blockquote', commandObject
);
244 editor
.ui
.addButton
&& editor
.ui
.addButton( 'Blockquote', {
245 label
: editor
.lang
.blockquote
.toolbar
,
246 command
: 'blockquote',