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 CKEDITOR.dialog.add( 'docProps', function( editor )
  7 {
  8 	var lang = editor.lang.docprops,
  9 		langCommon = editor.lang.common,
 10 		metaHash = {};
 11 
 12 	function getDialogValue( dialogName, callback )
 13 	{
 14 		var onOk = function()
 15 		{
 16 			releaseHandlers( this );
 17 			callback( this, this._.parentDialog );
 18 		};
 19 		var releaseHandlers = function( dialog )
 20 		{
 21 			dialog.removeListener( 'ok', onOk );
 22 			dialog.removeListener( 'cancel', releaseHandlers );
 23 		};
 24 		var bindToDialog = function( dialog )
 25 		{
 26 			dialog.on( 'ok', onOk );
 27 			dialog.on( 'cancel', releaseHandlers );
 28 		};
 29 		editor.execCommand( dialogName );
 30 		if ( editor._.storedDialogs.colordialog )
 31 			bindToDialog( editor._.storedDialogs.colordialog );
 32 		else
 33 		{
 34 			CKEDITOR.on( 'dialogDefinition', function( e )
 35 			{
 36 				if ( e.data.name != dialogName )
 37 					return;
 38 
 39 				var definition = e.data.definition;
 40 
 41 				e.removeListener();
 42 				definition.onLoad = CKEDITOR.tools.override( definition.onLoad, function( orginal )
 43 				{
 44 					return function()
 45 					{
 46 						bindToDialog( this );
 47 						definition.onLoad = orginal;
 48 						if ( typeof orginal == 'function' )
 49 							orginal.call( this );
 50 					};
 51 				});
 52 			});
 53 		}
 54 	}
 55 	function handleOther()
 56 	{
 57 		var dialog = this.getDialog(),
 58 			other = dialog.getContentElement( 'general', this.id + 'Other' );
 59 		if ( !other )
 60 			return;
 61 		if ( this.getValue() == 'other' )
 62 		{
 63 			other.getInputElement().removeAttribute( 'readOnly' );
 64 			other.focus();
 65 			other.getElement().removeClass( 'cke_disabled' );
 66 		}
 67 		else
 68 		{
 69 			other.getInputElement().setAttribute( 'readOnly', true );
 70 			other.getElement().addClass( 'cke_disabled' );
 71 		}
 72 	}
 73 	function commitMeta( name, isHttp, value )
 74 	{
 75 		return function( doc, html, head )
 76 		{
 77 			var hash = metaHash,
 78 				val = typeof value != 'undefined' ? value : this.getValue();
 79 			if ( !val && ( name in hash ) )
 80 				hash[ name ].remove();
 81 			else if ( val && ( name in hash ) )
 82 				hash[ name ].setAttribute( 'content', val );
 83 			else if ( val )
 84 			{
 85 				var meta = new CKEDITOR.dom.element( 'meta', editor.document );
 86 				meta.setAttribute( isHttp ? 'http-equiv' : 'name', name );
 87 				meta.setAttribute( 'content', val );
 88 				head.append( meta );
 89 			}
 90 		};
 91 	}
 92 	function setupMeta( name, ret )
 93 	{
 94 		return function()
 95 		{
 96 			var hash = metaHash,
 97 				result = ( name in hash ) ? hash[ name ].getAttribute( 'content' ) || '' : '';
 98 			if ( ret )
 99 				return result;
100 			this.setValue( result );
101 			return null;
102 		};
103 	}
104 	function commitMargin( name )
105 	{
106 		return function( doc, html, head, body )
107 		{
108 			body.removeAttribute( 'margin' + name );
109 			var val = this.getValue();
110 			if ( val !== '' )
111 				body.setStyle( 'margin-' + name, CKEDITOR.tools.cssLength( val ) );
112 			else
113 				body.removeStyle( 'margin-' + name );
114 		};
115 	}
116 
117 	function createMetaHash( doc )
118 	{
119 		var hash = {},
120 			metas = doc.getElementsByTag( 'meta' ),
121 			count = metas.count();
122 
123 		for ( var i = 0; i < count; i++ )
124 		{
125 			var meta = metas.getItem( i );
126 			hash[ meta.getAttribute( meta.hasAttribute( 'http-equiv' ) ? 'http-equiv' : 'name' ).toLowerCase() ] = meta;
127 		}
128 		return hash;
129 	}
130 	// We cannot just remove the style from the element, as it might be affected from non-inline stylesheets.
131 	// To get the proper result, we should manually set the inline style to its default value.
132 	function resetStyle( element, prop, resetVal )
133 	{
134 		element.removeStyle( prop );
135 		if ( element.getComputedStyle( prop ) != resetVal )
136 			element.setStyle( prop, resetVal );
137 	}
138 
139 	// Utilty to shorten the creation of color fields in the dialog.
140 	var colorField = function( id, label, fieldProps )
141 	{
142 		return {
143 			type : 'hbox',
144 			padding : 0,
145 			widths : [ '60%', '40%' ],
146 			children : [
147 				CKEDITOR.tools.extend( {
148 					type : 'text',
149 					id : id,
150 					label : lang[ label ]
151 				}, fieldProps || {}, 1 ),
152 				{
153 					type : 'button',
154 					id : id + 'Choose',
155 					label : lang.chooseColor,
156 					className : 'colorChooser',
157 					onClick : function()
158 					{
159 						var self = this;
160 						getDialogValue( 'colordialog', function( colorDialog )
161 						{
162 							var dialog = self.getDialog();
163 							dialog.getContentElement( dialog._.currentTabId, id ).setValue( colorDialog.getContentElement( 'picker', 'selectedColor' ).getValue() );
164 						});
165 					}
166 				}
167 			]
168 		};
169 	};
170 	var previewSrc = 'javascript:' +
171 		'void((function(){' +
172 			encodeURIComponent(
173 				'document.open();' +
174 				( CKEDITOR.env.isCustomDomain() ? 'document.domain=\'' + document.domain + '\';' : '' ) +
175 				'document.write( \'<html style="background-color: #ffffff; height: 100%"><head></head><body style="width: 100%; height: 100%; margin: 0px">' + lang.previewHtml + '</body></html>\' );' +
176 				'document.close();'
177 			) +
178 		'})())';
179 
180 	return {
181 		title : lang.title,
182 		minHeight: 330,
183 		minWidth: 500,
184 		onShow : function()
185 		{
186 			var doc = editor.document,
187 				html = doc.getElementsByTag( 'html' ).getItem( 0 ),
188 				head = doc.getHead(),
189 				body = doc.getBody();
190 			metaHash = createMetaHash( doc );
191 			this.setupContent( doc, html, head, body );
192 		},
193 		onHide : function()
194 		{
195 			metaHash = {};
196 		},
197 		onOk : function()
198 		{
199 			var doc = editor.document,
200 				html = doc.getElementsByTag( 'html' ).getItem( 0 ),
201 				head = doc.getHead(),
202 				body = doc.getBody();
203 			this.commitContent( doc, html, head, body );
204 		},
205 		contents : [
206 			{
207 				id : 'general',
208 				label : langCommon.generalTab,
209 				elements : [
210 					{
211 						type : 'text',
212 						id : 'title',
213 						label : lang.docTitle,
214 						setup : function( doc )
215 						{
216 							this.setValue( doc.getElementsByTag( 'title' ).getItem( 0 ).data( 'cke-title' ) );
217 						},
218 						commit : function( doc, html, head, body, isPreview )
219 						{
220 							if ( isPreview )
221 								return;
222 							doc.getElementsByTag( 'title' ).getItem( 0 ).data( 'cke-title', this.getValue() );
223 						}
224 					},
225 					{
226 						type : 'hbox',
227 						children : [
228 							{
229 								type : 'select',
230 								id : 'dir',
231 								label : langCommon.langDir,
232 								style : 'width: 100%',
233 								items : [
234 									[ langCommon.notSet , '' ],
235 									[ langCommon.langDirLtr, 'ltr' ],
236 									[ langCommon.langDirRtl, 'rtl' ]
237 								],
238 								setup : function( doc, html, head, body )
239 								{
240 									this.setValue( body.getDirection() || '' );
241 								},
242 								commit : function( doc, html, head, body )
243 								{
244 									var val = this.getValue();
245 									if ( val )
246 										body.setAttribute( 'dir', val );
247 									else
248 										body.removeAttribute( 'dir' );
249 									body.removeStyle( 'direction' );
250 								}
251 							},
252 							{
253 								type : 'text',
254 								id : 'langCode',
255 								label : langCommon.langCode,
256 								setup : function( doc, html )
257 								{
258 									this.setValue( html.getAttribute( 'xml:lang' ) || html.getAttribute( 'lang' ) || '' );
259 								},
260 								commit : function( doc, html, head, body, isPreview )
261 								{
262 									if ( isPreview )
263 										return;
264 									var val = this.getValue();
265 									if ( val )
266 										html.setAttributes( { 'xml:lang' : val, lang : val } );
267 									else
268 										html.removeAttributes( { 'xml:lang' : 1, lang : 1 } );
269 								}
270 							}
271 						]
272 					},
273 					{
274 						type : 'hbox',
275 						children : [
276 							{
277 								type : 'select',
278 								id : 'charset',
279 								label : lang.charset,
280 								style : 'width: 100%',
281 								items : [
282 									[ langCommon.notSet, '' ],
283 									[ lang.charsetASCII, 'us-ascii' ],
284 									[ lang.charsetCE, 'iso-8859-2' ],
285 									[ lang.charsetCT, 'big5' ],
286 									[ lang.charsetCR, 'iso-8859-5' ],
287 									[ lang.charsetGR, 'iso-8859-7' ],
288 									[ lang.charsetJP, 'iso-2022-jp' ],
289 									[ lang.charsetKR, 'iso-2022-kr' ],
290 									[ lang.charsetTR, 'iso-8859-9' ],
291 									[ lang.charsetUN, 'utf-8' ],
292 									[ lang.charsetWE, 'iso-8859-1' ],
293 									[ lang.other, 'other' ]
294 								],
295 								'default' : '',
296 								onChange : function()
297 								{
298 									this.getDialog().selectedCharset = this.getValue() != 'other' ? this.getValue() : '';
299 									handleOther.call( this );
300 								},
301 								setup : function()
302 								{
303 									this.metaCharset = ( 'charset' in metaHash );
304 
305 									var func = setupMeta( this.metaCharset ? 'charset' : 'content-type', 1, 1 ),
306 										val = func.call( this );
307 
308 									!this.metaCharset && val.match( /charset=[^=]+$/ ) && ( val = val.substring( val.indexOf( '=' ) + 1 ) );
309 
310 									if ( val )
311 									{
312 										this.setValue( val.toLowerCase() );
313 										if ( !this.getValue() )
314 										{
315 											this.setValue( 'other' );
316 											var other = this.getDialog().getContentElement( 'general', 'charsetOther' );
317 											other && other.setValue( val );
318 										}
319 										this.getDialog().selectedCharset = val;
320 									}
321 
322 									handleOther.call( this );
323 								},
324 								commit : function( doc, html, head, body, isPreview )
325 								{
326 									if ( isPreview )
327 										return;
328 									var value = this.getValue(),
329 										other = this.getDialog().getContentElement( 'general', 'charsetOther' );
330 
331 									value == 'other' && ( value = other ? other.getValue() : '' );
332 
333 									value && !this.metaCharset && ( value = ( metaHash[ 'content-type' ] ? metaHash[ 'content-type' ].getAttribute( 'content' ).split( ';' )[0] : 'text/html' ) + '; charset=' + value );
334 
335 									var func = commitMeta( this.metaCharset ? 'charset' : 'content-type', 1, value );
336 									func.call( this, doc, html, head );
337 								}
338 							},
339 							{
340 								type : 'text',
341 								id : 'charsetOther',
342 								label : lang.charsetOther,
343 								onChange : function(){ this.getDialog().selectedCharset = this.getValue(); }
344 							}
345 						]
346 					},
347 					{
348 						type : 'hbox',
349 						children : [
350 							{
351 								type : 'select',
352 								id : 'docType',
353 								label : lang.docType,
354 								style : 'width: 100%',
355 								items : [
356 									[ langCommon.notSet , '' ],
357 									[ 'XHTML 1.1', '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">' ],
358 									[ 'XHTML 1.0 Transitional', '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">' ],
359 									[ 'XHTML 1.0 Strict', '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' ],
360 									[ 'XHTML 1.0 Frameset', '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">' ],
361 									[ 'HTML 5', '<!DOCTYPE html>' ],
362 									[ 'HTML 4.01 Transitional', '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' ],
363 									[ 'HTML 4.01 Strict', '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">' ],
364 									[ 'HTML 4.01 Frameset', '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">' ],
365 									[ 'HTML 3.2', '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">' ],
366 									[ 'HTML 2.0', '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">' ],
367 									[ lang.other, 'other' ]
368 								],
369 								onChange : handleOther,
370 								setup : function()
371 								{
372 									if ( editor.docType )
373 									{
374 										this.setValue( editor.docType );
375 										if ( !this.getValue() )
376 										{
377 											this.setValue( 'other' );
378 											var other = this.getDialog().getContentElement( 'general', 'docTypeOther' );
379 											other && other.setValue( editor.docType );
380 										}
381 									}
382 									handleOther.call( this );
383 								},
384 								commit : function( doc, html, head, body, isPreview )
385 								{
386 									if ( isPreview )
387 										return;
388 									var value = this.getValue(),
389 										other = this.getDialog().getContentElement( 'general', 'docTypeOther' );
390 									editor.docType = value == 'other' ? ( other ? other.getValue() : '' ) : value;
391 								}
392 							},
393 							{
394 								type : 'text',
395 								id : 'docTypeOther',
396 								label : lang.docTypeOther
397 							}
398 						]
399 					},
400 					{
401 						type : 'checkbox',
402 						id : 'xhtmlDec',
403 						label : lang.xhtmlDec,
404 						setup : function()
405 						{
406 							this.setValue( !!editor.xmlDeclaration );
407 						},
408 						commit : function( doc, html, head, body, isPreview )
409 						{
410 							if ( isPreview )
411 								return;
412 							if ( this.getValue() )
413 							{
414 								editor.xmlDeclaration = '<?xml version="1.0" encoding="' + ( this.getDialog().selectedCharset || 'utf-8' )+ '"?>' ;
415 								html.setAttribute( 'xmlns', 'http://www.w3.org/1999/xhtml' );
416 							}
417 							else
418 							{
419 								editor.xmlDeclaration = '';
420 								html.removeAttribute( 'xmlns' );
421 							}
422 						}
423 					}
424 				]
425 			},
426 			{
427 				id : 'design',
428 				label : lang.design,
429 				elements : [
430 					{
431 						type : 'hbox',
432 						widths : [ '60%', '40%' ],
433 						children : [
434 							{
435 								type : 'vbox',
436 								children : [
437 									colorField( 'txtColor', 'txtColor',
438 									{
439 										setup : function( doc, html, head, body )
440 										{
441 											this.setValue( body.getComputedStyle( 'color' ) );
442 										},
443 										commit : function( doc, html, head, body, isPreview )
444 										{
445 											if ( this.isChanged() || isPreview )
446 											{
447 												body.removeAttribute( 'text' );
448 												var val = this.getValue();
449 												if ( val )
450 													body.setStyle( 'color', val );
451 												else
452 													body.removeStyle( 'color' );
453 											}
454 										}
455 									}),
456 									colorField( 'bgColor', 'bgColor', {
457 										setup : function( doc, html, head, body )
458 										{
459 											var val = body.getComputedStyle( 'background-color' ) || '';
460 											this.setValue( val == 'transparent' ? '' : val );
461 										},
462 										commit : function( doc, html, head, body, isPreview )
463 										{
464 											if ( this.isChanged() || isPreview )
465 											{
466 												body.removeAttribute( 'bgcolor' );
467 												var val = this.getValue();
468 												if ( val )
469 													body.setStyle( 'background-color', val );
470 												else
471 													resetStyle( body, 'background-color', 'transparent' );
472 											}
473 										}
474 									}),
475 									{
476 										type : 'hbox',
477 										widths : [ '60%', '40%' ],
478 										padding : 1,
479 										children : [
480 											{
481 												type : 'text',
482 												id : 'bgImage',
483 												label : lang.bgImage,
484 												setup : function( doc, html, head, body )
485 												{
486 													var val = body.getComputedStyle( 'background-image' ) || '';
487 													if ( val == 'none' )
488 														val = '';
489 													else
490 													{
491 														val = val.replace( /url\(\s*(["']?)\s*([^\)]*)\s*\1\s*\)/i, function( match, quote, url )
492 														{
493 															return url;
494 														});
495 													}
496 													this.setValue( val );
497 												},
498 												commit : function( doc, html, head, body )
499 												{
500 													body.removeAttribute( 'background' );
501 													var val = this.getValue();
502 													if ( val )
503 														body.setStyle( 'background-image', 'url(' + val + ')' );
504 													else
505 														resetStyle( body, 'background-image', 'none' );
506 												}
507 											},
508 											{
509 												type : 'button',
510 												id : 'bgImageChoose',
511 												label : langCommon.browseServer,
512 												style : 'display:inline-block;margin-top:10px;',
513 												hidden : true,
514 												filebrowser : 'design:bgImage'
515 											}
516 										]
517 									},
518 									{
519 										type : 'checkbox',
520 										id : 'bgFixed',
521 										label : lang.bgFixed,
522 										setup : function( doc, html, head, body )
523 										{
524 											this.setValue( body.getComputedStyle( 'background-attachment' ) == 'fixed' );
525 										},
526 										commit : function( doc, html, head, body )
527 										{
528 											if ( this.getValue() )
529 												body.setStyle( 'background-attachment', 'fixed' );
530 											else
531 												resetStyle( body, 'background-attachment', 'scroll' );
532 										}
533 									}
534 								]
535 							},
536 							{
537 								type : 'vbox',
538 								children : [
539 									{
540 										type : 'html',
541 										id : 'marginTitle',
542 										html : '<div style="text-align: center; margin: 0px auto; font-weight: bold">' + lang.margin + '</div>'
543 									},
544 									{
545 										type : 'text',
546 										id : 'marginTop',
547 										label : lang.marginTop,
548 										style : 'width: 80px; text-align: center',
549 										align : 'center',
550 										inputStyle : 'text-align: center',
551 										setup : function( doc, html, head, body )
552 										{
553 											this.setValue( body.getStyle( 'margin-top' ) || body.getAttribute( 'margintop' ) || '' );
554 										},
555 										commit : commitMargin( 'top' )
556 									},
557 									{
558 										type : 'hbox',
559 										children : [
560 											{
561 												type : 'text',
562 												id : 'marginLeft',
563 												label : lang.marginLeft,
564 												style : 'width: 80px; text-align: center',
565 												align : 'center',
566 												inputStyle : 'text-align: center',
567 												setup : function( doc, html, head, body )
568 												{
569 													this.setValue( body.getStyle( 'margin-left' ) || body.getAttribute( 'marginleft' ) || '' );
570 												},
571 												commit : commitMargin( 'left' )
572 											},
573 											{
574 												type : 'text',
575 												id : 'marginRight',
576 												label : lang.marginRight,
577 												style : 'width: 80px; text-align: center',
578 												align : 'center',
579 												inputStyle : 'text-align: center',
580 												setup : function( doc, html, head, body )
581 												{
582 													this.setValue( body.getStyle( 'margin-right' ) || body.getAttribute( 'marginright' ) || '' );
583 												},
584 												commit : commitMargin( 'right' )
585 											}
586 										]
587 									},
588 									{
589 										type : 'text',
590 										id : 'marginBottom',
591 										label : lang.marginBottom,
592 										style : 'width: 80px; text-align: center',
593 										align : 'center',
594 										inputStyle : 'text-align: center',
595 										setup : function( doc, html, head, body )
596 										{
597 											this.setValue( body.getStyle( 'margin-bottom' ) || body.getAttribute( 'marginbottom' ) || '' );
598 										},
599 										commit : commitMargin( 'bottom' )
600 									}
601 								]
602 							}
603 						]
604 					}
605 				]
606 			},
607 			{
608 				id : 'meta',
609 				label : lang.meta,
610 				elements : [
611 					{
612 						type : 'textarea',
613 						id : 'metaKeywords',
614 						label : lang.metaKeywords,
615 						setup : setupMeta( 'keywords' ),
616 						commit : commitMeta( 'keywords' )
617 					},
618 					{
619 						type : 'textarea',
620 						id : 'metaDescription',
621 						label : lang.metaDescription,
622 						setup : setupMeta( 'description' ),
623 						commit : commitMeta( 'description' )
624 					},
625 					{
626 						type : 'text',
627 						id : 'metaAuthor',
628 						label : lang.metaAuthor,
629 						setup : setupMeta( 'author' ),
630 						commit : commitMeta( 'author' )
631 					},
632 					{
633 						type : 'text',
634 						id : 'metaCopyright',
635 						label : lang.metaCopyright,
636 						setup : setupMeta( 'copyright' ),
637 						commit : commitMeta( 'copyright' )
638 					}
639 				]
640 			},
641 			{
642 				id : 'preview',
643 				label : langCommon.preview,
644 				elements : [
645 					{
646 						type : 'html',
647 						id : 'previewHtml',
648 						html : '<iframe src="' + previewSrc + '" style="width: 100%; height: 310px" hidefocus="true" frameborder="0" ' +
649 								'id="cke_docProps_preview_iframe"></iframe>',
650 						onLoad : function()
651 						{
652 							this.getDialog().on( 'selectPage', function( ev )
653 							{
654 								if ( ev.data.page == 'preview' )
655 								{
656 									var self = this;
657 									setTimeout( function()
658 									{
659 										var doc = CKEDITOR.document.getById( 'cke_docProps_preview_iframe' ).getFrameDocument(),
660 											html = doc.getElementsByTag( 'html' ).getItem( 0 ),
661 											head = doc.getHead(),
662 											body = doc.getBody();
663 										self.commitContent( doc, html, head, body, 1 );
664 									}, 50 );
665 								}
666 							});
667 							CKEDITOR.document.getById( 'cke_docProps_preview_iframe' ).getAscendant( 'table' ).setStyle( 'height', '100%' );
668 						}
669 					}
670 				]
671 			}
672 		]
673 	};
674 });
675