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