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