]> git.donarmstrong.com Git - roundcube.git/blob - program/js/tiny_mce/plugins/paste/editor_plugin_src.js
Merge commit 'upstream/0.3' into unstable-import
[roundcube.git] / program / js / tiny_mce / plugins / paste / editor_plugin_src.js
1 /**\r
2  * $Id: editor_plugin_src.js 1104 2009-04-22 12:16:47Z spocke $\r
3  *\r
4  * @author Moxiecode\r
5  * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved.\r
6  */\r
7 \r
8 (function() {\r
9         var each = tinymce.each;\r
10 \r
11         tinymce.create('tinymce.plugins.PastePlugin', {\r
12                 init : function(ed, url) {\r
13                         var t = this, cb;\r
14 \r
15                         t.editor = ed;\r
16                         t.url = url;\r
17 \r
18                         // Setup plugin events\r
19                         t.onPreProcess = new tinymce.util.Dispatcher(t);\r
20                         t.onPostProcess = new tinymce.util.Dispatcher(t);\r
21 \r
22                         // Register default handlers\r
23                         t.onPreProcess.add(t._preProcess);\r
24                         t.onPostProcess.add(t._postProcess);\r
25 \r
26                         // Register optional preprocess handler\r
27                         t.onPreProcess.add(function(pl, o) {\r
28                                 ed.execCallback('paste_preprocess', pl, o);\r
29                         });\r
30 \r
31                         // Register optional postprocess\r
32                         t.onPostProcess.add(function(pl, o) {\r
33                                 ed.execCallback('paste_postprocess', pl, o);\r
34                         });\r
35 \r
36                         // This function executes the process handlers and inserts the contents\r
37                         function process(h) {\r
38                                 var dom = ed.dom, o = {content : h};\r
39 \r
40                                 // Execute pre process handlers\r
41                                 t.onPreProcess.dispatch(t, o);\r
42 \r
43                                 // Create DOM structure\r
44                                 o.node = dom.create('div', 0, o.content);\r
45 \r
46                                 // Execute post process handlers\r
47                                 t.onPostProcess.dispatch(t, o);\r
48 \r
49                                 // Serialize content\r
50                                 o.content = ed.serializer.serialize(o.node, {getInner : 1});\r
51 \r
52                                 // Insert cleaned content. We need to handle insertion of contents containing block elements separatly\r
53                                 if (/<(p|h[1-6]|ul|ol)/.test(o.content))\r
54                                         t._insertBlockContent(ed, dom, o.content);\r
55                                 else\r
56                                         t._insert(o.content);\r
57                         };\r
58 \r
59                         // Add command for external usage\r
60                         ed.addCommand('mceInsertClipboardContent', function(u, v) {\r
61                                 process(v);\r
62                         });\r
63 \r
64                         // This function grabs the contents from the clipboard by adding a\r
65                         // hidden div and placing the caret inside it and after the browser paste\r
66                         // is done it grabs that contents and processes that\r
67                         function grabContent(e) {\r
68                                 var n, or, rng, sel = ed.selection, dom = ed.dom, body = ed.getBody(), posY;\r
69 \r
70                                 if (dom.get('_mcePaste'))\r
71                                         return;\r
72 \r
73                                 // Create container to paste into\r
74                                 n = dom.add(body, 'div', {id : '_mcePaste'}, '&nbsp;');\r
75 \r
76                                 // If contentEditable mode we need to find out the position of the closest element\r
77                                 if (body != ed.getDoc().body)\r
78                                         posY = dom.getPos(ed.selection.getStart(), body).y;\r
79                                 else\r
80                                         posY = body.scrollTop;\r
81 \r
82                                 // Styles needs to be applied after the element is added to the document since WebKit will otherwise remove all styles\r
83                                 dom.setStyles(n, {\r
84                                         position : 'absolute',\r
85                                         left : -10000,\r
86                                         top : posY,\r
87                                         width : 1,\r
88                                         height : 1,\r
89                                         overflow : 'hidden'\r
90                                 });\r
91 \r
92                                 if (tinymce.isIE) {\r
93                                         // Select the container\r
94                                         rng = dom.doc.body.createTextRange();\r
95                                         rng.moveToElementText(n);\r
96                                         rng.execCommand('Paste');\r
97 \r
98                                         // Remove container\r
99                                         dom.remove(n);\r
100 \r
101                                         // Process contents\r
102                                         process(n.innerHTML);\r
103 \r
104                                         return tinymce.dom.Event.cancel(e);\r
105                                 } else {\r
106                                         or = ed.selection.getRng();\r
107 \r
108                                         // Move caret into hidden div\r
109                                         n = n.firstChild;\r
110                                         rng = ed.getDoc().createRange();\r
111                                         rng.setStart(n, 0);\r
112                                         rng.setEnd(n, 1);\r
113                                         sel.setRng(rng);\r
114 \r
115                                         // Wait a while and grab the pasted contents\r
116                                         window.setTimeout(function() {\r
117                                                 var n = dom.get('_mcePaste'), h;\r
118 \r
119                                                 // Webkit clones the _mcePaste div for some odd reason so this will ensure that we get the real new div not the old empty one\r
120                                                 n.id = '_mceRemoved';\r
121                                                 dom.remove(n);\r
122                                                 n = dom.get('_mcePaste') || n;\r
123 \r
124                                                 // Grab the HTML contents\r
125                                                 // We need to look for a apple style wrapper on webkit it also adds a div wrapper if you copy/paste the body of the editor\r
126                                                 // It's amazing how strange the contentEditable mode works in WebKit\r
127                                                 h = (dom.select('> span.Apple-style-span div', n)[0] || dom.select('> span.Apple-style-span', n)[0] || n).innerHTML;\r
128 \r
129                                                 // Remove hidden div and restore selection\r
130                                                 dom.remove(n);\r
131 \r
132                                                 // Restore the old selection\r
133                                                 if (or)\r
134                                                         sel.setRng(or);\r
135 \r
136                                                 process(h);\r
137                                         }, 0);\r
138                                 }\r
139                         };\r
140 \r
141                         // Check if we should use the new auto process method                   \r
142                         if (ed.getParam('paste_auto_cleanup_on_paste', true)) {\r
143                                 // Is it's Opera or older FF use key handler\r
144                                 if (tinymce.isOpera || /Firefox\/2/.test(navigator.userAgent)) {\r
145                                         ed.onKeyDown.add(function(ed, e) {\r
146                                                 if (((tinymce.isMac ? e.metaKey : e.ctrlKey) && e.keyCode == 86) || (e.shiftKey && e.keyCode == 45))\r
147                                                         grabContent(e);\r
148                                         });\r
149                                 } else {\r
150                                         // Grab contents on paste event on Gecko and WebKit\r
151                                         ed.onPaste.addToTop(function(ed, e) {\r
152                                                 return grabContent(e);\r
153                                         });\r
154                                 }\r
155                         }\r
156 \r
157                         // Add legacy support\r
158                         t._legacySupport();\r
159                 },\r
160 \r
161                 getInfo : function() {\r
162                         return {\r
163                                 longname : 'Paste text/word',\r
164                                 author : 'Moxiecode Systems AB',\r
165                                 authorurl : 'http://tinymce.moxiecode.com',\r
166                                 infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/paste',\r
167                                 version : tinymce.majorVersion + "." + tinymce.minorVersion\r
168                         };\r
169                 },\r
170 \r
171                 _preProcess : function(pl, o) {\r
172                         var h = o.content, process;\r
173 \r
174                         //console.log('Before preprocess:' + o.content);\r
175 \r
176                         function process(items) {\r
177                                 each(items, function(v) {\r
178                                         // Remove or replace\r
179                                         if (v.constructor == RegExp)\r
180                                                 h = h.replace(v, '');\r
181                                         else\r
182                                                 h = h.replace(v[0], v[1]);\r
183                                 });\r
184                         };\r
185 \r
186                         // Process away some basic content\r
187                         process([\r
188                                 /^\s*(&nbsp;)+/g,                                                                                       // nbsp entities at the start of contents\r
189                                 /(&nbsp;|<br[^>]*>)+\s*$/g                                                                      // nbsp entities at the end of contents\r
190                         ]);\r
191 \r
192                         // Detect Word content and process it more agressive\r
193                         if (/(class=\"?Mso|style=\"[^\"]*\bmso\-|w:WordDocument)/.test(h)) {\r
194                                 o.wordContent = true; // Mark the pasted contents as word specific content\r
195                                 //console.log('Word contents detected.');\r
196 \r
197                                 process([\r
198                                         /<!--[\s\S]+?-->/gi,                                                                                            // Word comments\r
199                                         /<\/?(img|font|meta|link|style|span|div|v:\w+)[^>]*>/gi,                        // Remove some tags including VML content\r
200                                         /<\\?\?xml[^>]*>/gi,                                                                                            // XML namespace declarations\r
201                                         /<\/?o:[^>]*>/gi,                                                                                                       // MS namespaced elements <o:tag>\r
202                                         / (id|name|class|language|type|on\w+|v:\w+)=\"([^\"]*)\"/gi,    // on.., class, style and language attributes with quotes\r
203                                         / (id|name|class|language|type|on\w+|v:\w+)=(\w+)/gi,                   // on.., class, style and language attributes without quotes (IE)\r
204                                         [/<(\/?)s>/gi, '<$1strike>'],                                                                           // Convert <s> into <strike> for line-though\r
205                                         /<script[^>]+>[\s\S]*?<\/script>/gi,                                                            // All scripts elements for msoShowComment for example\r
206                                         [/&nbsp;/g, '\u00a0']                                                                                           // Replace nsbp entites to char since it's easier to handle\r
207                                 ]);\r
208                         }\r
209 \r
210                         //console.log('After preprocess:' + h);\r
211 \r
212                         o.content = h;\r
213                 },\r
214 \r
215                 /**\r
216                  * Various post process items.\r
217                  */\r
218                 _postProcess : function(pl, o) {\r
219                         var t = this, dom = t.editor.dom;\r
220 \r
221                         if (o.wordContent) {\r
222                                 // Remove named anchors or TOC links\r
223                                 each(dom.select('a', o.node), function(a) {\r
224                                         if (!a.href || a.href.indexOf('#_Toc') != -1)\r
225                                                 dom.remove(a, 1);\r
226                                 });\r
227 \r
228                                 if (t.editor.getParam('paste_convert_middot_lists', true))\r
229                                         t._convertLists(pl, o);\r
230 \r
231                                 // Remove all styles\r
232                                 each(dom.select('*', o.node), function(el) {\r
233                                         dom.setAttrib(el, 'style', '');\r
234                                 });\r
235                         }\r
236 \r
237                         if (tinymce.isWebKit) {\r
238                                 // We need to compress the styles on WebKit since if you paste <img border="0" /> it will become <img border="0" style="... lots of junk ..." />\r
239                                 // Removing the mce_style that contains the real value will force the Serializer engine to compress the styles\r
240                                 each(dom.select('*', o.node), function(el) {\r
241                                         el.removeAttribute('mce_style');\r
242                                 });\r
243                         }\r
244                 },\r
245 \r
246                 /**\r
247                  * Converts the most common bullet and number formats in Office into a real semantic UL/LI list.\r
248                  */\r
249                 _convertLists : function(pl, o) {\r
250                         var dom = pl.editor.dom, listElm, li, lastMargin = -1, margin, levels = [], lastType;\r
251 \r
252                         // Convert middot lists into real scemantic lists\r
253                         each(dom.select('p', o.node), function(p) {\r
254                                 var sib, val = '', type, html, idx, parents;\r
255 \r
256                                 // Get text node value at beginning of paragraph\r
257                                 for (sib = p.firstChild; sib && sib.nodeType == 3; sib = sib.nextSibling)\r
258                                         val += sib.nodeValue;\r
259 \r
260                                 // Detect unordered lists look for bullets\r
261                                 if (/^[\u2022\u00b7\u00a7\u00d8o]\s*\u00a0\u00a0*/.test(val))\r
262                                         type = 'ul';\r
263 \r
264                                 // Detect ordered lists 1., a. or ixv.\r
265                                 if (/^[\s\S]*\w+\.[\s\S]*\u00a0{2,}/.test(val))\r
266                                         type = 'ol';\r
267 \r
268                                 // Check if node value matches the list pattern: o&nbsp;&nbsp;\r
269                                 if (type) {\r
270                                         margin = parseFloat(p.style.marginLeft || 0);\r
271 \r
272                                         if (margin > lastMargin)\r
273                                                 levels.push(margin);\r
274 \r
275                                         if (!listElm || type != lastType) {\r
276                                                 listElm = dom.create(type);\r
277                                                 dom.insertAfter(listElm, p);\r
278                                         } else {\r
279                                                 // Nested list element\r
280                                                 if (margin > lastMargin) {\r
281                                                         listElm = li.appendChild(dom.create(type));\r
282                                                 } else if (margin < lastMargin) {\r
283                                                         // Find parent level based on margin value\r
284                                                         idx = tinymce.inArray(levels, margin);\r
285                                                         parents = dom.getParents(listElm.parentNode, type);\r
286                                                         listElm = parents[parents.length - 1 - idx] || listElm;\r
287                                                 }\r
288                                         }\r
289 \r
290                                         if (type == 'ul')\r
291                                                 html = p.innerHTML.replace(/^[\u2022\u00b7\u00a7\u00d8o]\s*(&nbsp;|\u00a0)+\s*/, '');\r
292                                         else\r
293                                                 html = p.innerHTML.replace(/^[\s\S]*\w+\.(&nbsp;|\u00a0)+\s*/, '');\r
294 \r
295                                         li = listElm.appendChild(dom.create('li', 0, html));\r
296                                         dom.remove(p);\r
297 \r
298                                         lastMargin = margin;\r
299                                         lastType = type;\r
300                                 } else\r
301                                         listElm = lastMargin = 0; // End list element\r
302                         });\r
303                 },\r
304 \r
305                 /**\r
306                  * This method will split the current block parent and insert the contents inside the split position.\r
307                  * This logic can be improved so text nodes at the start/end remain in the start/end block elements\r
308                  */\r
309                 _insertBlockContent : function(ed, dom, content) {\r
310                         var parentBlock, marker, sel = ed.selection, last, elm, vp, y, elmHeight;\r
311 \r
312                         function select(n) {\r
313                                 var r;\r
314 \r
315                                 if (tinymce.isIE) {\r
316                                         r = ed.getDoc().body.createTextRange();\r
317                                         r.moveToElementText(n);\r
318                                         r.collapse(false);\r
319                                         r.select();\r
320                                 } else {\r
321                                         sel.select(n, 1);\r
322                                         sel.collapse(false);\r
323                                 }\r
324                         };\r
325 \r
326                         // Insert a marker for the caret position\r
327                         this._insert('<span id="_marker">&nbsp;</span>', 1);\r
328                         marker = dom.get('_marker');\r
329                         parentBlock = dom.getParent(marker, 'p,h1,h2,h3,h4,h5,h6,ul,ol');\r
330 \r
331                         if (parentBlock) {\r
332                                 // Split parent block\r
333                                 marker = dom.split(parentBlock, marker);\r
334 \r
335                                 // Insert nodes before the marker\r
336                                 each(dom.create('div', 0, content).childNodes, function(n) {\r
337                                         last = marker.parentNode.insertBefore(n.cloneNode(true), marker);\r
338                                 });\r
339 \r
340                                 // Move caret after marker\r
341                                 select(last);\r
342                         } else {\r
343                                 dom.setOuterHTML(marker, content);\r
344                                 sel.select(ed.getBody(), 1);\r
345                                 sel.collapse(0);\r
346                         }\r
347 \r
348                         dom.remove('_marker'); // Remove marker if it's left\r
349 \r
350                         // Get element, position and height\r
351                         elm = sel.getStart();\r
352                         vp = dom.getViewPort(ed.getWin());\r
353                         y = ed.dom.getPos(elm).y;\r
354                         elmHeight = elm.clientHeight;\r
355 \r
356                         // Is element within viewport if not then scroll it into view\r
357                         if (y < vp.y || y + elmHeight > vp.y + vp.h)\r
358                                 ed.getDoc().body.scrollTop = y < vp.y ? y : y - vp.h + 25;\r
359                 },\r
360 \r
361                 /**\r
362                  * Inserts the specified contents at the caret position.\r
363                  */\r
364                 _insert : function(h, skip_undo) {\r
365                         var ed = this.editor;\r
366 \r
367                         // First delete the contents seems to work better on WebKit\r
368                         ed.execCommand('Delete');\r
369 \r
370                         // It's better to use the insertHTML method on Gecko since it will combine paragraphs correctly before inserting the contents\r
371                         ed.execCommand(tinymce.isGecko ? 'insertHTML' : 'mceInsertContent', false, h, {skip_undo : skip_undo});\r
372                 },\r
373 \r
374                 /**\r
375                  * This method will open the old style paste dialogs. Some users might want the old behavior but still use the new cleanup engine.\r
376                  */\r
377                 _legacySupport : function() {\r
378                         var t = this, ed = t.editor;\r
379 \r
380                         // Register commands for backwards compatibility\r
381                         each(['mcePasteText', 'mcePasteWord'], function(cmd) {\r
382                                 ed.addCommand(cmd, function() {\r
383                                         ed.windowManager.open({\r
384                                                 file : t.url + (cmd == 'mcePasteText' ? '/pastetext.htm' : '/pasteword.htm'),\r
385                                                 width : 450,\r
386                                                 height : 400,\r
387                                                 inline : 1\r
388                                         });\r
389                                 });\r
390                         });\r
391 \r
392                         // Register buttons for backwards compatibility\r
393                         ed.addButton('pastetext', {title : 'paste.paste_text_desc', cmd : 'mcePasteText'});\r
394                         ed.addButton('pasteword', {title : 'paste.paste_word_desc', cmd : 'mcePasteWord'});\r
395                         ed.addButton('selectall', {title : 'paste.selectall_desc', cmd : 'selectall'});\r
396                 }\r
397         });\r
398 \r
399         // Register plugin\r
400         tinymce.PluginManager.add('paste', tinymce.plugins.PastePlugin);\r
401 })();