JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
vanilla ckeditor-3.6
[ckeditor.git] / _source / plugins / bbcode / 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 (function()\r
7 {\r
8         CKEDITOR.on( 'dialogDefinition', function( ev )\r
9         {\r
10                 var tab, name = ev.data.name,\r
11                         definition = ev.data.definition;\r
12 \r
13                 if ( name == 'link' )\r
14                 {\r
15                         definition.removeContents( 'target' );\r
16                         definition.removeContents( 'upload' );\r
17                         definition.removeContents( 'advanced' );\r
18                         tab = definition.getContents( 'info' );\r
19                         tab.remove( 'emailSubject' );\r
20                         tab.remove( 'emailBody' );\r
21                 }\r
22                 else if ( name == 'image' )\r
23                 {\r
24                         definition.removeContents( 'advanced' );\r
25                         tab = definition.getContents( 'Link' );\r
26                         tab.remove( 'cmbTarget' );\r
27                         tab = definition.getContents( 'info' );\r
28                         tab.remove( 'txtAlt' );\r
29                         tab.remove( 'basic' );\r
30                 }\r
31         });\r
32 \r
33         var bbcodeMap = { 'b' : 'strong', 'u': 'u', 'i' : 'em', 'color' : 'span', 'size' : 'span', 'quote' : 'blockquote', 'code' : 'code', 'url' : 'a', 'email' : 'span', 'img' : 'span', '*' : 'li', 'list' : 'ol' },\r
34                         convertMap = { 'strong' : 'b' , 'b' : 'b', 'u': 'u', 'em' : 'i', 'i': 'i', 'code' : 'code', 'li' : '*' },\r
35                         tagnameMap = { 'strong' : 'b', 'em' : 'i', 'u' : 'u', 'li' : '*', 'ul' : 'list', 'ol' : 'list', 'code' : 'code', 'a' : 'link', 'img' : 'img', 'blockquote' : 'quote' },\r
36                         stylesMap = { 'color' : 'color', 'size' : 'font-size' },\r
37                         attributesMap = { 'url' : 'href', 'email' : 'mailhref', 'quote': 'cite', 'list' : 'listType' };\r
38 \r
39         // List of block-like tags.\r
40         var dtd =  CKEDITOR.dtd,\r
41                 blockLikeTags = CKEDITOR.tools.extend( { table:1 }, dtd.$block, dtd.$listItem, dtd.$tableContent, dtd.$list );\r
42 \r
43         var semicolonFixRegex = /\s*(?:;\s*|$)/;\r
44         function serializeStyleText( stylesObject )\r
45         {\r
46                 var styleText = '';\r
47                 for ( var style in stylesObject )\r
48                 {\r
49                         var styleVal = stylesObject[ style ],\r
50                                 text = ( style + ':' + styleVal ).replace( semicolonFixRegex, ';' );\r
51 \r
52                         styleText += text;\r
53                 }\r
54                 return styleText;\r
55         }\r
56 \r
57         function parseStyleText( styleText )\r
58         {\r
59                 var retval = {};\r
60                 ( styleText || '' )\r
61                                 .replace( /"/g, '"' )\r
62                                 .replace( /\s*([^ :;]+)\s*:\s*([^;]+)\s*(?=;|$)/g, function( match, name, value )\r
63                 {\r
64                         retval[ name.toLowerCase() ] = value;\r
65                 } );\r
66                 return retval;\r
67         }\r
68 \r
69         function RGBToHex( cssStyle )\r
70         {\r
71                 return cssStyle.replace( /(?:rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\))/gi, function( match, red, green, blue )\r
72                         {\r
73                                 red = parseInt( red, 10 ).toString( 16 );\r
74                                 green = parseInt( green, 10 ).toString( 16 );\r
75                                 blue = parseInt( blue, 10 ).toString( 16 );\r
76                                 var color = [red, green, blue] ;\r
77 \r
78                                 // Add padding zeros if the hex value is less than 0x10.\r
79                                 for ( var i = 0 ; i < color.length ; i++ )\r
80                                         color[i] = String( '0' + color[i] ).slice( -2 ) ;\r
81 \r
82                                 return '#' + color.join( '' ) ;\r
83                          });\r
84         }\r
85 \r
86         // Maintain the map of smiley-to-description.\r
87         var smileyMap = {"smiley":":)","sad":":(","wink":";)","laugh":":D","cheeky":":P","blush":":*)","surprise":":-o","indecision":":|","angry":">:(","angel":"o:)","cool":"8-)","devil":">:-)","crying":";(","kiss":":-*" },\r
88                 smileyReverseMap = {},\r
89                 smileyRegExp = [];\r
90 \r
91         // Build regexp for the list of smiley text.\r
92         for ( var i in smileyMap )\r
93         {\r
94                 smileyReverseMap[ smileyMap[ i ] ] = i;\r
95                 smileyRegExp.push( smileyMap[ i ].replace( /\(|\)|\:|\/|\*|\-|\|/g, function( match ) { return '\\' + match; } ) );\r
96         }\r
97 \r
98         smileyRegExp = new RegExp( smileyRegExp.join( '|' ), 'g' );\r
99 \r
100         var decodeHtml = ( function ()\r
101         {\r
102                 var regex = [],\r
103                         entities =\r
104                         {\r
105                                 nbsp    : '\u00A0',             // IE | FF\r
106                                 shy             : '\u00AD',             // IE\r
107                                 gt              : '\u003E',             // IE | FF |   --   | Opera\r
108                                 lt              : '\u003C'              // IE | FF | Safari | Opera\r
109                         };\r
110 \r
111                 for ( var entity in entities )\r
112                         regex.push( entity );\r
113 \r
114                 regex = new RegExp( '&(' + regex.join( '|' ) + ');', 'g' );\r
115 \r
116                 return function( html )\r
117                 {\r
118                         return html.replace( regex, function( match, entity )\r
119                         {\r
120                                 return entities[ entity ];\r
121                         });\r
122                 };\r
123         })();\r
124 \r
125         CKEDITOR.BBCodeParser = function()\r
126         {\r
127                 this._ =\r
128                 {\r
129                         bbcPartsRegex : /(?:\[([^\/\]=]*?)(?:=([^\]]*?))?\])|(?:\[\/([a-z]{1,16})\])/ig\r
130                 };\r
131         };\r
132 \r
133         CKEDITOR.BBCodeParser.prototype =\r
134         {\r
135                 parse : function( bbcode )\r
136                 {\r
137                         var parts,\r
138                                         part,\r
139                                         lastIndex = 0;\r
140 \r
141                         while ( ( parts = this._.bbcPartsRegex.exec( bbcode ) ) )\r
142                         {\r
143                                 var tagIndex = parts.index;\r
144                                 if ( tagIndex > lastIndex )\r
145                                 {\r
146                                         var text = bbcode.substring( lastIndex, tagIndex );\r
147                                         this.onText( text, 1 );\r
148                                 }\r
149 \r
150                                 lastIndex = this._.bbcPartsRegex.lastIndex;\r
151 \r
152                                 /*\r
153                                  "parts" is an array with the following items:\r
154                                  0 : The entire match for opening/closing tags and line-break;\r
155                                  1 : line-break;\r
156                                  2 : open of tag excludes option;\r
157                                  3 : tag option;\r
158                                  4 : close of tag;\r
159                                  */\r
160 \r
161                                 // Opening tag\r
162                                 if ( ( part = parts[ 1 ] ) )\r
163                                 {\r
164                                         part = part.toLowerCase();\r
165 \r
166                                         var tagName = bbcodeMap[ part ],\r
167                                                         attribs = {},\r
168                                                         styles = {},\r
169                                                         optionPart = parts[ 2 ];\r
170 \r
171                                         if ( optionPart )\r
172                                         {\r
173                                                 if ( part == 'list' )\r
174                                                 {\r
175                                                         if ( !isNaN( optionPart ) )\r
176                                                                 optionPart = 'decimal';\r
177                                                         else if ( /^[a-z]+$/.test( optionPart ) )\r
178                                                                 optionPart = 'lower-alpha';\r
179                                                         else if ( /^[A-Z]+$/.test( optionPart ) )\r
180                                                                 optionPart = 'upper-alpha';\r
181                                                 }\r
182 \r
183                                                 if ( stylesMap[ part ] )\r
184                                                 {\r
185                                                         // Font size represents percentage.\r
186                                                         if ( part == 'size' )\r
187                                                                 optionPart += '%';\r
188 \r
189                                                         styles[ stylesMap[ part ] ] = optionPart;\r
190                                                         attribs.style = serializeStyleText( styles );\r
191                                                 }\r
192                                                 else if ( attributesMap[ part ] )\r
193                                                         attribs[ attributesMap[ part ] ] = optionPart;\r
194                                         }\r
195 \r
196                                         // Two special handling - image and email, protect them\r
197                                         // as "span" with an attribute marker.\r
198                                         if ( part == 'email' || part == 'img' )\r
199                                                 attribs[ 'bbcode' ] = part;\r
200 \r
201                                         this.onTagOpen( tagName, attribs, CKEDITOR.dtd.$empty[ tagName ] );\r
202                                 }\r
203                                 // Closing tag\r
204                                 else if ( ( part = parts[ 3 ] ) )\r
205                                         this.onTagClose( bbcodeMap[ part ] );\r
206                         }\r
207 \r
208                         if ( bbcode.length > lastIndex )\r
209                                 this.onText( bbcode.substring( lastIndex, bbcode.length ), 1 );\r
210                 }\r
211         };\r
212 \r
213         /**\r
214          * Creates a {@link CKEDITOR.htmlParser.fragment} from an HTML string.\r
215          * @param {String} source The HTML to be parsed, filling the fragment.\r
216          * @param {Number} [fixForBody=false] Wrap body with specified element if needed.\r
217          * @returns CKEDITOR.htmlParser.fragment The fragment created.\r
218          * @example\r
219          * var fragment = CKEDITOR.htmlParser.fragment.fromHtml( '<b>Sample</b> Text' );\r
220          * alert( fragment.children[0].name );  "b"\r
221          * alert( fragment.children[1].value );  " Text"\r
222          */\r
223         CKEDITOR.htmlParser.fragment.fromBBCode = function( source )\r
224         {\r
225                 var parser = new CKEDITOR.BBCodeParser(),\r
226                         fragment = new CKEDITOR.htmlParser.fragment(),\r
227                         pendingInline = [],\r
228                         pendingBrs = 0,\r
229                         currentNode = fragment,\r
230                         returnPoint;\r
231 \r
232                 function checkPending( newTagName )\r
233                 {\r
234                         if ( pendingInline.length > 0 )\r
235                         {\r
236                                 for ( var i = 0 ; i < pendingInline.length ; i++ )\r
237                                 {\r
238                                         var pendingElement = pendingInline[ i ],\r
239                                                 pendingName = pendingElement.name,\r
240                                                 pendingDtd = CKEDITOR.dtd[ pendingName ],\r
241                                                 currentDtd = currentNode.name && CKEDITOR.dtd[ currentNode.name ];\r
242 \r
243                                         if ( ( !currentDtd || currentDtd[ pendingName ] ) && ( !newTagName || !pendingDtd || pendingDtd[ newTagName ] || !CKEDITOR.dtd[ newTagName ] ) )\r
244                                         {\r
245                                                 // Get a clone for the pending element.\r
246                                                 pendingElement = pendingElement.clone();\r
247 \r
248                                                 // Add it to the current node and make it the current,\r
249                                                 // so the new element will be added inside of it.\r
250                                                 pendingElement.parent = currentNode;\r
251                                                 currentNode = pendingElement;\r
252 \r
253                                                 // Remove the pending element (back the index by one\r
254                                                 // to properly process the next entry).\r
255                                                 pendingInline.splice( i, 1 );\r
256                                                 i--;\r
257                                         }\r
258                                 }\r
259                         }\r
260                 }\r
261 \r
262                 function checkPendingBrs( tagName, closing )\r
263                 {\r
264                         var len = currentNode.children.length,\r
265                                 previous = len > 0 && currentNode.children[ len - 1 ],\r
266                                 lineBreakParent = !previous && BBCodeWriter.getRule( tagnameMap[ currentNode.name ], 'breakAfterOpen' ),\r
267                                 lineBreakPrevious = previous && previous.type == CKEDITOR.NODE_ELEMENT && BBCodeWriter.getRule( tagnameMap[ previous.name ], 'breakAfterClose' ),\r
268                                 lineBreakCurrent = tagName && BBCodeWriter.getRule( tagnameMap[ tagName ], closing ? 'breakBeforeClose' : 'breakBeforeOpen' );\r
269 \r
270                         if ( pendingBrs && ( lineBreakParent || lineBreakPrevious || lineBreakCurrent ) )\r
271                                 pendingBrs--;\r
272 \r
273                         // 1. Either we're at the end of block, where it requires us to compensate the br filler\r
274                         // removing logic (from htmldataprocessor).\r
275                         // 2. Or we're at the end of pseudo block, where it requires us to compensate\r
276                         // the bogus br effect.\r
277                         if ( pendingBrs && tagName in blockLikeTags )\r
278                                 pendingBrs++;\r
279 \r
280                         while ( pendingBrs && pendingBrs-- )\r
281                                 currentNode.children.push( previous = new CKEDITOR.htmlParser.element( 'br' ) );\r
282                 }\r
283 \r
284                 function addElement( node, target )\r
285                 {\r
286                         checkPendingBrs( node.name, 1 );\r
287 \r
288                         target = target || currentNode || fragment;\r
289 \r
290                         var len = target.children.length,\r
291                                 previous = len > 0 && target.children[ len - 1 ] || null;\r
292 \r
293                         node.previous = previous;\r
294                         node.parent = target;\r
295 \r
296                         target.children.push( node );\r
297 \r
298                         if ( node.returnPoint )\r
299                         {\r
300                                 currentNode = node.returnPoint;\r
301                                 delete node.returnPoint;\r
302                         }\r
303                 }\r
304 \r
305                 parser.onTagOpen = function( tagName, attributes, selfClosing )\r
306                 {\r
307                         var element = new CKEDITOR.htmlParser.element( tagName, attributes );\r
308 \r
309                         // This is a tag to be removed if empty, so do not add it immediately.\r
310                         if ( CKEDITOR.dtd.$removeEmpty[ tagName ] )\r
311                         {\r
312                                 pendingInline.push( element );\r
313                                 return;\r
314                         }\r
315 \r
316                         var currentName = currentNode.name;\r
317 \r
318                         var currentDtd = currentName\r
319                                 && ( CKEDITOR.dtd[ currentName ]\r
320                                         || ( currentNode._.isBlockLike ? CKEDITOR.dtd.div : CKEDITOR.dtd.span ) );\r
321 \r
322                         // If the element cannot be child of the current element.\r
323                         if ( currentDtd && !currentDtd[ tagName ] )\r
324                         {\r
325                                 var reApply = false,\r
326                                         addPoint;   // New position to start adding nodes.\r
327 \r
328                                 // If the element name is the same as the current element name,\r
329                                 // then just close the current one and append the new one to the\r
330                                 // parent. This situation usually happens with <p>, <li>, <dt> and\r
331                                 // <dd>, specially in IE. Do not enter in this if block in this case.\r
332                                 if ( tagName == currentName )\r
333                                         addElement( currentNode, currentNode.parent );\r
334                                 else if ( tagName in CKEDITOR.dtd.$listItem )\r
335                                 {\r
336                                         parser.onTagOpen( 'ul', {} );\r
337                                         addPoint = currentNode;\r
338                                         reApply = true;\r
339                                 }\r
340                                 else\r
341                                 {\r
342                                         addElement( currentNode, currentNode.parent );\r
343 \r
344                                         // The current element is an inline element, which\r
345                                         // cannot hold the new one. Put it in the pending list,\r
346                                         // and try adding the new one after it.\r
347                                         pendingInline.unshift( currentNode );\r
348                                         reApply = true;\r
349                                 }\r
350 \r
351                                 if ( addPoint )\r
352                                         currentNode = addPoint;\r
353                                 // Try adding it to the return point, or the parent element.\r
354                                 else\r
355                                         currentNode = currentNode.returnPoint || currentNode.parent;\r
356 \r
357                                 if ( reApply )\r
358                                 {\r
359                                         parser.onTagOpen.apply( this, arguments );\r
360                                         return;\r
361                                 }\r
362                         }\r
363 \r
364                         checkPending( tagName );\r
365                         checkPendingBrs( tagName );\r
366 \r
367                         element.parent = currentNode;\r
368                         element.returnPoint = returnPoint;\r
369                         returnPoint = 0;\r
370 \r
371                         if ( element.isEmpty )\r
372                                 addElement( element );\r
373                         else\r
374                                 currentNode = element;\r
375                 };\r
376 \r
377                 parser.onTagClose = function( tagName )\r
378                 {\r
379                         // Check if there is any pending tag to be closed.\r
380                         for ( var i = pendingInline.length - 1 ; i >= 0 ; i-- )\r
381                         {\r
382                                 // If found, just remove it from the list.\r
383                                 if ( tagName == pendingInline[ i ].name )\r
384                                 {\r
385                                         pendingInline.splice( i, 1 );\r
386                                         return;\r
387                                 }\r
388                         }\r
389 \r
390                         var pendingAdd = [],\r
391                                 newPendingInline = [],\r
392                                 candidate = currentNode;\r
393 \r
394                         while ( candidate.type && candidate.name != tagName )\r
395                         {\r
396                                 // If this is an inline element, add it to the pending list, if we're\r
397                                 // really closing one of the parents element later, they will continue\r
398                                 // after it.\r
399                                 if ( !candidate._.isBlockLike )\r
400                                         newPendingInline.unshift( candidate );\r
401 \r
402                                 // This node should be added to it's parent at this point. But,\r
403                                 // it should happen only if the closing tag is really closing\r
404                                 // one of the nodes. So, for now, we just cache it.\r
405                                 pendingAdd.push( candidate );\r
406 \r
407                                 candidate = candidate.parent;\r
408                         }\r
409 \r
410                         if ( candidate.type )\r
411                         {\r
412                                 // Add all elements that have been found in the above loop.\r
413                                 for ( i = 0 ; i < pendingAdd.length ; i++ )\r
414                                 {\r
415                                         var node = pendingAdd[ i ];\r
416                                         addElement( node, node.parent );\r
417                                 }\r
418 \r
419                                 currentNode = candidate;\r
420 \r
421 \r
422                                 addElement( candidate, candidate.parent );\r
423 \r
424                                 // The parent should start receiving new nodes now, except if\r
425                                 // addElement changed the currentNode.\r
426                                 if ( candidate == currentNode )\r
427                                         currentNode = currentNode.parent;\r
428 \r
429                                 pendingInline = pendingInline.concat( newPendingInline );\r
430                         }\r
431                 };\r
432 \r
433                 parser.onText = function( text )\r
434                 {\r
435                         var currentDtd = CKEDITOR.dtd[ currentNode.name ];\r
436                         if ( !currentDtd || currentDtd[ '#' ] )\r
437                         {\r
438                                 checkPendingBrs();\r
439                                 checkPending();\r
440 \r
441                                 text.replace(/([\r\n])|[^\r\n]*/g, function( piece, lineBreak )\r
442                                 {\r
443                                         if ( lineBreak !== undefined && lineBreak.length )\r
444                                                 pendingBrs++;\r
445                                         else if ( piece.length )\r
446                                         {\r
447                                                 var lastIndex = 0;\r
448 \r
449                                                 // Create smiley from text emotion.\r
450                                                 piece.replace( smileyRegExp, function( match, index )\r
451                                                 {\r
452                                                         addElement( new CKEDITOR.htmlParser.text( piece.substring( lastIndex, index ) ), currentNode );\r
453                                                         addElement( new CKEDITOR.htmlParser.element( 'smiley', { 'desc': smileyReverseMap[ match ] } ), currentNode );\r
454                                                         lastIndex = index + match.length;\r
455                                                 });\r
456 \r
457                                                 if ( lastIndex != piece.length )\r
458                                                         addElement( new CKEDITOR.htmlParser.text( piece.substring( lastIndex, piece.length ) ), currentNode );\r
459                                         }\r
460                                 });\r
461                         }\r
462                 };\r
463 \r
464                 // Parse it.\r
465                 parser.parse( CKEDITOR.tools.htmlEncode( source ) );\r
466 \r
467                 // Close all hanging nodes.\r
468                 while ( currentNode.type )\r
469                 {\r
470                         var parent = currentNode.parent,\r
471                                 node = currentNode;\r
472 \r
473                         addElement( node, parent );\r
474                         currentNode = parent;\r
475                 }\r
476 \r
477                 return fragment;\r
478         };\r
479 \r
480         CKEDITOR.htmlParser.BBCodeWriter = CKEDITOR.tools.createClass(\r
481         {\r
482                 $ : function()\r
483                 {\r
484                         this._ =\r
485                         {\r
486                                 output : [],\r
487                                 rules : []\r
488                         };\r
489 \r
490                         // List and list item.\r
491                         this.setRules( 'list',\r
492                    {\r
493                            breakBeforeOpen : 1,\r
494                            breakAfterOpen : 1,\r
495                            breakBeforeClose : 1,\r
496                            breakAfterClose : 1\r
497                    } );\r
498 \r
499                         this.setRules( '*',\r
500                    {\r
501                            breakBeforeOpen : 1,\r
502                            breakAfterOpen : 0,\r
503                            breakBeforeClose : 1,\r
504                            breakAfterClose : 0\r
505                    } );\r
506 \r
507                         this.setRules( 'quote',\r
508                    {\r
509                            breakBeforeOpen : 1,\r
510                            breakAfterOpen : 0,\r
511                            breakBeforeClose : 0,\r
512                            breakAfterClose : 1\r
513                    } );\r
514                 },\r
515 \r
516                 proto :\r
517                 {\r
518                         /**\r
519                          * Sets formatting rules for a given tag. The possible rules are:\r
520                          * <ul>\r
521                          *      <li><b>breakBeforeOpen</b>: break line before the opener tag for this element.</li>\r
522                          *      <li><b>breakAfterOpen</b>: break line after the opener tag for this element.</li>\r
523                          *      <li><b>breakBeforeClose</b>: break line before the closer tag for this element.</li>\r
524                          *      <li><b>breakAfterClose</b>: break line after the closer tag for this element.</li>\r
525                          * </ul>\r
526                          *\r
527                          * All rules default to "false". Each call to the function overrides\r
528                          * already present rules, leaving the undefined untouched.\r
529                          *\r
530                          * @param {String} tagName The tag name to which set the rules.\r
531                          * @param {Object} rules An object containing the element rules.\r
532                          * @example\r
533                          * // Break line before and after "img" tags.\r
534                          * writer.setRules( 'list',\r
535                          *     {\r
536                          *         breakBeforeOpen : true\r
537                          *         breakAfterOpen : true\r
538                          *     });\r
539                          */\r
540                         setRules : function( tagName, rules )\r
541                         {\r
542                                 var currentRules = this._.rules[ tagName ];\r
543 \r
544                                 if ( currentRules )\r
545                                         CKEDITOR.tools.extend( currentRules, rules, true );\r
546                                 else\r
547                                         this._.rules[ tagName ] = rules;\r
548                         },\r
549 \r
550                         getRule : function( tagName, ruleName )\r
551                         {\r
552                                 return this._.rules[ tagName ] && this._.rules[ tagName ][ ruleName ];\r
553                         },\r
554 \r
555                         openTag : function( tag, attributes )\r
556                         {\r
557                                 if ( tag in bbcodeMap )\r
558                                 {\r
559                                         if ( this.getRule( tag, 'breakBeforeOpen' ) )\r
560                                                 this.lineBreak( 1 );\r
561 \r
562                                         this.write( '[', tag );\r
563                                         var option = attributes.option;\r
564                                         option && this.write( '=', option );\r
565                                         this.write( ']' );\r
566 \r
567                                         if ( this.getRule( tag, 'breakAfterOpen' ) )\r
568                                                 this.lineBreak( 1 );\r
569                                 }\r
570                                 else if ( tag == 'br' )\r
571                                         this._.output.push( '\n' );\r
572                         },\r
573 \r
574                         openTagClose : function() { },\r
575                         attribute : function() { },\r
576 \r
577                         closeTag : function( tag )\r
578                         {\r
579                                 if ( tag in bbcodeMap )\r
580                                 {\r
581                                         if ( this.getRule( tag, 'breakBeforeClose' ) )\r
582                                                 this.lineBreak( 1 );\r
583 \r
584                                         tag != '*' && this.write( '[/', tag, ']' );\r
585 \r
586                                         if ( this.getRule( tag, 'breakAfterClose' ) )\r
587                                                 this.lineBreak( 1 );\r
588                                 }\r
589                         },\r
590 \r
591                         text : function( text )\r
592                         {\r
593                                 this.write( text );\r
594                         },\r
595 \r
596                         /**\r
597                          * Writes a comment.\r
598                          * @param {String} comment The comment text.\r
599                          * @example\r
600                          * // Writes "&lt;!-- My comment --&gt;".\r
601                          * writer.comment( ' My comment ' );\r
602                          */\r
603                         comment : function() {},\r
604 \r
605                         /*\r
606                         * Output line-break for formatting.\r
607                          */\r
608                         lineBreak : function()\r
609                         {\r
610                                 // Avoid line break when:\r
611                                 // 1) Previous tag already put one.\r
612                                 // 2) We're at output start.\r
613                                 if ( !this._.hasLineBreak && this._.output.length )\r
614                                 {\r
615                                         this.write( '\n' );\r
616                                         this._.hasLineBreak = 1;\r
617                                 }\r
618                         },\r
619 \r
620                         write : function()\r
621                         {\r
622                                 this._.hasLineBreak = 0;\r
623                                 var data = Array.prototype.join.call( arguments, '' );\r
624                                 this._.output.push( data );\r
625                         },\r
626 \r
627                         reset : function()\r
628                         {\r
629                                 this._.output = [];\r
630                                 this._.hasLineBreak = 0;\r
631                         },\r
632 \r
633                         getHtml : function( reset )\r
634                         {\r
635                                 var bbcode = this._.output.join( '' );\r
636 \r
637                                 if ( reset )\r
638                                         this.reset();\r
639 \r
640                                 return decodeHtml ( bbcode );\r
641                         }\r
642                 }\r
643         });\r
644 \r
645         var BBCodeWriter = new CKEDITOR.htmlParser.BBCodeWriter();\r
646 \r
647         CKEDITOR.plugins.add( 'bbcode',\r
648           {\r
649                   requires : [ 'htmldataprocessor', 'entities' ],\r
650                   beforeInit : function( editor )\r
651                   {\r
652                           // Adapt some critical editor configuration for better support\r
653                           // of BBCode environment.\r
654                           var config = editor.config;\r
655                           CKEDITOR.tools.extend( config,\r
656                           {\r
657                                   enterMode : CKEDITOR.ENTER_BR,\r
658                                   basicEntities: false,\r
659                                   entities : false,\r
660                                   fillEmptyBlocks : false\r
661                           }, true );\r
662                   },\r
663                   init : function( editor )\r
664                   {\r
665                           var config = editor.config;\r
666 \r
667                           function BBCodeToHtml( code )\r
668                           {\r
669                                   var fragment = CKEDITOR.htmlParser.fragment.fromBBCode( code ),\r
670                                                   writer = new CKEDITOR.htmlParser.basicWriter();\r
671 \r
672                                   fragment.writeHtml( writer, dataFilter );\r
673                                   return writer.getHtml( true );\r
674                           }\r
675 \r
676                           var dataFilter = new CKEDITOR.htmlParser.filter();\r
677                           dataFilter.addRules(\r
678                           {\r
679                                   elements :\r
680                                   {\r
681                                           'blockquote' : function( element )\r
682                                           {\r
683                                                   var quoted = new CKEDITOR.htmlParser.element( 'div' );\r
684                                                   quoted.children = element.children;\r
685                                                   element.children = [ quoted ];\r
686                                                   var citeText = element.attributes.cite;\r
687                                                   if ( citeText )\r
688                                                   {\r
689                                                           var cite = new CKEDITOR.htmlParser.element( 'cite' );\r
690                                                           cite.add( new CKEDITOR.htmlParser.text( citeText.replace( /^"|"$/g, '' ) ) );\r
691                                                           delete element.attributes.cite;\r
692                                                           element.children.unshift( cite );\r
693                                                   }\r
694                                           },\r
695                                           'span' : function( element )\r
696                                           {\r
697                                                   var bbcode;\r
698                                                   if ( ( bbcode = element.attributes.bbcode ) )\r
699                                                   {\r
700                                                           if ( bbcode == 'img' )\r
701                                                           {\r
702                                                                   element.name = 'img';\r
703                                                                   element.attributes.src = element.children[ 0 ].value;\r
704                                                                   element.children = [];\r
705                                                           }\r
706                                                           else if ( bbcode == 'email' )\r
707                                                           {\r
708                                                                   element.name = 'a';\r
709                                                                   element.attributes.href = 'mailto:' + element.children[ 0 ].value;\r
710                                                           }\r
711 \r
712                                                           delete element.attributes.bbcode;\r
713                                                   }\r
714                                           },\r
715                                           'ol' : function ( element )\r
716                                           {\r
717                                                   if ( element.attributes.listType )\r
718                                                   {\r
719                                                           if ( element.attributes.listType != 'decimal' )\r
720                                                                   element.attributes.style = 'list-style-type:' + element.attributes.listType;\r
721                                                   }\r
722                                                   else\r
723                                                           element.name = 'ul';\r
724 \r
725                                                   delete element.attributes.listType;\r
726                                           },\r
727                                           a : function( element )\r
728                                           {\r
729                                                   if ( !element.attributes.href )\r
730                                                           element.attributes.href = element.children[ 0 ].value;\r
731                                           },\r
732                                           'smiley' : function( element )\r
733                                           {\r
734                                                         element.name = 'img';\r
735 \r
736                                                         var description = element.attributes.desc,\r
737                                                                 image = config.smiley_images[ CKEDITOR.tools.indexOf( config.smiley_descriptions, description ) ],\r
738                                                                 src = CKEDITOR.tools.htmlEncode( config.smiley_path + image );\r
739 \r
740                                                   element.attributes =\r
741                                                   {\r
742                                                           src : src,\r
743                                                           'data-cke-saved-src' : src,\r
744                                                           title :  description,\r
745                                                           alt : description\r
746                                                   };\r
747                                           }\r
748                                   }\r
749                           } );\r
750 \r
751                           editor.dataProcessor.htmlFilter.addRules(\r
752                           {\r
753                                 elements :\r
754                                 {\r
755                                         $ : function( element )\r
756                                         {\r
757                                                 var attributes = element.attributes,\r
758                                                                 style = parseStyleText( attributes.style ),\r
759                                                                 value;\r
760 \r
761                                                 var tagName = element.name;\r
762                                                 if ( tagName in convertMap )\r
763                                                         tagName = convertMap[ tagName ];\r
764                                                 else if ( tagName == 'span' )\r
765                                                 {\r
766                                                         if ( ( value = style.color ) )\r
767                                                         {\r
768                                                                 tagName = 'color';\r
769                                                                 value = RGBToHex( value );\r
770                                                         }\r
771                                                         else if ( ( value = style[ 'font-size' ] ) )\r
772                                                         {\r
773                                                                 var percentValue = value.match( /(\d+)%$/ );\r
774                                                                 if ( percentValue )\r
775                                                                 {\r
776                                                                         value = percentValue[ 1 ];\r
777                                                                         tagName = 'size';\r
778                                                                 }\r
779                                                         }\r
780                                                 }\r
781                                                 else if ( tagName == 'ol' || tagName == 'ul' )\r
782                                                 {\r
783                                                         if ( ( value = style[ 'list-style-type'] ) )\r
784                                                         {\r
785                                                                 switch ( value )\r
786                                                                 {\r
787                                                                         case 'lower-alpha':\r
788                                                                                 value = 'a';\r
789                                                                                 break;\r
790                                                                         case 'upper-alpha':\r
791                                                                                 value = 'A';\r
792                                                                                 break;\r
793                                                                 }\r
794                                                         }\r
795                                                         else if ( tagName == 'ol' )\r
796                                                                 value = 1;\r
797 \r
798                                                         tagName = 'list';\r
799                                                 }\r
800                                                 else if ( tagName == 'blockquote' )\r
801                                                 {\r
802                                                         try\r
803                                                         {\r
804                                                                 var cite = element.children[ 0 ],\r
805                                                                                 quoted = element.children[ 1 ],\r
806                                                                                 citeText = cite.name == 'cite' && cite.children[ 0 ].value;\r
807 \r
808                                                                 if ( citeText )\r
809                                                                 {\r
810                                                                         value = '"' + citeText + '"';\r
811                                                                         element.children = quoted.children;\r
812                                                                 }\r
813 \r
814                                                         }\r
815                                                         catch( er )\r
816                                                         {\r
817                                                         }\r
818 \r
819                                                         tagName = 'quote';\r
820                                                 }\r
821                                                 else if ( tagName == 'a' )\r
822                                                 {\r
823                                                         if ( ( value = attributes.href ) )\r
824                                                         {\r
825                                                                 if ( value.indexOf( 'mailto:' ) !== -1 )\r
826                                                                 {\r
827                                                                         tagName = 'email';\r
828                                                                         // [email] should have a single text child with email address.\r
829                                                                         element.children = [ new CKEDITOR.htmlParser.text( value.replace( 'mailto:', '' ) ) ];\r
830                                                                         value = '';\r
831                                                                 }\r
832                                                                 else\r
833                                                                 {\r
834                                                                         var singleton = element.children.length == 1 && element.children[ 0 ];\r
835                                                                         if ( singleton\r
836                                                                                         && singleton.type == CKEDITOR.NODE_TEXT\r
837                                                                                         && singleton.value == value )\r
838                                                                                 value = '';\r
839 \r
840                                                                         tagName = 'url';\r
841                                                                 }\r
842                                                         }\r
843                                                 }\r
844                                                 else if ( tagName == 'img' )\r
845                                                 {\r
846                                                         element.isEmpty = 0;\r
847 \r
848                                                         // Translate smiley (image) to text emotion.\r
849                                                         var src = attributes[ 'data-cke-saved-src' ];\r
850                                                         if ( src && src.indexOf( editor.config.smiley_path ) != -1 )\r
851                                                                 return new CKEDITOR.htmlParser.text( smileyMap[ attributes.alt ] );\r
852                                                         else\r
853                                                                 element.children = [ new CKEDITOR.htmlParser.text( src ) ];\r
854                                                 }\r
855 \r
856                                                 element.name = tagName;\r
857                                                 value && ( element.attributes.option = value );\r
858 \r
859                                                 return null;\r
860                                         },\r
861 \r
862                                         // Remove any bogus br from the end of a pseudo block,\r
863                                         // e.g. <div>some text<br /><p>paragraph</p></div>\r
864                                         br : function( element )\r
865                                         {\r
866                                                 var next = element.next;\r
867                                                 if ( next && next.name in blockLikeTags )\r
868                                                         return false;\r
869                                         }\r
870                                 }\r
871                           }, 1 );\r
872 \r
873                           editor.dataProcessor.writer = BBCodeWriter;\r
874 \r
875                           editor.on( 'editingBlockReady', function ()\r
876                           {\r
877                                   var wysiwyg = editor._.modes[ 'wysiwyg' ];\r
878                                   wysiwyg.loadData = CKEDITOR.tools.override( wysiwyg.loadData, function( org )\r
879                                   {\r
880                                           return function( data )\r
881                                           {\r
882                                                   return ( org.call( this, BBCodeToHtml( data ) ) );\r
883                                           };\r
884                                   } );\r
885                           } );\r
886                   },\r
887 \r
888                   afterInit : function( editor )\r
889                   {\r
890                           var filters;\r
891                           if ( editor._.elementsPath  )\r
892                           {\r
893                                   // Eliminate irrelevant elements from displaying, e.g body and p.\r
894                                   if ( ( filters = editor._.elementsPath.filters ) )\r
895                                         filters.push( function( element )\r
896                                                 {\r
897                                                         var htmlName = element.getName(),\r
898                                                                 name = tagnameMap[ htmlName ] || false;\r
899 \r
900                                                         // Specialized anchor presents as email.\r
901                                                         if ( name == 'link' && element.getAttribute( 'href' ).indexOf( 'mailto:' ) === 0 )\r
902                                                                 name = 'email';\r
903                                                         // Styled span could be either size or color.\r
904                                                         else if ( htmlName == 'span' )\r
905                                                         {\r
906                                                                 if ( element.getStyle( 'font-size' ) )\r
907                                                                         name = 'size';\r
908                                                                 else if ( element.getStyle( 'color' ) )\r
909                                                                         name = 'color';\r
910                                                         }\r
911                                                         else if ( name == 'img' )\r
912                                                         {\r
913                                                                 var src = element.data( 'cke-saved-src' );\r
914                                                                 if ( src && src.indexOf( editor.config.smiley_path ) === 0 )\r
915                                                                         name = 'smiley';\r
916                                                         }\r
917 \r
918                                                         return name;\r
919                                                 });\r
920                           }\r
921                   }\r
922           } );\r
923 \r
924 })();\r