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