Upgrade TinyMCE to v3.4.5
[citadel.git] / webcit / tiny_mce / plugins / fullpage / editor_plugin_src.js
1 /**\r
2  * editor_plugin_src.js\r
3  *\r
4  * Copyright 2009, Moxiecode Systems AB\r
5  * Released under LGPL License.\r
6  *\r
7  * License: http://tinymce.moxiecode.com/license\r
8  * Contributing: http://tinymce.moxiecode.com/contributing\r
9  */\r
10 \r
11 (function() {\r
12         var each = tinymce.each, Node = tinymce.html.Node;\r
13 \r
14         tinymce.create('tinymce.plugins.FullPagePlugin', {\r
15                 init : function(ed, url) {\r
16                         var t = this;\r
17 \r
18                         t.editor = ed;\r
19 \r
20                         // Register commands\r
21                         ed.addCommand('mceFullPageProperties', function() {\r
22                                 ed.windowManager.open({\r
23                                         file : url + '/fullpage.htm',\r
24                                         width : 430 + parseInt(ed.getLang('fullpage.delta_width', 0)),\r
25                                         height : 495 + parseInt(ed.getLang('fullpage.delta_height', 0)),\r
26                                         inline : 1\r
27                                 }, {\r
28                                         plugin_url : url,\r
29                                         data : t._htmlToData()\r
30                                 });\r
31                         });\r
32 \r
33                         // Register buttons\r
34                         ed.addButton('fullpage', {title : 'fullpage.desc', cmd : 'mceFullPageProperties'});\r
35 \r
36                         ed.onBeforeSetContent.add(t._setContent, t);\r
37                         ed.onGetContent.add(t._getContent, t);\r
38                 },\r
39 \r
40                 getInfo : function() {\r
41                         return {\r
42                                 longname : 'Fullpage',\r
43                                 author : 'Moxiecode Systems AB',\r
44                                 authorurl : 'http://tinymce.moxiecode.com',\r
45                                 infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/fullpage',\r
46                                 version : tinymce.majorVersion + "." + tinymce.minorVersion\r
47                         };\r
48                 },\r
49 \r
50                 // Private plugin internal methods\r
51 \r
52                 _htmlToData : function() {\r
53                         var headerFragment = this._parseHeader(), data = {}, nodes, elm, matches, editor = this.editor;\r
54 \r
55                         function getAttr(elm, name) {\r
56                                 var value = elm.attr(name);\r
57 \r
58                                 return value || '';\r
59                         };\r
60 \r
61                         // Default some values\r
62                         data.fontface = editor.getParam("fullpage_default_fontface", "");\r
63                         data.fontsize = editor.getParam("fullpage_default_fontsize", "");\r
64 \r
65                         // Parse XML PI\r
66                         elm = headerFragment.firstChild;\r
67                         if (elm.type == 7) {\r
68                                 data.xml_pi = true;\r
69                                 matches = /encoding="([^"]+)"/.exec(elm.value);\r
70                                 if (matches)\r
71                                         data.docencoding = matches[1];\r
72                         }\r
73 \r
74                         // Parse doctype\r
75                         elm = headerFragment.getAll('#doctype')[0];\r
76                         if (elm)\r
77                                 data.doctype = '<!DOCTYPE' + elm.value + ">"; \r
78 \r
79                         // Parse title element\r
80                         elm = headerFragment.getAll('title')[0];\r
81                         if (elm && elm.firstChild) {\r
82                                 data.metatitle = elm.firstChild.value;\r
83                         }\r
84 \r
85                         // Parse meta elements\r
86                         each(headerFragment.getAll('meta'), function(meta) {\r
87                                 var name = meta.attr('name'), httpEquiv = meta.attr('http-equiv'), matches;\r
88 \r
89                                 if (name)\r
90                                         data['meta' + name.toLowerCase()] = meta.attr('content');\r
91                                 else if (httpEquiv == "Content-Type") {\r
92                                         matches = /charset\s*=\s*(.*)\s*/gi.exec(meta.attr('content'));\r
93 \r
94                                         if (matches)\r
95                                                 data.docencoding = matches[1];\r
96                                 }\r
97                         });\r
98 \r
99                         // Parse html attribs\r
100                         elm = headerFragment.getAll('html')[0];\r
101                         if (elm)\r
102                                 data.langcode = getAttr(elm, 'lang') || getAttr(elm, 'xml:lang');\r
103         \r
104                         // Parse stylesheet\r
105                         elm = headerFragment.getAll('link')[0];\r
106                         if (elm && elm.attr('rel') == 'stylesheet')\r
107                                 data.stylesheet = elm.attr('href');\r
108 \r
109                         // Parse body parts\r
110                         elm = headerFragment.getAll('body')[0];\r
111                         if (elm) {\r
112                                 data.langdir = getAttr(elm, 'dir');\r
113                                 data.style = getAttr(elm, 'style');\r
114                                 data.visited_color = getAttr(elm, 'vlink');\r
115                                 data.link_color = getAttr(elm, 'link');\r
116                                 data.active_color = getAttr(elm, 'alink');\r
117                         }\r
118 \r
119                         return data;\r
120                 },\r
121 \r
122                 _dataToHtml : function(data) {\r
123                         var headerFragment, headElement, html, elm, value, dom = this.editor.dom;\r
124 \r
125                         function setAttr(elm, name, value) {\r
126                                 elm.attr(name, value ? value : undefined);\r
127                         };\r
128 \r
129                         function addHeadNode(node) {\r
130                                 if (headElement.firstChild)\r
131                                         headElement.insert(node, headElement.firstChild);\r
132                                 else\r
133                                         headElement.append(node);\r
134                         };\r
135 \r
136                         headerFragment = this._parseHeader();\r
137                         headElement = headerFragment.getAll('head')[0];\r
138                         if (!headElement) {\r
139                                 elm = headerFragment.getAll('html')[0];\r
140                                 headElement = new Node('head', 1);\r
141 \r
142                                 if (elm.firstChild)\r
143                                         elm.insert(headElement, elm.firstChild, true);\r
144                                 else\r
145                                         elm.append(headElement);\r
146                         }\r
147 \r
148                         // Add/update/remove XML-PI\r
149                         elm = headerFragment.firstChild;\r
150                         if (data.xml_pi) {\r
151                                 value = 'version="1.0"';\r
152 \r
153                                 if (data.docencoding)\r
154                                         value += ' encoding="' + data.docencoding + '"';\r
155 \r
156                                 if (elm.type != 7) {\r
157                                         elm = new Node('xml', 7);\r
158                                         headerFragment.insert(elm, headerFragment.firstChild, true);\r
159                                 }\r
160 \r
161                                 elm.value = value;\r
162                         } else if (elm && elm.type == 7)\r
163                                 elm.remove();\r
164 \r
165                         // Add/update/remove doctype\r
166                         elm = headerFragment.getAll('#doctype')[0];\r
167                         if (data.doctype) {\r
168                                 if (!elm) {\r
169                                         elm = new Node('#doctype', 10);\r
170 \r
171                                         if (data.xml_pi)\r
172                                                 headerFragment.insert(elm, headerFragment.firstChild);\r
173                                         else\r
174                                                 addHeadNode(elm);\r
175                                 }\r
176 \r
177                                 elm.value = data.doctype.substring(9, data.doctype.length - 1);\r
178                         } else if (elm)\r
179                                 elm.remove();\r
180 \r
181                         // Add/update/remove title\r
182                         elm = headerFragment.getAll('title')[0];\r
183                         if (data.metatitle) {\r
184                                 if (!elm) {\r
185                                         elm = new Node('title', 1);\r
186                                         elm.append(new Node('#text', 3)).value = data.metatitle;\r
187                                         addHeadNode(elm);\r
188                                 }\r
189                         }\r
190 \r
191                         // Add meta encoding\r
192                         if (data.docencoding) {\r
193                                 elm = null;\r
194                                 each(headerFragment.getAll('meta'), function(meta) {\r
195                                         if (meta.attr('http-equiv') == 'Content-Type')\r
196                                                 elm = meta;\r
197                                 });\r
198 \r
199                                 if (!elm) {\r
200                                         elm = new Node('meta', 1);\r
201                                         elm.attr('http-equiv', 'Content-Type');\r
202                                         elm.shortEnded = true;\r
203                                         addHeadNode(elm);\r
204                                 }\r
205 \r
206                                 elm.attr('content', 'text/html; charset=' + data.docencoding);\r
207                         }\r
208 \r
209                         // Add/update/remove meta\r
210                         each('keywords,description,author,copyright,robots'.split(','), function(name) {\r
211                                 var nodes = headerFragment.getAll('meta'), i, meta, value = data['meta' + name];\r
212 \r
213                                 for (i = 0; i < nodes.length; i++) {\r
214                                         meta = nodes[i];\r
215 \r
216                                         if (meta.attr('name') == name) {\r
217                                                 if (value)\r
218                                                         meta.attr('content', value);\r
219                                                 else\r
220                                                         meta.remove();\r
221 \r
222                                                 return;\r
223                                         }\r
224                                 }\r
225 \r
226                                 if (value) {\r
227                                         elm = new Node('meta', 1);\r
228                                         elm.attr('name', name);\r
229                                         elm.attr('content', value);\r
230                                         elm.shortEnded = true;\r
231 \r
232                                         addHeadNode(elm);\r
233                                 }\r
234                         });\r
235 \r
236                         // Add/update/delete link\r
237                         elm = headerFragment.getAll('link')[0];\r
238                         if (elm && elm.attr('rel') == 'stylesheet') {\r
239                                 if (data.stylesheet)\r
240                                         elm.attr('href', data.stylesheet);\r
241                                 else\r
242                                         elm.remove();\r
243                         } else if (data.stylesheet) {\r
244                                 elm = new Node('link', 1);\r
245                                 elm.attr({\r
246                                         rel : 'stylesheet',\r
247                                         text : 'text/css',\r
248                                         href : data.stylesheet\r
249                                 });\r
250                                 elm.shortEnded = true;\r
251 \r
252                                 addHeadNode(elm);\r
253                         }\r
254 \r
255                         // Update body attributes\r
256                         elm = headerFragment.getAll('body')[0];\r
257                         if (elm) {\r
258                                 setAttr(elm, 'dir', data.langdir);\r
259                                 setAttr(elm, 'style', data.style);\r
260                                 setAttr(elm, 'vlink', data.visited_color);\r
261                                 setAttr(elm, 'link', data.link_color);\r
262                                 setAttr(elm, 'alink', data.active_color);\r
263 \r
264                                 // Update iframe body as well\r
265                                 dom.setAttribs(this.editor.getBody(), {\r
266                                         style : data.style,\r
267                                         dir : data.dir,\r
268                                         vLink : data.visited_color,\r
269                                         link : data.link_color,\r
270                                         aLink : data.active_color\r
271                                 });\r
272                         }\r
273 \r
274                         // Set html attributes\r
275                         elm = headerFragment.getAll('html')[0];\r
276                         if (elm) {\r
277                                 setAttr(elm, 'lang', data.langcode);\r
278                                 setAttr(elm, 'xml:lang', data.langcode);\r
279                         }\r
280 \r
281                         // Serialize header fragment and crop away body part\r
282                         html = new tinymce.html.Serializer({\r
283                                 validate: false,\r
284                                 indent: true,\r
285                                 apply_source_formatting : true,\r
286                                 indent_before: 'head,html,body,meta,title,script,link,style',\r
287                                 indent_after: 'head,html,body,meta,title,script,link,style'\r
288                         }).serialize(headerFragment);\r
289 \r
290                         this.head = html.substring(0, html.indexOf('</body>'));\r
291                 },\r
292 \r
293                 _parseHeader : function() {\r
294                         // Parse the contents with a DOM parser\r
295                         return new tinymce.html.DomParser({\r
296                                 validate: false,\r
297                                 root_name: '#document'\r
298                         }).parse(this.head);\r
299                 },\r
300 \r
301                 _setContent : function(ed, o) {\r
302                         var self = this, startPos, endPos, content = o.content, headerFragment, styles = '', dom = self.editor.dom, elm;\r
303 \r
304                         function low(s) {\r
305                                 return s.replace(/<\/?[A-Z]+/g, function(a) {\r
306                                         return a.toLowerCase();\r
307                                 })\r
308                         };\r
309 \r
310                         // Ignore raw updated if we already have a head, this will fix issues with undo/redo keeping the head/foot separate\r
311                         if (o.format == 'raw' && self.head)\r
312                                 return;\r
313 \r
314                         if (o.source_view && ed.getParam('fullpage_hide_in_source_view'))\r
315                                 return;\r
316 \r
317                         // Parse out head, body and footer\r
318                         content = content.replace(/<(\/?)BODY/gi, '<$1body');\r
319                         startPos = content.indexOf('<body');\r
320 \r
321                         if (startPos != -1) {\r
322                                 startPos = content.indexOf('>', startPos);\r
323                                 self.head = low(content.substring(0, startPos + 1));\r
324 \r
325                                 endPos = content.indexOf('</body', startPos);\r
326                                 if (endPos == -1)\r
327                                         endPos = content.length;\r
328 \r
329                                 o.content = content.substring(startPos + 1, endPos);\r
330                                 self.foot = low(content.substring(endPos));\r
331                         } else {\r
332                                 self.head = this._getDefaultHeader();\r
333                                 self.foot = '\n</body>\n</html>';\r
334                         }\r
335 \r
336                         // Parse header and update iframe\r
337                         headerFragment = self._parseHeader();\r
338                         each(headerFragment.getAll('style'), function(node) {\r
339                                 if (node.firstChild)\r
340                                         styles += node.firstChild.value;\r
341                         });\r
342 \r
343                         elm = headerFragment.getAll('body')[0];\r
344                         if (elm) {\r
345                                 dom.setAttribs(self.editor.getBody(), {\r
346                                         style : elm.attr('style') || '',\r
347                                         dir : elm.attr('dir') || '',\r
348                                         vLink : elm.attr('vlink') || '',\r
349                                         link : elm.attr('link') || '',\r
350                                         aLink : elm.attr('alink') || ''\r
351                                 });\r
352                         }\r
353 \r
354                         dom.remove('fullpage_styles');\r
355 \r
356                         if (styles) {\r
357                                 dom.add(self.editor.getDoc().getElementsByTagName('head')[0], 'style', {id : 'fullpage_styles'}, styles);\r
358 \r
359                                 // Needed for IE 6/7\r
360                                 elm = dom.get('fullpage_styles');\r
361                                 if (elm.styleSheet)\r
362                                         elm.styleSheet.cssText = styles;\r
363                         }\r
364                 },\r
365 \r
366                 _getDefaultHeader : function() {\r
367                         var header = '', editor = this.editor, value, styles = '';\r
368 \r
369                         if (editor.getParam('fullpage_default_xml_pi'))\r
370                                 header += '<?xml version="1.0" encoding="' + editor.getParam('fullpage_default_encoding', 'ISO-8859-1') + '" ?>\n';\r
371 \r
372                         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">');\r
373                         header += '\n<html>\n<head>\n';\r
374 \r
375                         if (value = editor.getParam('fullpage_default_title'))\r
376                                 header += '<title>' + value + '</title>\n';\r
377 \r
378                         if (value = editor.getParam('fullpage_default_encoding'))\r
379                                 header += '<meta http-equiv="Content-Type" content="text/html; charset=' + value + '" />\n';\r
380 \r
381                         if (value = editor.getParam('fullpage_default_font_family'))\r
382                                 styles += 'font-family: ' + value + ';';\r
383 \r
384                         if (value = editor.getParam('fullpage_default_font_size'))\r
385                                 styles += 'font-size: ' + value + ';';\r
386 \r
387                         if (value = editor.getParam('fullpage_default_text_color'))\r
388                                 styles += 'color: ' + value + ';';\r
389 \r
390                         header += '</head>\n<body' + (styles ? ' style="' + styles + '"' : '') + '>\n';\r
391 \r
392                         return header;\r
393                 },\r
394 \r
395                 _getContent : function(ed, o) {\r
396                         var self = this;\r
397 \r
398                         if (!o.source_view || !ed.getParam('fullpage_hide_in_source_view'))\r
399                                 o.content = tinymce.trim(self.head) + '\n' + tinymce.trim(o.content) + '\n' + tinymce.trim(self.foot);\r
400                 }\r
401         });\r
402 \r
403         // Register plugin\r
404         tinymce.PluginManager.add('fullpage', tinymce.plugins.FullPagePlugin);\r
405 })();\r