bgneal@312: /**
bgneal@312:  * editor_plugin_src.js
bgneal@312:  *
bgneal@312:  * Copyright 2009, Moxiecode Systems AB
bgneal@312:  * Released under LGPL License.
bgneal@312:  *
bgneal@312:  * License: http://tinymce.moxiecode.com/license
bgneal@312:  * Contributing: http://tinymce.moxiecode.com/contributing
bgneal@312:  */
bgneal@312: 
bgneal@312: (function() {
bgneal@442: 	var each = tinymce.each, Node = tinymce.html.Node;
bgneal@442: 
bgneal@312: 	tinymce.create('tinymce.plugins.FullPagePlugin', {
bgneal@312: 		init : function(ed, url) {
bgneal@312: 			var t = this;
bgneal@312: 
bgneal@312: 			t.editor = ed;
bgneal@312: 
bgneal@312: 			// Register commands
bgneal@312: 			ed.addCommand('mceFullPageProperties', function() {
bgneal@312: 				ed.windowManager.open({
bgneal@312: 					file : url + '/fullpage.htm',
bgneal@312: 					width : 430 + parseInt(ed.getLang('fullpage.delta_width', 0)),
bgneal@312: 					height : 495 + parseInt(ed.getLang('fullpage.delta_height', 0)),
bgneal@312: 					inline : 1
bgneal@312: 				}, {
bgneal@312: 					plugin_url : url,
bgneal@442: 					data : t._htmlToData()
bgneal@312: 				});
bgneal@312: 			});
bgneal@312: 
bgneal@312: 			// Register buttons
bgneal@312: 			ed.addButton('fullpage', {title : 'fullpage.desc', cmd : 'mceFullPageProperties'});
bgneal@312: 
bgneal@312: 			ed.onBeforeSetContent.add(t._setContent, t);
bgneal@312: 			ed.onGetContent.add(t._getContent, t);
bgneal@312: 		},
bgneal@312: 
bgneal@312: 		getInfo : function() {
bgneal@312: 			return {
bgneal@312: 				longname : 'Fullpage',
bgneal@312: 				author : 'Moxiecode Systems AB',
bgneal@312: 				authorurl : 'http://tinymce.moxiecode.com',
bgneal@312: 				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/fullpage',
bgneal@312: 				version : tinymce.majorVersion + "." + tinymce.minorVersion
bgneal@312: 			};
bgneal@312: 		},
bgneal@312: 
bgneal@312: 		// Private plugin internal methods
bgneal@312: 
bgneal@442: 		_htmlToData : function() {
bgneal@442: 			var headerFragment = this._parseHeader(), data = {}, nodes, elm, matches, editor = this.editor;
bgneal@312: 
bgneal@442: 			function getAttr(elm, name) {
bgneal@442: 				var value = elm.attr(name);
bgneal@312: 
bgneal@442: 				return value || '';
bgneal@442: 			};
bgneal@312: 
bgneal@442: 			// Default some values
bgneal@442: 			data.fontface = editor.getParam("fullpage_default_fontface", "");
bgneal@442: 			data.fontsize = editor.getParam("fullpage_default_fontsize", "");
bgneal@312: 
bgneal@442: 			// Parse XML PI
bgneal@442: 			elm = headerFragment.firstChild;
bgneal@442: 			if (elm.type == 7) {
bgneal@442: 				data.xml_pi = true;
bgneal@442: 				matches = /encoding="([^"]+)"/.exec(elm.value);
bgneal@442: 				if (matches)
bgneal@442: 					data.docencoding = matches[1];
bgneal@442: 			}
bgneal@312: 
bgneal@442: 			// Parse doctype
bgneal@442: 			elm = headerFragment.getAll('#doctype')[0];
bgneal@442: 			if (elm)
bgneal@442: 				data.doctype = '<!DOCTYPE' + elm.value + ">"; 
bgneal@442: 
bgneal@442: 			// Parse title element
bgneal@442: 			elm = headerFragment.getAll('title')[0];
bgneal@442: 			if (elm && elm.firstChild) {
bgneal@442: 				data.metatitle = elm.firstChild.value;
bgneal@442: 			}
bgneal@442: 
bgneal@442: 			// Parse meta elements
bgneal@442: 			each(headerFragment.getAll('meta'), function(meta) {
bgneal@442: 				var name = meta.attr('name'), httpEquiv = meta.attr('http-equiv'), matches;
bgneal@442: 
bgneal@442: 				if (name)
bgneal@442: 					data['meta' + name.toLowerCase()] = meta.attr('content');
bgneal@442: 				else if (httpEquiv == "Content-Type") {
bgneal@442: 					matches = /charset\s*=\s*(.*)\s*/gi.exec(meta.attr('content'));
bgneal@442: 
bgneal@442: 					if (matches)
bgneal@442: 						data.docencoding = matches[1];
bgneal@442: 				}
bgneal@442: 			});
bgneal@442: 
bgneal@442: 			// Parse html attribs
bgneal@442: 			elm = headerFragment.getAll('html')[0];
bgneal@442: 			if (elm)
bgneal@442: 				data.langcode = getAttr(elm, 'lang') || getAttr(elm, 'xml:lang');
bgneal@442: 	
bgneal@442: 			// Parse stylesheet
bgneal@442: 			elm = headerFragment.getAll('link')[0];
bgneal@442: 			if (elm && elm.attr('rel') == 'stylesheet')
bgneal@442: 				data.stylesheet = elm.attr('href');
bgneal@442: 
bgneal@442: 			// Parse body parts
bgneal@442: 			elm = headerFragment.getAll('body')[0];
bgneal@442: 			if (elm) {
bgneal@442: 				data.langdir = getAttr(elm, 'dir');
bgneal@442: 				data.style = getAttr(elm, 'style');
bgneal@442: 				data.visited_color = getAttr(elm, 'vlink');
bgneal@442: 				data.link_color = getAttr(elm, 'link');
bgneal@442: 				data.active_color = getAttr(elm, 'alink');
bgneal@442: 			}
bgneal@442: 
bgneal@442: 			return data;
bgneal@442: 		},
bgneal@442: 
bgneal@442: 		_dataToHtml : function(data) {
bgneal@442: 			var headerFragment, headElement, html, elm, value, dom = this.editor.dom;
bgneal@442: 
bgneal@442: 			function setAttr(elm, name, value) {
bgneal@442: 				elm.attr(name, value ? value : undefined);
bgneal@442: 			};
bgneal@442: 
bgneal@442: 			function addHeadNode(node) {
bgneal@442: 				if (headElement.firstChild)
bgneal@442: 					headElement.insert(node, headElement.firstChild);
bgneal@442: 				else
bgneal@442: 					headElement.append(node);
bgneal@442: 			};
bgneal@442: 
bgneal@442: 			headerFragment = this._parseHeader();
bgneal@442: 			headElement = headerFragment.getAll('head')[0];
bgneal@442: 			if (!headElement) {
bgneal@442: 				elm = headerFragment.getAll('html')[0];
bgneal@442: 				headElement = new Node('head', 1);
bgneal@442: 
bgneal@442: 				if (elm.firstChild)
bgneal@442: 					elm.insert(headElement, elm.firstChild, true);
bgneal@442: 				else
bgneal@442: 					elm.append(headElement);
bgneal@442: 			}
bgneal@442: 
bgneal@442: 			// Add/update/remove XML-PI
bgneal@442: 			elm = headerFragment.firstChild;
bgneal@442: 			if (data.xml_pi) {
bgneal@442: 				value = 'version="1.0"';
bgneal@442: 
bgneal@442: 				if (data.docencoding)
bgneal@442: 					value += ' encoding="' + data.docencoding + '"';
bgneal@442: 
bgneal@442: 				if (elm.type != 7) {
bgneal@442: 					elm = new Node('xml', 7);
bgneal@442: 					headerFragment.insert(elm, headerFragment.firstChild, true);
bgneal@442: 				}
bgneal@442: 
bgneal@442: 				elm.value = value;
bgneal@442: 			} else if (elm && elm.type == 7)
bgneal@442: 				elm.remove();
bgneal@442: 
bgneal@442: 			// Add/update/remove doctype
bgneal@442: 			elm = headerFragment.getAll('#doctype')[0];
bgneal@442: 			if (data.doctype) {
bgneal@442: 				if (!elm) {
bgneal@442: 					elm = new Node('#doctype', 10);
bgneal@442: 
bgneal@442: 					if (data.xml_pi)
bgneal@442: 						headerFragment.insert(elm, headerFragment.firstChild);
bgneal@442: 					else
bgneal@442: 						addHeadNode(elm);
bgneal@442: 				}
bgneal@442: 
bgneal@442: 				elm.value = data.doctype.substring(9, data.doctype.length - 1);
bgneal@442: 			} else if (elm)
bgneal@442: 				elm.remove();
bgneal@442: 
bgneal@442: 			// Add/update/remove title
bgneal@442: 			elm = headerFragment.getAll('title')[0];
bgneal@442: 			if (data.metatitle) {
bgneal@442: 				if (!elm) {
bgneal@442: 					elm = new Node('title', 1);
bgneal@442: 					elm.append(new Node('#text', 3)).value = data.metatitle;
bgneal@442: 					addHeadNode(elm);
bgneal@442: 				}
bgneal@442: 			}
bgneal@442: 
bgneal@442: 			// Add meta encoding
bgneal@442: 			if (data.docencoding) {
bgneal@442: 				elm = null;
bgneal@442: 				each(headerFragment.getAll('meta'), function(meta) {
bgneal@442: 					if (meta.attr('http-equiv') == 'Content-Type')
bgneal@442: 						elm = meta;
bgneal@442: 				});
bgneal@442: 
bgneal@442: 				if (!elm) {
bgneal@442: 					elm = new Node('meta', 1);
bgneal@442: 					elm.attr('http-equiv', 'Content-Type');
bgneal@442: 					elm.shortEnded = true;
bgneal@442: 					addHeadNode(elm);
bgneal@442: 				}
bgneal@442: 
bgneal@442: 				elm.attr('content', 'text/html; charset=' + data.docencoding);
bgneal@442: 			}
bgneal@442: 
bgneal@442: 			// Add/update/remove meta
bgneal@442: 			each('keywords,description,author,copyright,robots'.split(','), function(name) {
bgneal@442: 				var nodes = headerFragment.getAll('meta'), i, meta, value = data['meta' + name];
bgneal@442: 
bgneal@442: 				for (i = 0; i < nodes.length; i++) {
bgneal@442: 					meta = nodes[i];
bgneal@442: 
bgneal@442: 					if (meta.attr('name') == name) {
bgneal@442: 						if (value)
bgneal@442: 							meta.attr('content', value);
bgneal@442: 						else
bgneal@442: 							meta.remove();
bgneal@442: 
bgneal@442: 						return;
bgneal@312: 					}
bgneal@312: 				}
bgneal@442: 
bgneal@442: 				if (value) {
bgneal@442: 					elm = new Node('meta', 1);
bgneal@442: 					elm.attr('name', name);
bgneal@442: 					elm.attr('content', value);
bgneal@442: 					elm.shortEnded = true;
bgneal@442: 
bgneal@442: 					addHeadNode(elm);
bgneal@442: 				}
bgneal@442: 			});
bgneal@442: 
bgneal@442: 			// Add/update/delete link
bgneal@442: 			elm = headerFragment.getAll('link')[0];
bgneal@442: 			if (elm && elm.attr('rel') == 'stylesheet') {
bgneal@442: 				if (data.stylesheet)
bgneal@442: 					elm.attr('href', data.stylesheet);
bgneal@442: 				else
bgneal@442: 					elm.remove();
bgneal@442: 			} else if (data.stylesheet) {
bgneal@442: 				elm = new Node('link', 1);
bgneal@442: 				elm.attr({
bgneal@442: 					rel : 'stylesheet',
bgneal@442: 					text : 'text/css',
bgneal@442: 					href : data.stylesheet
bgneal@442: 				});
bgneal@442: 				elm.shortEnded = true;
bgneal@442: 
bgneal@442: 				addHeadNode(elm);
bgneal@312: 			}
bgneal@442: 
bgneal@442: 			// Update body attributes
bgneal@442: 			elm = headerFragment.getAll('body')[0];
bgneal@442: 			if (elm) {
bgneal@442: 				setAttr(elm, 'dir', data.langdir);
bgneal@442: 				setAttr(elm, 'style', data.style);
bgneal@442: 				setAttr(elm, 'vlink', data.visited_color);
bgneal@442: 				setAttr(elm, 'link', data.link_color);
bgneal@442: 				setAttr(elm, 'alink', data.active_color);
bgneal@442: 
bgneal@442: 				// Update iframe body as well
bgneal@442: 				dom.setAttribs(this.editor.getBody(), {
bgneal@442: 					style : data.style,
bgneal@442: 					dir : data.dir,
bgneal@442: 					vLink : data.visited_color,
bgneal@442: 					link : data.link_color,
bgneal@442: 					aLink : data.active_color
bgneal@442: 				});
bgneal@442: 			}
bgneal@442: 
bgneal@442: 			// Set html attributes
bgneal@442: 			elm = headerFragment.getAll('html')[0];
bgneal@442: 			if (elm) {
bgneal@442: 				setAttr(elm, 'lang', data.langcode);
bgneal@442: 				setAttr(elm, 'xml:lang', data.langcode);
bgneal@442: 			}
bgneal@442: 
bgneal@442: 			// Serialize header fragment and crop away body part
bgneal@442: 			html = new tinymce.html.Serializer({
bgneal@442: 				validate: false,
bgneal@442: 				indent: true,
bgneal@442: 				apply_source_formatting : true,
bgneal@442: 				indent_before: 'head,html,body,meta,title,script,link,style',
bgneal@442: 				indent_after: 'head,html,body,meta,title,script,link,style'
bgneal@442: 			}).serialize(headerFragment);
bgneal@442: 
bgneal@442: 			this.head = html.substring(0, html.indexOf('</body>'));
bgneal@312: 		},
bgneal@312: 
bgneal@442: 		_parseHeader : function() {
bgneal@442: 			// Parse the contents with a DOM parser
bgneal@442: 			return new tinymce.html.DomParser({
bgneal@442: 				validate: false,
bgneal@442: 				root_name: '#document'
bgneal@442: 			}).parse(this.head);
bgneal@312: 		},
bgneal@312: 
bgneal@312: 		_setContent : function(ed, o) {
bgneal@442: 			var self = this, startPos, endPos, content = o.content, headerFragment, styles = '', dom = self.editor.dom, elm;
bgneal@442: 
bgneal@442: 			function low(s) {
bgneal@442: 				return s.replace(/<\/?[A-Z]+/g, function(a) {
bgneal@442: 					return a.toLowerCase();
bgneal@442: 				})
bgneal@442: 			};
bgneal@312: 
bgneal@312: 			// Ignore raw updated if we already have a head, this will fix issues with undo/redo keeping the head/foot separate
bgneal@442: 			if (o.format == 'raw' && self.head)
bgneal@312: 				return;
bgneal@312: 
bgneal@312: 			if (o.source_view && ed.getParam('fullpage_hide_in_source_view'))
bgneal@312: 				return;
bgneal@312: 
bgneal@312: 			// Parse out head, body and footer
bgneal@442: 			content = content.replace(/<(\/?)BODY/gi, '<$1body');
bgneal@442: 			startPos = content.indexOf('<body');
bgneal@312: 
bgneal@442: 			if (startPos != -1) {
bgneal@442: 				startPos = content.indexOf('>', startPos);
bgneal@442: 				self.head = low(content.substring(0, startPos + 1));
bgneal@312: 
bgneal@442: 				endPos = content.indexOf('</body', startPos);
bgneal@442: 				if (endPos == -1)
bgneal@442: 					endPos = content.length;
bgneal@312: 
bgneal@442: 				o.content = content.substring(startPos + 1, endPos);
bgneal@442: 				self.foot = low(content.substring(endPos));
bgneal@442: 			} else {
bgneal@442: 				self.head = this._getDefaultHeader();
bgneal@442: 				self.foot = '\n</body>\n</html>';
bgneal@442: 			}
bgneal@312: 
bgneal@442: 			// Parse header and update iframe
bgneal@442: 			headerFragment = self._parseHeader();
bgneal@442: 			each(headerFragment.getAll('style'), function(node) {
bgneal@442: 				if (node.firstChild)
bgneal@442: 					styles += node.firstChild.value;
bgneal@442: 			});
bgneal@312: 
bgneal@442: 			elm = headerFragment.getAll('body')[0];
bgneal@442: 			if (elm) {
bgneal@442: 				dom.setAttribs(self.editor.getBody(), {
bgneal@442: 					style : elm.attr('style') || '',
bgneal@442: 					dir : elm.attr('dir') || '',
bgneal@442: 					vLink : elm.attr('vlink') || '',
bgneal@442: 					link : elm.attr('link') || '',
bgneal@442: 					aLink : elm.attr('alink') || ''
bgneal@442: 				});
bgneal@442: 			}
bgneal@312: 
bgneal@442: 			if (styles)
bgneal@442: 				dom.add(self.editor.getDoc().getElementsByTagName('head')[0], 'style', {id : 'fullpage_styles'}, styles);
bgneal@442: 			else
bgneal@442: 				dom.remove('fullpage_styles');
bgneal@442: 		},
bgneal@312: 
bgneal@442: 		_getDefaultHeader : function() {
bgneal@442: 			var header = '', editor = this.editor, value, styles = '';
bgneal@312: 
bgneal@442: 			if (editor.getParam('fullpage_default_xml_pi'))
bgneal@442: 				header += '<?xml version="1.0" encoding="' + editor.getParam('fullpage_default_encoding', 'ISO-8859-1') + '" ?>\n';
bgneal@312: 
bgneal@442: 			header += editor.getParam('fullpage_default_doctype', '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">');
bgneal@442: 			header += '\n<html>\n<head>\n';
bgneal@312: 
bgneal@442: 			if (value = editor.getParam('fullpage_default_title'))
bgneal@442: 				header += '<title>' + v + '</title>\n';
bgneal@312: 
bgneal@442: 			if (value = editor.getParam('fullpage_default_encoding'))
bgneal@442: 				header += '<meta http-equiv="Content-Type" content="text/html; charset=' + value + '" />\n';
bgneal@442: 
bgneal@442: 			if (value = editor.getParam('fullpage_default_font_family'))
bgneal@442: 				styles += 'font-family: ' + value + ';';
bgneal@442: 
bgneal@442: 			if (value = editor.getParam('fullpage_default_font_size'))
bgneal@442: 				styles += 'font-size: ' + value + ';';
bgneal@442: 
bgneal@442: 			if (value = editor.getParam('fullpage_default_text_color'))
bgneal@442: 				styles += 'color: ' + value + ';';
bgneal@442: 
bgneal@442: 			header += '</head>\n<body' + (styles ? ' style="' + styles + '"' : '') + '>\n';
bgneal@442: 
bgneal@442: 			return header;
bgneal@312: 		},
bgneal@312: 
bgneal@312: 		_getContent : function(ed, o) {
bgneal@442: 			var self = this;
bgneal@312: 
bgneal@312: 			if (!o.source_view || !ed.getParam('fullpage_hide_in_source_view'))
bgneal@442: 				o.content = tinymce.trim(self.head) + '\n' + tinymce.trim(o.content) + '\n' + tinymce.trim(self.foot);
bgneal@312: 		}
bgneal@312: 	});
bgneal@312: 
bgneal@312: 	// Register plugin
bgneal@312: 	tinymce.PluginManager.add('fullpage', tinymce.plugins.FullPagePlugin);
bgneal@442: })();