JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
vanilla ckeditor-3.6.3
[ckeditor.git] / _source / plugins / blockquote / plugin.js
1 /*\r
2 Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved.\r
3 For licensing, see LICENSE.html or http://ckeditor.com/license\r
4 */\r
5 \r
6 /**\r
7  * @file Blockquote.\r
8  */\r
9 \r
10 (function()\r
11 {\r
12         function getState( editor, path )\r
13         {\r
14                 var firstBlock = path.block || path.blockLimit;\r
15 \r
16                 if ( !firstBlock || firstBlock.getName() == 'body' )\r
17                         return CKEDITOR.TRISTATE_OFF;\r
18 \r
19                 // See if the first block has a blockquote parent.\r
20                 if ( firstBlock.getAscendant( 'blockquote', true ) )\r
21                         return CKEDITOR.TRISTATE_ON;\r
22 \r
23                 return CKEDITOR.TRISTATE_OFF;\r
24         }\r
25 \r
26         function onSelectionChange( evt )\r
27         {\r
28                 var editor = evt.editor;\r
29                 if ( editor.readOnly )\r
30                         return;\r
31 \r
32                 var command = editor.getCommand( 'blockquote' );\r
33                 command.state = getState( editor, evt.data.path );\r
34                 command.fire( 'state' );\r
35         }\r
36 \r
37         function noBlockLeft( bqBlock )\r
38         {\r
39                 for ( var i = 0, length = bqBlock.getChildCount(), child ; i < length && ( child = bqBlock.getChild( i ) ) ; i++ )\r
40                 {\r
41                         if ( child.type == CKEDITOR.NODE_ELEMENT && child.isBlockBoundary() )\r
42                                 return false;\r
43                 }\r
44                 return true;\r
45         }\r
46 \r
47         var commandObject =\r
48         {\r
49                 exec : function( editor )\r
50                 {\r
51                         var state = editor.getCommand( 'blockquote' ).state,\r
52                                 selection = editor.getSelection(),\r
53                                 range = selection && selection.getRanges( true )[0];\r
54 \r
55                         if ( !range )\r
56                                 return;\r
57 \r
58                         var bookmarks = selection.createBookmarks();\r
59 \r
60                         // Kludge for #1592: if the bookmark nodes are in the beginning of\r
61                         // blockquote, then move them to the nearest block element in the\r
62                         // blockquote.\r
63                         if ( CKEDITOR.env.ie )\r
64                         {\r
65                                 var bookmarkStart = bookmarks[0].startNode,\r
66                                         bookmarkEnd = bookmarks[0].endNode,\r
67                                         cursor;\r
68 \r
69                                 if ( bookmarkStart && bookmarkStart.getParent().getName() == 'blockquote' )\r
70                                 {\r
71                                         cursor = bookmarkStart;\r
72                                         while ( ( cursor = cursor.getNext() ) )\r
73                                         {\r
74                                                 if ( cursor.type == CKEDITOR.NODE_ELEMENT &&\r
75                                                                 cursor.isBlockBoundary() )\r
76                                                 {\r
77                                                         bookmarkStart.move( cursor, true );\r
78                                                         break;\r
79                                                 }\r
80                                         }\r
81                                 }\r
82 \r
83                                 if ( bookmarkEnd\r
84                                                 && bookmarkEnd.getParent().getName() == 'blockquote' )\r
85                                 {\r
86                                         cursor = bookmarkEnd;\r
87                                         while ( ( cursor = cursor.getPrevious() ) )\r
88                                         {\r
89                                                 if ( cursor.type == CKEDITOR.NODE_ELEMENT &&\r
90                                                                 cursor.isBlockBoundary() )\r
91                                                 {\r
92                                                         bookmarkEnd.move( cursor );\r
93                                                         break;\r
94                                                 }\r
95                                         }\r
96                                 }\r
97                         }\r
98 \r
99                         var iterator = range.createIterator(),\r
100                                 block;\r
101                         iterator.enlargeBr = editor.config.enterMode != CKEDITOR.ENTER_BR;\r
102 \r
103                         if ( state == CKEDITOR.TRISTATE_OFF )\r
104                         {\r
105                                 var paragraphs = [];\r
106                                 while ( ( block = iterator.getNextParagraph() ) )\r
107                                         paragraphs.push( block );\r
108 \r
109                                 // If no paragraphs, create one from the current selection position.\r
110                                 if ( paragraphs.length < 1 )\r
111                                 {\r
112                                         var para = editor.document.createElement( editor.config.enterMode == CKEDITOR.ENTER_P ? 'p' : 'div' ),\r
113                                                 firstBookmark = bookmarks.shift();\r
114                                         range.insertNode( para );\r
115                                         para.append( new CKEDITOR.dom.text( '\ufeff', editor.document ) );\r
116                                         range.moveToBookmark( firstBookmark );\r
117                                         range.selectNodeContents( para );\r
118                                         range.collapse( true );\r
119                                         firstBookmark = range.createBookmark();\r
120                                         paragraphs.push( para );\r
121                                         bookmarks.unshift( firstBookmark );\r
122                                 }\r
123 \r
124                                 // Make sure all paragraphs have the same parent.\r
125                                 var commonParent = paragraphs[0].getParent(),\r
126                                         tmp = [];\r
127                                 for ( var i = 0 ; i < paragraphs.length ; i++ )\r
128                                 {\r
129                                         block = paragraphs[i];\r
130                                         commonParent = commonParent.getCommonAncestor( block.getParent() );\r
131                                 }\r
132 \r
133                                 // The common parent must not be the following tags: table, tbody, tr, ol, ul.\r
134                                 var denyTags = { table : 1, tbody : 1, tr : 1, ol : 1, ul : 1 };\r
135                                 while ( denyTags[ commonParent.getName() ] )\r
136                                         commonParent = commonParent.getParent();\r
137 \r
138                                 // Reconstruct the block list to be processed such that all resulting blocks\r
139                                 // satisfy parentNode.equals( commonParent ).\r
140                                 var lastBlock = null;\r
141                                 while ( paragraphs.length > 0 )\r
142                                 {\r
143                                         block = paragraphs.shift();\r
144                                         while ( !block.getParent().equals( commonParent ) )\r
145                                                 block = block.getParent();\r
146                                         if ( !block.equals( lastBlock ) )\r
147                                                 tmp.push( block );\r
148                                         lastBlock = block;\r
149                                 }\r
150 \r
151                                 // If any of the selected blocks is a blockquote, remove it to prevent\r
152                                 // nested blockquotes.\r
153                                 while ( tmp.length > 0 )\r
154                                 {\r
155                                         block = tmp.shift();\r
156                                         if ( block.getName() == 'blockquote' )\r
157                                         {\r
158                                                 var docFrag = new CKEDITOR.dom.documentFragment( editor.document );\r
159                                                 while ( block.getFirst() )\r
160                                                 {\r
161                                                         docFrag.append( block.getFirst().remove() );\r
162                                                         paragraphs.push( docFrag.getLast() );\r
163                                                 }\r
164 \r
165                                                 docFrag.replace( block );\r
166                                         }\r
167                                         else\r
168                                                 paragraphs.push( block );\r
169                                 }\r
170 \r
171                                 // Now we have all the blocks to be included in a new blockquote node.\r
172                                 var bqBlock = editor.document.createElement( 'blockquote' );\r
173                                 bqBlock.insertBefore( paragraphs[0] );\r
174                                 while ( paragraphs.length > 0 )\r
175                                 {\r
176                                         block = paragraphs.shift();\r
177                                         bqBlock.append( block );\r
178                                 }\r
179                         }\r
180                         else if ( state == CKEDITOR.TRISTATE_ON )\r
181                         {\r
182                                 var moveOutNodes = [],\r
183                                         database = {};\r
184 \r
185                                 while ( ( block = iterator.getNextParagraph() ) )\r
186                                 {\r
187                                         var bqParent = null,\r
188                                                 bqChild = null;\r
189                                         while ( block.getParent() )\r
190                                         {\r
191                                                 if ( block.getParent().getName() == 'blockquote' )\r
192                                                 {\r
193                                                         bqParent = block.getParent();\r
194                                                         bqChild = block;\r
195                                                         break;\r
196                                                 }\r
197                                                 block = block.getParent();\r
198                                         }\r
199 \r
200                                         // Remember the blocks that were recorded down in the moveOutNodes array\r
201                                         // to prevent duplicates.\r
202                                         if ( bqParent && bqChild && !bqChild.getCustomData( 'blockquote_moveout' ) )\r
203                                         {\r
204                                                 moveOutNodes.push( bqChild );\r
205                                                 CKEDITOR.dom.element.setMarker( database, bqChild, 'blockquote_moveout', true );\r
206                                         }\r
207                                 }\r
208 \r
209                                 CKEDITOR.dom.element.clearAllMarkers( database );\r
210 \r
211                                 var movedNodes = [],\r
212                                         processedBlockquoteBlocks = [];\r
213 \r
214                                 database = {};\r
215                                 while ( moveOutNodes.length > 0 )\r
216                                 {\r
217                                         var node = moveOutNodes.shift();\r
218                                         bqBlock = node.getParent();\r
219 \r
220                                         // If the node is located at the beginning or the end, just take it out\r
221                                         // without splitting. Otherwise, split the blockquote node and move the\r
222                                         // paragraph in between the two blockquote nodes.\r
223                                         if ( !node.getPrevious() )\r
224                                                 node.remove().insertBefore( bqBlock );\r
225                                         else if ( !node.getNext() )\r
226                                                 node.remove().insertAfter( bqBlock );\r
227                                         else\r
228                                         {\r
229                                                 node.breakParent( node.getParent() );\r
230                                                 processedBlockquoteBlocks.push( node.getNext() );\r
231                                         }\r
232 \r
233                                         // Remember the blockquote node so we can clear it later (if it becomes empty).\r
234                                         if ( !bqBlock.getCustomData( 'blockquote_processed' ) )\r
235                                         {\r
236                                                 processedBlockquoteBlocks.push( bqBlock );\r
237                                                 CKEDITOR.dom.element.setMarker( database, bqBlock, 'blockquote_processed', true );\r
238                                         }\r
239 \r
240                                         movedNodes.push( node );\r
241                                 }\r
242 \r
243                                 CKEDITOR.dom.element.clearAllMarkers( database );\r
244 \r
245                                 // Clear blockquote nodes that have become empty.\r
246                                 for ( i = processedBlockquoteBlocks.length - 1 ; i >= 0 ; i-- )\r
247                                 {\r
248                                         bqBlock = processedBlockquoteBlocks[i];\r
249                                         if ( noBlockLeft( bqBlock ) )\r
250                                                 bqBlock.remove();\r
251                                 }\r
252 \r
253                                 if ( editor.config.enterMode == CKEDITOR.ENTER_BR )\r
254                                 {\r
255                                         var firstTime = true;\r
256                                         while ( movedNodes.length )\r
257                                         {\r
258                                                 node = movedNodes.shift();\r
259 \r
260                                                 if ( node.getName() == 'div' )\r
261                                                 {\r
262                                                         docFrag = new CKEDITOR.dom.documentFragment( editor.document );\r
263                                                         var needBeginBr = firstTime && node.getPrevious() &&\r
264                                                                         !( node.getPrevious().type == CKEDITOR.NODE_ELEMENT && node.getPrevious().isBlockBoundary() );\r
265                                                         if ( needBeginBr )\r
266                                                                 docFrag.append( editor.document.createElement( 'br' ) );\r
267 \r
268                                                         var needEndBr = node.getNext() &&\r
269                                                                 !( node.getNext().type == CKEDITOR.NODE_ELEMENT && node.getNext().isBlockBoundary() );\r
270                                                         while ( node.getFirst() )\r
271                                                                 node.getFirst().remove().appendTo( docFrag );\r
272 \r
273                                                         if ( needEndBr )\r
274                                                                 docFrag.append( editor.document.createElement( 'br' ) );\r
275 \r
276                                                         docFrag.replace( node );\r
277                                                         firstTime = false;\r
278                                                 }\r
279                                         }\r
280                                 }\r
281                         }\r
282 \r
283                         selection.selectBookmarks( bookmarks );\r
284                         editor.focus();\r
285                 }\r
286         };\r
287 \r
288         CKEDITOR.plugins.add( 'blockquote',\r
289         {\r
290                 init : function( editor )\r
291                 {\r
292                         editor.addCommand( 'blockquote', commandObject );\r
293 \r
294                         editor.ui.addButton( 'Blockquote',\r
295                                 {\r
296                                         label : editor.lang.blockquote,\r
297                                         command : 'blockquote'\r
298                                 } );\r
299 \r
300                         editor.on( 'selectionChange', onSelectionChange );\r
301                 },\r
302 \r
303                 requires : [ 'domiterator' ]\r
304         } );\r
305 })();\r