[239] | 1 | /*
|
---|
| 2 | Copyright (c) 2003-2011, 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 | var option = attributes.option;
|
---|
| 570 | option && this.write( '=', option );
|
---|
| 571 | this.write( ']' );
|
---|
| 572 |
|
---|
| 573 | if ( this.getRule( tag, 'breakAfterOpen' ) )
|
---|
| 574 | this.lineBreak( 1 );
|
---|
| 575 | }
|
---|
| 576 | else if ( tag == 'br' )
|
---|
| 577 | this._.output.push( '\n' );
|
---|
| 578 | },
|
---|
| 579 |
|
---|
| 580 | openTagClose : function() { },
|
---|
| 581 | attribute : function() { },
|
---|
| 582 |
|
---|
| 583 | closeTag : function( tag )
|
---|
| 584 | {
|
---|
| 585 | if ( tag in bbcodeMap )
|
---|
| 586 | {
|
---|
| 587 | if ( this.getRule( tag, 'breakBeforeClose' ) )
|
---|
| 588 | this.lineBreak( 1 );
|
---|
| 589 |
|
---|
| 590 | tag != '*' && this.write( '[/', tag, ']' );
|
---|
| 591 |
|
---|
| 592 | if ( this.getRule( tag, 'breakAfterClose' ) )
|
---|
| 593 | this.lineBreak( 1 );
|
---|
| 594 | }
|
---|
| 595 | },
|
---|
| 596 |
|
---|
| 597 | text : function( text )
|
---|
| 598 | {
|
---|
| 599 | this.write( text );
|
---|
| 600 | },
|
---|
| 601 |
|
---|
| 602 | /**
|
---|
| 603 | * Writes a comment.
|
---|
| 604 | * @param {String} comment The comment text.
|
---|
| 605 | * @example
|
---|
| 606 | * // Writes "<!-- My comment -->".
|
---|
| 607 | * writer.comment( ' My comment ' );
|
---|
| 608 | */
|
---|
| 609 | comment : function() {},
|
---|
| 610 |
|
---|
| 611 | /*
|
---|
| 612 | * Output line-break for formatting.
|
---|
| 613 | */
|
---|
| 614 | lineBreak : function()
|
---|
| 615 | {
|
---|
| 616 | // Avoid line break when:
|
---|
| 617 | // 1) Previous tag already put one.
|
---|
| 618 | // 2) We're at output start.
|
---|
| 619 | if ( !this._.hasLineBreak && this._.output.length )
|
---|
| 620 | {
|
---|
| 621 | this.write( '\n' );
|
---|
| 622 | this._.hasLineBreak = 1;
|
---|
| 623 | }
|
---|
| 624 | },
|
---|
| 625 |
|
---|
| 626 | write : function()
|
---|
| 627 | {
|
---|
| 628 | this._.hasLineBreak = 0;
|
---|
| 629 | var data = Array.prototype.join.call( arguments, '' );
|
---|
| 630 | this._.output.push( data );
|
---|
| 631 | },
|
---|
| 632 |
|
---|
| 633 | reset : function()
|
---|
| 634 | {
|
---|
| 635 | this._.output = [];
|
---|
| 636 | this._.hasLineBreak = 0;
|
---|
| 637 | },
|
---|
| 638 |
|
---|
| 639 | getHtml : function( reset )
|
---|
| 640 | {
|
---|
| 641 | var bbcode = this._.output.join( '' );
|
---|
| 642 |
|
---|
| 643 | if ( reset )
|
---|
| 644 | this.reset();
|
---|
| 645 |
|
---|
| 646 | return decodeHtml ( bbcode );
|
---|
| 647 | }
|
---|
| 648 | }
|
---|
| 649 | });
|
---|
| 650 |
|
---|
| 651 | var BBCodeWriter = new CKEDITOR.htmlParser.BBCodeWriter();
|
---|
| 652 |
|
---|
| 653 | CKEDITOR.plugins.add( 'bbcode',
|
---|
| 654 | {
|
---|
| 655 | requires : [ 'htmldataprocessor', 'entities' ],
|
---|
| 656 | beforeInit : function( editor )
|
---|
| 657 | {
|
---|
| 658 | // Adapt some critical editor configuration for better support
|
---|
| 659 | // of BBCode environment.
|
---|
| 660 | var config = editor.config;
|
---|
| 661 | CKEDITOR.tools.extend( config,
|
---|
| 662 | {
|
---|
| 663 | enterMode : CKEDITOR.ENTER_BR,
|
---|
| 664 | basicEntities: false,
|
---|
| 665 | entities : false,
|
---|
| 666 | fillEmptyBlocks : false
|
---|
| 667 | }, true );
|
---|
| 668 | },
|
---|
| 669 | init : function( editor )
|
---|
| 670 | {
|
---|
| 671 | var config = editor.config;
|
---|
| 672 |
|
---|
| 673 | function BBCodeToHtml( code )
|
---|
| 674 | {
|
---|
| 675 | var fragment = CKEDITOR.htmlParser.fragment.fromBBCode( code ),
|
---|
| 676 | writer = new CKEDITOR.htmlParser.basicWriter();
|
---|
| 677 |
|
---|
| 678 | fragment.writeHtml( writer, dataFilter );
|
---|
| 679 | return writer.getHtml( true );
|
---|
| 680 | }
|
---|
| 681 |
|
---|
| 682 | var dataFilter = new CKEDITOR.htmlParser.filter();
|
---|
| 683 | dataFilter.addRules(
|
---|
| 684 | {
|
---|
| 685 | elements :
|
---|
| 686 | {
|
---|
| 687 | 'blockquote' : function( element )
|
---|
| 688 | {
|
---|
| 689 | var quoted = new CKEDITOR.htmlParser.element( 'div' );
|
---|
| 690 | quoted.children = element.children;
|
---|
| 691 | element.children = [ quoted ];
|
---|
| 692 | var citeText = element.attributes.cite;
|
---|
| 693 | if ( citeText )
|
---|
| 694 | {
|
---|
| 695 | var cite = new CKEDITOR.htmlParser.element( 'cite' );
|
---|
| 696 | cite.add( new CKEDITOR.htmlParser.text( citeText.replace( /^"|"$/g, '' ) ) );
|
---|
| 697 | delete element.attributes.cite;
|
---|
| 698 | element.children.unshift( cite );
|
---|
| 699 | }
|
---|
| 700 | },
|
---|
| 701 | 'span' : function( element )
|
---|
| 702 | {
|
---|
| 703 | var bbcode;
|
---|
| 704 | if ( ( bbcode = element.attributes.bbcode ) )
|
---|
| 705 | {
|
---|
| 706 | if ( bbcode == 'img' )
|
---|
| 707 | {
|
---|
| 708 | element.name = 'img';
|
---|
| 709 | element.attributes.src = element.children[ 0 ].value;
|
---|
| 710 | element.children = [];
|
---|
| 711 | }
|
---|
| 712 | else if ( bbcode == 'email' )
|
---|
| 713 | {
|
---|
| 714 | element.name = 'a';
|
---|
| 715 | element.attributes.href = 'mailto:' + element.children[ 0 ].value;
|
---|
| 716 | }
|
---|
| 717 |
|
---|
| 718 | delete element.attributes.bbcode;
|
---|
| 719 | }
|
---|
| 720 | },
|
---|
| 721 | 'ol' : function ( element )
|
---|
| 722 | {
|
---|
| 723 | if ( element.attributes.listType )
|
---|
| 724 | {
|
---|
| 725 | if ( element.attributes.listType != 'decimal' )
|
---|
| 726 | element.attributes.style = 'list-style-type:' + element.attributes.listType;
|
---|
| 727 | }
|
---|
| 728 | else
|
---|
| 729 | element.name = 'ul';
|
---|
| 730 |
|
---|
| 731 | delete element.attributes.listType;
|
---|
| 732 | },
|
---|
| 733 | a : function( element )
|
---|
| 734 | {
|
---|
| 735 | if ( !element.attributes.href )
|
---|
| 736 | element.attributes.href = element.children[ 0 ].value;
|
---|
| 737 | },
|
---|
| 738 | 'smiley' : function( element )
|
---|
| 739 | {
|
---|
| 740 | element.name = 'img';
|
---|
| 741 |
|
---|
| 742 | var description = element.attributes.desc,
|
---|
| 743 | image = config.smiley_images[ CKEDITOR.tools.indexOf( config.smiley_descriptions, description ) ],
|
---|
| 744 | src = CKEDITOR.tools.htmlEncode( config.smiley_path + image );
|
---|
| 745 |
|
---|
| 746 | element.attributes =
|
---|
| 747 | {
|
---|
| 748 | src : src,
|
---|
| 749 | 'data-cke-saved-src' : src,
|
---|
| 750 | title : description,
|
---|
| 751 | alt : description
|
---|
| 752 | };
|
---|
| 753 | }
|
---|
| 754 | }
|
---|
| 755 | } );
|
---|
| 756 |
|
---|
| 757 | editor.dataProcessor.htmlFilter.addRules(
|
---|
| 758 | {
|
---|
| 759 | elements :
|
---|
| 760 | {
|
---|
| 761 | $ : function( element )
|
---|
| 762 | {
|
---|
| 763 | var attributes = element.attributes,
|
---|
| 764 | style = parseStyleText( attributes.style ),
|
---|
| 765 | value;
|
---|
| 766 |
|
---|
| 767 | var tagName = element.name;
|
---|
| 768 | if ( tagName in convertMap )
|
---|
| 769 | tagName = convertMap[ tagName ];
|
---|
| 770 | else if ( tagName == 'span' )
|
---|
| 771 | {
|
---|
| 772 | if ( ( value = style.color ) )
|
---|
| 773 | {
|
---|
| 774 | tagName = 'color';
|
---|
| 775 | value = RGBToHex( value );
|
---|
| 776 | }
|
---|
| 777 | else if ( ( value = style[ 'font-size' ] ) )
|
---|
| 778 | {
|
---|
| 779 | var percentValue = value.match( /(\d+)%$/ );
|
---|
| 780 | if ( percentValue )
|
---|
| 781 | {
|
---|
| 782 | value = percentValue[ 1 ];
|
---|
| 783 | tagName = 'size';
|
---|
| 784 | }
|
---|
| 785 | }
|
---|
| 786 | }
|
---|
| 787 | else if ( tagName == 'ol' || tagName == 'ul' )
|
---|
| 788 | {
|
---|
| 789 | if ( ( value = style[ 'list-style-type'] ) )
|
---|
| 790 | {
|
---|
| 791 | switch ( value )
|
---|
| 792 | {
|
---|
| 793 | case 'lower-alpha':
|
---|
| 794 | value = 'a';
|
---|
| 795 | break;
|
---|
| 796 | case 'upper-alpha':
|
---|
| 797 | value = 'A';
|
---|
| 798 | break;
|
---|
| 799 | }
|
---|
| 800 | }
|
---|
| 801 | else if ( tagName == 'ol' )
|
---|
| 802 | value = 1;
|
---|
| 803 |
|
---|
| 804 | tagName = 'list';
|
---|
| 805 | }
|
---|
| 806 | else if ( tagName == 'blockquote' )
|
---|
| 807 | {
|
---|
| 808 | try
|
---|
| 809 | {
|
---|
| 810 | var cite = element.children[ 0 ],
|
---|
| 811 | quoted = element.children[ 1 ],
|
---|
| 812 | citeText = cite.name == 'cite' && cite.children[ 0 ].value;
|
---|
| 813 |
|
---|
| 814 | if ( citeText )
|
---|
| 815 | {
|
---|
| 816 | value = '"' + citeText + '"';
|
---|
| 817 | element.children = quoted.children;
|
---|
| 818 | }
|
---|
| 819 |
|
---|
| 820 | }
|
---|
| 821 | catch( er )
|
---|
| 822 | {
|
---|
| 823 | }
|
---|
| 824 |
|
---|
| 825 | tagName = 'quote';
|
---|
| 826 | }
|
---|
| 827 | else if ( tagName == 'a' )
|
---|
| 828 | {
|
---|
| 829 | if ( ( value = attributes.href ) )
|
---|
| 830 | {
|
---|
| 831 | if ( value.indexOf( 'mailto:' ) !== -1 )
|
---|
| 832 | {
|
---|
| 833 | tagName = 'email';
|
---|
| 834 | // [email] should have a single text child with email address.
|
---|
| 835 | element.children = [ new CKEDITOR.htmlParser.text( value.replace( 'mailto:', '' ) ) ];
|
---|
| 836 | value = '';
|
---|
| 837 | }
|
---|
| 838 | else
|
---|
| 839 | {
|
---|
| 840 | var singleton = element.children.length == 1 && element.children[ 0 ];
|
---|
| 841 | if ( singleton
|
---|
| 842 | && singleton.type == CKEDITOR.NODE_TEXT
|
---|
| 843 | && singleton.value == value )
|
---|
| 844 | value = '';
|
---|
| 845 |
|
---|
| 846 | tagName = 'url';
|
---|
| 847 | }
|
---|
| 848 | }
|
---|
| 849 | }
|
---|
| 850 | else if ( tagName == 'img' )
|
---|
| 851 | {
|
---|
| 852 | element.isEmpty = 0;
|
---|
| 853 |
|
---|
| 854 | // Translate smiley (image) to text emotion.
|
---|
| 855 | var src = attributes[ 'data-cke-saved-src' ];
|
---|
| 856 | if ( src && src.indexOf( editor.config.smiley_path ) != -1 )
|
---|
| 857 | return new CKEDITOR.htmlParser.text( smileyMap[ attributes.alt ] );
|
---|
| 858 | else
|
---|
| 859 | element.children = [ new CKEDITOR.htmlParser.text( src ) ];
|
---|
| 860 | }
|
---|
| 861 |
|
---|
| 862 | element.name = tagName;
|
---|
| 863 | value && ( element.attributes.option = value );
|
---|
| 864 |
|
---|
| 865 | return null;
|
---|
| 866 | },
|
---|
| 867 |
|
---|
| 868 | // Remove any bogus br from the end of a pseudo block,
|
---|
| 869 | // e.g. <div>some text<br /><p>paragraph</p></div>
|
---|
| 870 | br : function( element )
|
---|
| 871 | {
|
---|
| 872 | var next = element.next;
|
---|
| 873 | if ( next && next.name in blockLikeTags )
|
---|
| 874 | return false;
|
---|
| 875 | }
|
---|
| 876 | }
|
---|
| 877 | }, 1 );
|
---|
| 878 |
|
---|
| 879 | editor.dataProcessor.writer = BBCodeWriter;
|
---|
| 880 |
|
---|
| 881 | editor.on( 'beforeSetMode', function( evt )
|
---|
| 882 | {
|
---|
| 883 | evt.removeListener();
|
---|
| 884 | var wysiwyg = editor._.modes[ 'wysiwyg' ];
|
---|
| 885 | wysiwyg.loadData = CKEDITOR.tools.override( wysiwyg.loadData, function( org )
|
---|
| 886 | {
|
---|
| 887 | return function( data )
|
---|
| 888 | {
|
---|
| 889 | return ( org.call( this, BBCodeToHtml( data ) ) );
|
---|
| 890 | };
|
---|
| 891 | } );
|
---|
| 892 | } );
|
---|
| 893 | },
|
---|
| 894 |
|
---|
| 895 | afterInit : function( editor )
|
---|
| 896 | {
|
---|
| 897 | var filters;
|
---|
| 898 | if ( editor._.elementsPath )
|
---|
| 899 | {
|
---|
| 900 | // Eliminate irrelevant elements from displaying, e.g body and p.
|
---|
| 901 | if ( ( filters = editor._.elementsPath.filters ) )
|
---|
| 902 | filters.push( function( element )
|
---|
| 903 | {
|
---|
| 904 | var htmlName = element.getName(),
|
---|
| 905 | name = tagnameMap[ htmlName ] || false;
|
---|
| 906 |
|
---|
| 907 | // Specialized anchor presents as email.
|
---|
| 908 | if ( name == 'link' && element.getAttribute( 'href' ).indexOf( 'mailto:' ) === 0 )
|
---|
| 909 | name = 'email';
|
---|
| 910 | // Styled span could be either size or color.
|
---|
| 911 | else if ( htmlName == 'span' )
|
---|
| 912 | {
|
---|
| 913 | if ( element.getStyle( 'font-size' ) )
|
---|
| 914 | name = 'size';
|
---|
| 915 | else if ( element.getStyle( 'color' ) )
|
---|
| 916 | name = 'color';
|
---|
| 917 | }
|
---|
| 918 | else if ( name == 'img' )
|
---|
| 919 | {
|
---|
| 920 | var src = element.data( 'cke-saved-src' );
|
---|
| 921 | if ( src && src.indexOf( editor.config.smiley_path ) === 0 )
|
---|
| 922 | name = 'smiley';
|
---|
| 923 | }
|
---|
| 924 |
|
---|
| 925 | return name;
|
---|
| 926 | });
|
---|
| 927 | }
|
---|
| 928 | }
|
---|
| 929 | } );
|
---|
| 930 |
|
---|
| 931 | })();
|
---|