29 | | CKEDITOR.plugins.add( 'fakeobjects', |
| 29 | var flashExtensionRegex = /\.swf($|#|\?)/i, |
| 30 | emptyElements = { base:1,col:1,meta:1,link:1,hr:1,br:1,param:1,img:1,area:1,input:1 }, |
| 31 | makeTagOpenerHtml = function( tagName, attributes, selfClosing ) |
31 | | init : function( editor, pluginPath ) |
32 | | { |
33 | | var flashExtensionRegex = /\.swf($|#|\?)/i, |
34 | | emptyElements = { base:1,col:1,meta:1,link:1,hr:1,br:1,param:1,img:1,area:1,input:1 }, |
35 | | objectTypes = |
36 | | [ |
37 | | { |
38 | | match : function( nodeName, attributes ) |
39 | | { |
40 | | return nodeName == 'embed' && ( attributes.type == 'application/x-shockwave-flash' || flashExtensionRegex.test( attributes.src || '' ) ); |
41 | | }, |
42 | | cssClass : 'flash', |
43 | | priority : 10 |
44 | | }, |
45 | | { |
46 | | match : function( nodeName, attributes ) |
47 | | { |
48 | | return ( nodeName == 'a' && attributes.name != null && attributes.name != '' ); |
49 | | }, |
50 | | cssClass : 'anchor', |
51 | | priority : 10 |
52 | | }, |
53 | | { |
54 | | match : function( nodeName, attributes ) |
55 | | { |
56 | | if ( nodeName == 'div' && attributes.style && attributes.style.pageBreakAfter == 'always' ) |
57 | | if ( attributes.firstChild ) |
58 | | { |
59 | | var element = new CKEDITOR.dom.element( attributes.firstChild ); |
60 | | return ( element && element.getName() == 'span'); |
61 | | } |
62 | | return false; |
63 | | }, |
64 | | cssClass : 'pagebreak', |
65 | | priority : 10 |
66 | | }, |
67 | | { |
68 | | match : function( nodeName, attributes ) |
69 | | { |
70 | | return nodeName == 'embed' || nodeName == 'object'; |
71 | | }, |
72 | | cssClass : 'object', |
73 | | priority : 0x100000000 |
74 | | } |
75 | | ], |
76 | | makeTagOpenerHtml = function( tagName, attributes, selfClosing ) |
77 | | { |
78 | | var attribStr = [], html = [ '<' + tagName ]; |
79 | | for ( var i in attributes ) |
80 | | attribStr.push( i + '="' + CKEDITOR.tools.htmlEncode( attributes[i] ) + '"'); |
81 | | if ( attribStr.length > 0 ) |
82 | | html.push( ' ', attribStr.join( ' ' ) ); |
83 | | if ( emptyElements[ tagName ] || selfClosing ) |
84 | | html.push( ' /' ); |
85 | | html.push( '>' ); |
86 | | return html.join( '' ); |
87 | | }, |
88 | | cssWidthRegex = /width\s*:\s*([0-9]+)\s*(?:[\w%]+)?\s*;?/i, |
89 | | cssHeightRegex = /height\s*:\s*([0-9]+)\s*(?:[\w%]+)?\s*;?/i; |
| 33 | var attribStr = [], html = [ '<' + tagName ]; |
| 34 | for ( var i in attributes ) |
| 35 | attribStr.push( i + '="' + CKEDITOR.tools.htmlEncode( attributes[i] ) + '"'); |
| 36 | if ( attribStr.length > 0 ) |
| 37 | html.push( ' ', attribStr.join( ' ' ) ); |
| 38 | if ( emptyElements[ tagName ] || selfClosing ) |
| 39 | html.push( ' /' ); |
| 40 | html.push( '>' ); |
| 41 | return html.join( '' ); |
| 42 | }, |
| 43 | cssWidthRegex = /width\s*:\s*([0-9]+)\s*(?:[\w%]+)?\s*;?/i, |
| 44 | cssHeightRegex = /height\s*:\s*([0-9]+)\s*(?:[\w%]+)?\s*;?/i; |
96 | | CKEDITOR.htmlParser.call( this ); |
97 | | }; |
98 | | copyParser.prototype = { |
99 | | onTagOpen : function( tagName, attributes, selfClosing ) |
100 | | { |
101 | | this.output.push( makeTagOpenerHtml( tagName, attributes, selfClosing ) ); |
102 | | }, |
| 51 | CKEDITOR.htmlParser.call( this ); |
| 52 | }; |
| 53 | copyParser.prototype = { |
| 54 | onTagOpen : function( tagName, attributes, selfClosing ) |
| 55 | { |
| 56 | this.output.push( makeTagOpenerHtml( tagName, attributes, selfClosing ) ); |
| 57 | }, |
110 | | onText : function( text ) |
| 65 | onText : function( text ) |
| 66 | { |
| 67 | this.output.push( text ); |
| 68 | }, |
| 69 | |
| 70 | onComment : function( comment ) |
| 71 | { |
| 72 | this.output.push( '<!--' + comment + '-->' ); |
| 73 | }, |
| 74 | |
| 75 | parse : function( html ) |
| 76 | { |
| 77 | this._.html = html; |
| 78 | return CKEDITOR.htmlParser.prototype.parse.apply( this, arguments ); |
| 79 | } |
| 80 | }; |
| 81 | |
| 82 | /** |
| 83 | * Manages element placeholders in WYSIWYG area. |
| 84 | * @constructor |
| 85 | * @example |
| 86 | */ |
| 87 | CKEDITOR.plugins.fakeobjects = function() |
| 88 | { |
| 89 | this._ = |
| 90 | { |
| 91 | objectTypes : [ |
| 92 | { |
| 93 | match : function( nodeName, attributes ) |
143 | | var $ = element.$.cloneNode( true ), |
144 | | doc = $.ownerDocument, |
145 | | temp = doc.createElement( 'div' ), |
146 | | html; |
147 | | |
148 | | // Get the object's HTML code. |
149 | | temp.appendChild( $ ); |
150 | | html = temp.innerHTML; |
151 | | |
152 | | // Get the fake element's CSS class. |
153 | | var cssClass = 'unknown'; |
154 | | var realElementType = element.getName(); |
155 | | for ( var i = 0 ; i < objectTypes.length ; i++ ) |
156 | | { |
157 | | if ( objectTypes[i].match( element.getName(), element.$ ) ) |
| 112 | if ( nodeName == 'div' && attributes.style && attributes.style.pageBreakAfter == 'always' ) |
| 113 | if ( attributes.firstChild ) |
163 | | } |
164 | | |
165 | | // Make the fake element. |
166 | | var fakeRawElement = doc.createElement( 'img' ), |
167 | | cssText = $.style.cssText, |
168 | | widthMatch = cssText.match( cssWidthRegex ), |
169 | | heightMatch = cssText.match( cssHeightRegex ); |
170 | | fakeRawElement.className = cssClass; |
171 | | fakeRawElement.src = CKEDITOR.getUrl( 'images/spacer.gif' ); |
172 | | if ( widthMatch) |
173 | | fakeRawElement.style.width = widthMatch[1] + 'px'; |
174 | | if ( heightMatch ) |
175 | | fakeRawElement.style.height = heightMatch[1] + 'px'; |
176 | | fakeRawElement.setAttribute( '_cke_protected_html', encodeURIComponent( html ) ); |
177 | | fakeRawElement.setAttribute( '_cke_real_element_type', realElementType ); |
178 | | return new CKEDITOR.dom.element( fakeRawElement ); |
| 118 | return false; |
189 | | var html = decodeURIComponent( fakeImgElement.getAttribute( '_cke_protected_html' ) ), |
190 | | realElement = CKEDITOR.dom.element.createFromHtml( html, editor.document ); |
| 126 | return nodeName == 'embed' || nodeName == 'object'; |
| 127 | }, |
| 128 | cssClass : 'object', |
| 129 | priority : 0x100000000 |
| 130 | } |
| 131 | ] |
| 132 | }; |
| 133 | }; |
| 134 | CKEDITOR.plugins.fakeobjects.prototype = |
| 135 | { |
| 136 | /** |
| 137 | * Converts an element into a placeholder <img> element, for adding |
| 138 | * into the WYSIWYG area. |
| 139 | * @param {CKEDITOR.dom.element} element Input DOM element. |
| 140 | * @returns {CKEDITOR.dom.element} The placeholder <img> element. |
| 141 | * @example |
| 142 | */ |
| 143 | protectElement : function( element ) |
| 144 | { |
| 145 | var $ = element.$.cloneNode( true ), |
| 146 | doc = $.ownerDocument, |
| 147 | temp = doc.createElement( 'div' ), |
| 148 | html; |
200 | | /** |
201 | | * Converts protectable elements in an HTML string to placeholders. |
202 | | * @param {String} html HTML with protectable elements. |
203 | | * @returns {String} HTML with placeholders. |
204 | | * @example |
205 | | */ |
206 | | protectHtml : function( html ) |
207 | | { |
208 | | var parser = new CKEDITOR.htmlParser(), |
209 | | tagDepth = 0, processedHtml = [], |
210 | | protectedHtml = [], inProtection = false; |
| 167 | // Make the fake element. |
| 168 | var fakeRawElement = doc.createElement( 'img' ), |
| 169 | cssText = $.style.cssText, |
| 170 | widthMatch = cssText.match( cssWidthRegex ), |
| 171 | heightMatch = cssText.match( cssHeightRegex ); |
| 172 | fakeRawElement.className = cssClass; |
| 173 | fakeRawElement.src = CKEDITOR.getUrl( 'images/spacer.gif' ); |
| 174 | if ( widthMatch) |
| 175 | fakeRawElement.style.width = widthMatch[1] + 'px'; |
| 176 | if ( heightMatch ) |
| 177 | fakeRawElement.style.height = heightMatch[1] + 'px'; |
| 178 | fakeRawElement.setAttribute( '_cke_protected_html', encodeURIComponent( html ) ); |
| 179 | fakeRawElement.setAttribute( '_cke_real_element_type', realElementType ); |
| 180 | return new CKEDITOR.dom.element( fakeRawElement ); |
| 181 | }, |
212 | | parser.onTagOpen = function( tagName, attributes, selfClosing ) |
213 | | { |
214 | | if ( inProtection ) |
215 | | { |
216 | | protectedHtml.push( makeTagOpenerHtml( tagName, attributes, selfClosing ) ); |
217 | | if ( !( emptyElements[ tagName ] || selfClosing ) ) |
218 | | tagDepth++; |
219 | | if ( tagDepth < 1 ) |
220 | | { |
221 | | inProtection = false; |
222 | | processedHtml.push( encodeURIComponent( protectedHtml.join( '' ) ), '" />' ); |
223 | | protectedHtml = []; |
224 | | } |
225 | | return; |
226 | | } |
| 183 | /** |
| 184 | * Converts a placeholder <img> element back to the real element. |
| 185 | * @param {CKEDITOR.dom.element} fakeImgElement The placeholder <img>. |
| 186 | * @returns {CKEDITOR.dom.element} The real element. |
| 187 | * @example |
| 188 | */ |
| 189 | restoreElement : function( fakeImgElement ) |
| 190 | { |
| 191 | var html = decodeURIComponent( fakeImgElement.getAttribute( '_cke_protected_html' ) ), |
| 192 | realElement = CKEDITOR.dom.element.createFromHtml( html, editor.document ); |
240 | | // Create the fake <img> tag. |
241 | | processedHtml.push( '<img src="', |
242 | | CKEDITOR.getUrl( 'images/spacer.gif' ), |
243 | | '" ', |
244 | | 'class="cke_fakeobject ' + objectTypes[i].cssClass + '" ', |
245 | | '_cke_real_element_type="' + objectTypes[i].cssClass + '"' ); |
| 202 | /** |
| 203 | * Converts protectable elements in an HTML string to placeholders. |
| 204 | * @param {String} html HTML with protectable elements. |
| 205 | * @returns {String} HTML with placeholders. |
| 206 | * @example |
| 207 | */ |
| 208 | protectHtml : function( html ) |
| 209 | { |
| 210 | var parser = new CKEDITOR.htmlParser(), |
| 211 | tagDepth = 0, processedHtml = [], |
| 212 | protectedHtml = [], inProtection = false, |
| 213 | objectTypes = this._.objectTypes; |
247 | | if ( widthMatch || heightMatch ) |
248 | | { |
249 | | processedHtml.push( 'style="', |
250 | | widthMatch ? 'width:' + widthMatch[1] + 'px;' : '', |
251 | | heightMatch ? 'height:' + heightMatch[1] + 'px;' : '', |
252 | | '" ' ); |
253 | | } |
| 215 | parser.onTagOpen = function( tagName, attributes, selfClosing ) |
| 216 | { |
| 217 | if ( inProtection ) |
| 218 | { |
| 219 | protectedHtml.push( makeTagOpenerHtml( tagName, attributes, selfClosing ) ); |
| 220 | if ( !( emptyElements[ tagName ] || selfClosing ) ) |
| 221 | tagDepth++; |
| 222 | if ( tagDepth < 1 ) |
| 223 | { |
| 224 | inProtection = false; |
| 225 | processedHtml.push( encodeURIComponent( protectedHtml.join( '' ) ), '" />' ); |
| 226 | protectedHtml = []; |
| 227 | } |
| 228 | return; |
| 229 | } |
274 | | parser.onTagClose = function( tagName ) |
275 | | { |
276 | | if ( inProtection ) |
277 | | { |
278 | | if ( !emptyElements[ tagName ] ) |
279 | | tagDepth--; |
280 | | protectedHtml.push( '</' + tagName + '>' ); |
281 | | if ( tagDepth < 1 ) |
282 | | { |
283 | | inProtection = false; |
284 | | processedHtml.push( encodeURIComponent( protectedHtml.join( '' ) ), '" />' ); |
285 | | protectedHtml = []; |
286 | | } |
287 | | } |
288 | | else |
289 | | processedHtml.push( '</' + tagName + '>' ); |
290 | | }; |
| 258 | processedHtml.push( '_cke_protected_html="' ); |
| 259 | arguments.callee.call( this, tagName, attributes, selfClosing ); |
| 260 | return; |
| 261 | } |
| 262 | } |
292 | | parser.parse( html ); |
293 | | return processedHtml.join( '' ); |
294 | | }, |
295 | | /** |
296 | | * Updates HTML into a placeholder |
297 | | * @param {CKEDITOR.dom.element} fakeImgElement The placeholder <img>. |
298 | | * @param {CKEDITOR.dom.element} element Input DOM element. |
299 | | * @returns {String} encoded HTML. |
300 | | * @example |
301 | | */ |
302 | | updateFakeElement : function( fakeElement, realElement ) |
303 | | { |
304 | | var $ = realElement.$.cloneNode( true ), |
305 | | doc = $.ownerDocument, |
306 | | temp = doc.createElement( 'div' ), |
307 | | html; |
| 264 | processedHtml.push( makeTagOpenerHtml( tagName, attributes ) ); |
| 265 | }; |
309 | | // Get the object's HTML code. |
310 | | temp.appendChild( $ ); |
311 | | html = temp.innerHTML; |
312 | | fakeElement.setAttribute( '_cke_protected_html', encodeURIComponent( html ) ); |
313 | | return html; |
314 | | }, |
315 | | |
316 | | /** |
317 | | * Restores placeholders in an HTML string back to their original elements. |
318 | | * @param {String} html HTML with placeholders. |
319 | | * @returns {String} Restored HTML. |
320 | | * @example |
321 | | */ |
322 | | restoreHtml : function( html ) |
| 267 | parser.onText = function( text ) |
| 268 | { |
| 269 | inProtection ? protectedHtml.push( text ) : processedHtml.push( text ); |
| 270 | }; |
| 271 | |
| 272 | parser.onComment = function( comment ) |
| 273 | { |
| 274 | inProtection ? protectedHtml.push( '<!--' + comment + '-->' ) : processedHtml.push( '<!--' + comment + '-->' ); |
| 275 | }; |
| 276 | |
| 277 | parser.onTagClose = function( tagName ) |
| 278 | { |
| 279 | if ( inProtection ) |
| 280 | { |
| 281 | if ( !emptyElements[ tagName ] ) |
| 282 | tagDepth--; |
| 283 | protectedHtml.push( '</' + tagName + '>' ); |
| 284 | if ( tagDepth < 1 ) |
333 | | styles = styles.replace( cssWidthRegex, '' ).replace( cssHeightRegex, '' ); |
334 | | if ( this.width ) |
335 | | styles += 'width:' + this.width + ';'; |
336 | | if ( this.height ) |
337 | | styles += 'height:' + this.height + ';'; |
338 | | attributes.style = styles; |
| 295 | parser.parse( html ); |
| 296 | return processedHtml.join( '' ); |
| 297 | }, |
| 298 | /** |
| 299 | * Updates HTML into a placeholder |
| 300 | * @param {CKEDITOR.dom.element} fakeImgElement The placeholder <img>. |
| 301 | * @param {CKEDITOR.dom.element} element Input DOM element. |
| 302 | * @returns {String} encoded HTML. |
| 303 | * @example |
| 304 | */ |
| 305 | updateFakeElement : function( fakeElement, realElement ) |
| 306 | { |
| 307 | var $ = realElement.$.cloneNode( true ), |
| 308 | doc = $.ownerDocument, |
| 309 | temp = doc.createElement( 'div' ), |
| 310 | html; |
340 | | this.done = true; |
341 | | } |
| 312 | // Get the object's HTML code. |
| 313 | temp.appendChild( $ ); |
| 314 | html = temp.innerHTML; |
| 315 | fakeElement.setAttribute( '_cke_protected_html', encodeURIComponent( html ) ); |
| 316 | return html; |
| 317 | }, |
| 318 | |
| 319 | /** |
| 320 | * Restores placeholders in an HTML string back to their original elements. |
| 321 | * @param {String} html HTML with placeholders. |
| 322 | * @returns {String} Restored HTML. |
| 323 | * @example |
| 324 | */ |
| 325 | restoreHtml : function( html ) |
| 326 | { |
| 327 | var parser = new copyParser(), |
| 328 | innerParser = new copyParser(); |
| 329 | |
| 330 | innerParser.onTagOpen = function( tagName, attributes, selfClosing ) |
| 331 | { |
| 332 | if ( !this.done ) |
| 333 | { |
| 334 | var styles = attributes.style || ''; |
363 | | this.output.push( protectedHtml ); |
364 | | return; |
365 | | } |
366 | | return copyParser.prototype.onTagOpen.apply( this, arguments ); |
367 | | }; |
| 349 | parser.onTagOpen = function( tagName, attributes, selfClosing ) |
| 350 | { |
| 351 | if ( tagName == 'img' && attributes._cke_protected_html !== undefined ) |
| 352 | { |
| 353 | var styles = attributes.style || '', |
| 354 | protectedHtml = decodeURIComponent( attributes._cke_protected_html ), |
| 355 | widthMatch = styles.match( cssWidthRegex ), |
| 356 | heightMatch = styles.match( cssHeightRegex ); |
369 | | parser.parse( html ); |
370 | | return parser.output.join( '' ); |
371 | | }, |
372 | | |
373 | | /** |
374 | | * Adds an object type to be displayed by placeholders. |
375 | | * @param {Function} matchFunc A predicate function to determine whether |
376 | | * an object needs to be replaced by placeholders or not. The function |
377 | | * should have the following signature: |
378 | | * <blockquote>function( tagName, attributes )</blockquote> |
379 | | * In which tagName is the object's tag name, and attributes is an object |
380 | | * storing all the object's attributes. |
381 | | * @param {String} cssClass The CSS class that should be added to the |
382 | | * placeholder <img> for representing this type of object. |
383 | | * @param {Number} priority (Optional) An integer representing the |
384 | | * priority of this type of object. If an element matches the descriptions |
385 | | * of two object types, the object type of <strong>lower</strong> priority |
386 | | * takes precedance. |
387 | | * @example |
388 | | */ |
389 | | addObjectType : function( matchFunc, cssClass, priority ) |
| 358 | if ( widthMatch || heightMatch ) |
409 | | objectTypes.unshift( obj ); |
410 | | } |
411 | | }; |
| 376 | /** |
| 377 | * Adds an object type to be displayed by placeholders. |
| 378 | * @param {Function} matchFunc A predicate function to determine whether |
| 379 | * an object needs to be replaced by placeholders or not. The function |
| 380 | * should have the following signature: |
| 381 | * <blockquote>function( tagName, attributes )</blockquote> |
| 382 | * In which tagName is the object's tag name, and attributes is an object |
| 383 | * storing all the object's attributes. |
| 384 | * @param {String} cssClass The CSS class that should be added to the |
| 385 | * placeholder <img> for representing this type of object. |
| 386 | * @param {Number} priority (Optional) An integer representing the |
| 387 | * priority of this type of object. If an element matches the descriptions |
| 388 | * of two object types, the object type of <strong>lower</strong> priority |
| 389 | * takes precedance. |
| 390 | * @example |
| 391 | */ |
| 392 | addObjectType : function( matchFunc, cssClass, priority ) |
| 393 | { |
| 394 | if ( priority === undefined ) |
| 395 | priority = 10; |
| 396 | |
| 397 | var obj = { |
| 398 | match : matchFunc, |
| 399 | cssClass : cssClass, |
| 400 | priority : priority |
| 401 | }; |
| 402 | |
| 403 | for ( var i = this._.objectTypes.length - 1 ; i >= 0 ; i-- ) |
| 404 | { |
| 405 | if ( this._.objectTypes[i].priority < priority ) |
| 406 | { |
| 407 | this._.objectTypes.splice( i + 1, 0, obj ); |
| 408 | return; |
| 409 | } |