Mercurial > public > sg101
comparison media/js/tiny_mce/plugins/paste/editor_plugin_src.js @ 183:149c3567fec1
Updated to TinyMCE version 3.3.2. This is for #57.
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Sun, 28 Mar 2010 21:47:48 +0000 |
parents | a5b4c5ce0658 |
children | 237710206167 |
comparison
equal
deleted
inserted
replaced
182:5c889b587416 | 183:149c3567fec1 |
---|---|
1 /** | 1 /** |
2 * $Id: editor_plugin_src.js 919 2008-09-08 20:31:23Z spocke $ | 2 * editor_plugin_src.js |
3 * | 3 * |
4 * @author Moxiecode | 4 * Copyright 2009, Moxiecode Systems AB |
5 * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. | 5 * Released under LGPL License. |
6 * | |
7 * License: http://tinymce.moxiecode.com/license | |
8 * Contributing: http://tinymce.moxiecode.com/contributing | |
6 */ | 9 */ |
7 | 10 |
8 (function() { | 11 (function() { |
9 var Event = tinymce.dom.Event; | 12 var each = tinymce.each, |
13 entities = null, | |
14 defs = { | |
15 paste_auto_cleanup_on_paste : true, | |
16 paste_block_drop : false, | |
17 paste_retain_style_properties : "none", | |
18 paste_strip_class_attributes : "mso", | |
19 paste_remove_spans : false, | |
20 paste_remove_styles : false, | |
21 paste_remove_styles_if_webkit : true, | |
22 paste_convert_middot_lists : true, | |
23 paste_convert_headers_to_strong : false, | |
24 paste_dialog_width : "450", | |
25 paste_dialog_height : "400", | |
26 paste_text_use_dialog : false, | |
27 paste_text_sticky : false, | |
28 paste_text_notifyalways : false, | |
29 paste_text_linebreaktype : "p", | |
30 paste_text_replacements : [ | |
31 [/\u2026/g, "..."], | |
32 [/[\x93\x94\u201c\u201d]/g, '"'], | |
33 [/[\x60\x91\x92\u2018\u2019]/g, "'"] | |
34 ] | |
35 }; | |
36 | |
37 function getParam(ed, name) { | |
38 return ed.getParam(name, defs[name]); | |
39 } | |
10 | 40 |
11 tinymce.create('tinymce.plugins.PastePlugin', { | 41 tinymce.create('tinymce.plugins.PastePlugin', { |
12 init : function(ed, url) { | 42 init : function(ed, url) { |
13 var t = this; | 43 var t = this; |
14 | 44 |
15 t.editor = ed; | 45 t.editor = ed; |
16 | 46 t.url = url; |
17 // Register commands | 47 |
18 ed.addCommand('mcePasteText', function(ui, v) { | 48 // Setup plugin events |
19 if (ui) { | 49 t.onPreProcess = new tinymce.util.Dispatcher(t); |
20 if ((ed.getParam('paste_use_dialog', true)) || (!tinymce.isIE)) { | 50 t.onPostProcess = new tinymce.util.Dispatcher(t); |
21 ed.windowManager.open({ | 51 |
22 file : url + '/pastetext.htm', | 52 // Register default handlers |
23 width : 450, | 53 t.onPreProcess.add(t._preProcess); |
24 height : 400, | 54 t.onPostProcess.add(t._postProcess); |
25 inline : 1 | 55 |
26 }, { | 56 // Register optional preprocess handler |
27 plugin_url : url | 57 t.onPreProcess.add(function(pl, o) { |
28 }); | 58 ed.execCallback('paste_preprocess', pl, o); |
29 } else | |
30 t._insertText(clipboardData.getData("Text"), true); | |
31 } else | |
32 t._insertText(v.html, v.linebreaks); | |
33 }); | 59 }); |
34 | 60 |
35 ed.addCommand('mcePasteWord', function(ui, v) { | 61 // Register optional postprocess |
36 if (ui) { | 62 t.onPostProcess.add(function(pl, o) { |
37 if ((ed.getParam('paste_use_dialog', true)) || (!tinymce.isIE)) { | 63 ed.execCallback('paste_postprocess', pl, o); |
38 ed.windowManager.open({ | |
39 file : url + '/pasteword.htm', | |
40 width : 450, | |
41 height : 400, | |
42 inline : 1 | |
43 }, { | |
44 plugin_url : url | |
45 }); | |
46 } else | |
47 t._insertText(t._clipboardHTML()); | |
48 } else | |
49 t._insertWordContent(v); | |
50 }); | 64 }); |
51 | 65 |
52 ed.addCommand('mceSelectAll', function() { | 66 // Initialize plain text flag |
53 ed.execCommand('selectall'); | 67 ed.pasteAsPlainText = false; |
68 | |
69 // This function executes the process handlers and inserts the contents | |
70 // force_rich overrides plain text mode set by user, important for pasting with execCommand | |
71 function process(o, force_rich) { | |
72 var dom = ed.dom; | |
73 | |
74 // Execute pre process handlers | |
75 t.onPreProcess.dispatch(t, o); | |
76 | |
77 // Create DOM structure | |
78 o.node = dom.create('div', 0, o.content); | |
79 | |
80 // Execute post process handlers | |
81 t.onPostProcess.dispatch(t, o); | |
82 | |
83 // Serialize content | |
84 o.content = ed.serializer.serialize(o.node, {getInner : 1}); | |
85 | |
86 // Plain text option active? | |
87 if ((!force_rich) && (ed.pasteAsPlainText)) { | |
88 t._insertPlainText(ed, dom, o.content); | |
89 | |
90 if (!getParam(ed, "paste_text_sticky")) { | |
91 ed.pasteAsPlainText = false; | |
92 ed.controlManager.setActive("pastetext", false); | |
93 } | |
94 } else if (/<(p|h[1-6]|ul|ol)/.test(o.content)) { | |
95 // Handle insertion of contents containing block elements separately | |
96 t._insertBlockContent(ed, dom, o.content); | |
97 } else { | |
98 t._insert(o.content); | |
99 } | |
100 } | |
101 | |
102 // Add command for external usage | |
103 ed.addCommand('mceInsertClipboardContent', function(u, o) { | |
104 process(o, true); | |
54 }); | 105 }); |
55 | 106 |
56 // Register buttons | 107 if (!getParam(ed, "paste_text_use_dialog")) { |
57 ed.addButton('pastetext', {title : 'paste.paste_text_desc', cmd : 'mcePasteText', ui : true}); | 108 ed.addCommand('mcePasteText', function(u, v) { |
58 ed.addButton('pasteword', {title : 'paste.paste_word_desc', cmd : 'mcePasteWord', ui : true}); | 109 var cookie = tinymce.util.Cookie; |
59 ed.addButton('selectall', {title : 'paste.selectall_desc', cmd : 'mceSelectAll'}); | 110 |
60 | 111 ed.pasteAsPlainText = !ed.pasteAsPlainText; |
61 if (ed.getParam("paste_auto_cleanup_on_paste", false)) { | 112 ed.controlManager.setActive('pastetext', ed.pasteAsPlainText); |
62 ed.onPaste.add(function(ed, e) { | 113 |
63 return t._handlePasteEvent(e) | 114 if ((ed.pasteAsPlainText) && (!cookie.get("tinymcePasteText"))) { |
64 }); | 115 if (getParam(ed, "paste_text_sticky")) { |
65 } | 116 ed.windowManager.alert("Paste is now in plain text mode. Click again to toggle back to regular paste mode. After you paste something you will be returned to regular paste mode."); |
66 | 117 } else { |
67 if (!tinymce.isIE && ed.getParam("paste_auto_cleanup_on_paste", false)) { | 118 ed.windowManager.alert("Paste is now in plain text mode. Click again to toggle back to regular paste mode."); |
68 // Force paste dialog if non IE browser | 119 } |
69 ed.onKeyDown.add(function(ed, e) { | 120 |
70 if (e.ctrlKey && e.keyCode == 86) { | 121 if (!getParam(ed, "paste_text_notifyalways")) { |
71 window.setTimeout(function() { | 122 cookie.set("tinymcePasteText", "1", new Date(new Date().getFullYear() + 1, 12, 31)) |
72 ed.execCommand("mcePasteText", true); | 123 } |
73 }, 1); | |
74 | |
75 Event.cancel(e); | |
76 } | 124 } |
77 }); | 125 }); |
78 } | 126 } |
127 | |
128 ed.addButton('pastetext', {title: 'paste.paste_text_desc', cmd: 'mcePasteText'}); | |
129 ed.addButton('selectall', {title: 'paste.selectall_desc', cmd: 'selectall'}); | |
130 | |
131 // This function grabs the contents from the clipboard by adding a | |
132 // hidden div and placing the caret inside it and after the browser paste | |
133 // is done it grabs that contents and processes that | |
134 function grabContent(e) { | |
135 var n, or, rng, sel = ed.selection, dom = ed.dom, body = ed.getBody(), posY; | |
136 | |
137 if (dom.get('_mcePaste')) | |
138 return; | |
139 | |
140 // Create container to paste into | |
141 n = dom.add(body, 'div', {id : '_mcePaste', 'class' : 'mcePaste'}, '\uFEFF'); | |
142 | |
143 // If contentEditable mode we need to find out the position of the closest element | |
144 if (body != ed.getDoc().body) | |
145 posY = dom.getPos(ed.selection.getStart(), body).y; | |
146 else | |
147 posY = body.scrollTop; | |
148 | |
149 // Styles needs to be applied after the element is added to the document since WebKit will otherwise remove all styles | |
150 dom.setStyles(n, { | |
151 position : 'absolute', | |
152 left : -10000, | |
153 top : posY, | |
154 width : 1, | |
155 height : 1, | |
156 overflow : 'hidden' | |
157 }); | |
158 | |
159 if (tinymce.isIE) { | |
160 // Select the container | |
161 rng = dom.doc.body.createTextRange(); | |
162 rng.moveToElementText(n); | |
163 rng.execCommand('Paste'); | |
164 | |
165 // Remove container | |
166 dom.remove(n); | |
167 | |
168 // Check if the contents was changed, if it wasn't then clipboard extraction failed probably due | |
169 // to IE security settings so we pass the junk though better than nothing right | |
170 if (n.innerHTML === '\uFEFF') { | |
171 ed.execCommand('mcePasteWord'); | |
172 e.preventDefault(); | |
173 return; | |
174 } | |
175 | |
176 // Process contents | |
177 process({content : n.innerHTML}); | |
178 | |
179 // Block the real paste event | |
180 return tinymce.dom.Event.cancel(e); | |
181 } else { | |
182 function block(e) { | |
183 e.preventDefault(); | |
184 }; | |
185 | |
186 // Block mousedown and click to prevent selection change | |
187 dom.bind(ed.getDoc(), 'mousedown', block); | |
188 dom.bind(ed.getDoc(), 'keydown', block); | |
189 | |
190 or = ed.selection.getRng(); | |
191 | |
192 // Move caret into hidden div | |
193 n = n.firstChild; | |
194 rng = ed.getDoc().createRange(); | |
195 rng.setStart(n, 0); | |
196 rng.setEnd(n, 1); | |
197 sel.setRng(rng); | |
198 | |
199 // Wait a while and grab the pasted contents | |
200 window.setTimeout(function() { | |
201 var h = '', nl = dom.select('div.mcePaste'); | |
202 | |
203 // WebKit will split the div into multiple ones so this will loop through then all and join them to get the whole HTML string | |
204 each(nl, function(n) { | |
205 // WebKit duplicates the divs so we need to remove them | |
206 each(dom.select('div.mcePaste', n), function(n) { | |
207 dom.remove(n, 1); | |
208 }); | |
209 | |
210 // Contents in WebKit is sometimes wrapped in a apple style span so we need to grab it from that one | |
211 h += (dom.select('> span.Apple-style-span div', n)[0] || dom.select('> span.Apple-style-span', n)[0] || n).innerHTML; | |
212 }); | |
213 | |
214 // Remove the nodes | |
215 each(nl, function(n) { | |
216 dom.remove(n); | |
217 }); | |
218 | |
219 // Restore the old selection | |
220 if (or) | |
221 sel.setRng(or); | |
222 | |
223 process({content : h}); | |
224 | |
225 // Unblock events ones we got the contents | |
226 dom.unbind(ed.getDoc(), 'mousedown', block); | |
227 dom.unbind(ed.getDoc(), 'keydown', block); | |
228 }, 0); | |
229 } | |
230 } | |
231 | |
232 // Check if we should use the new auto process method | |
233 if (getParam(ed, "paste_auto_cleanup_on_paste")) { | |
234 // Is it's Opera or older FF use key handler | |
235 if (tinymce.isOpera || /Firefox\/2/.test(navigator.userAgent)) { | |
236 ed.onKeyDown.add(function(ed, e) { | |
237 if (((tinymce.isMac ? e.metaKey : e.ctrlKey) && e.keyCode == 86) || (e.shiftKey && e.keyCode == 45)) | |
238 grabContent(e); | |
239 }); | |
240 } else { | |
241 // Grab contents on paste event on Gecko and WebKit | |
242 ed.onPaste.addToTop(function(ed, e) { | |
243 return grabContent(e); | |
244 }); | |
245 } | |
246 } | |
247 | |
248 // Block all drag/drop events | |
249 if (getParam(ed, "paste_block_drop")) { | |
250 ed.onInit.add(function() { | |
251 ed.dom.bind(ed.getBody(), ['dragend', 'dragover', 'draggesture', 'dragdrop', 'drop', 'drag'], function(e) { | |
252 e.preventDefault(); | |
253 e.stopPropagation(); | |
254 | |
255 return false; | |
256 }); | |
257 }); | |
258 } | |
259 | |
260 // Add legacy support | |
261 t._legacySupport(); | |
79 }, | 262 }, |
80 | 263 |
81 getInfo : function() { | 264 getInfo : function() { |
82 return { | 265 return { |
83 longname : 'Paste text/word', | 266 longname : 'Paste text/word', |
86 infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/paste', | 269 infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/paste', |
87 version : tinymce.majorVersion + "." + tinymce.minorVersion | 270 version : tinymce.majorVersion + "." + tinymce.minorVersion |
88 }; | 271 }; |
89 }, | 272 }, |
90 | 273 |
91 // Private methods | 274 _preProcess : function(pl, o) { |
92 | 275 //console.log('Before preprocess:' + o.content); |
93 _handlePasteEvent : function(e) { | 276 |
94 var html = this._clipboardHTML(), ed = this.editor, sel = ed.selection, r; | 277 var ed = this.editor, |
95 | 278 h = o.content, |
96 // Removes italic, strong etc, the if was needed due to bug #1437114 | 279 grep = tinymce.grep, |
97 if (ed && (r = sel.getRng()) && r.text.length > 0) | 280 explode = tinymce.explode, |
98 ed.execCommand('delete'); | 281 trim = tinymce.trim, |
99 | 282 len, stripClass; |
100 if (html && html.length > 0) | 283 |
101 ed.execCommand('mcePasteWord', false, html); | 284 function process(items) { |
102 | 285 each(items, function(v) { |
103 return Event.cancel(e); | 286 // Remove or replace |
287 if (v.constructor == RegExp) | |
288 h = h.replace(v, ''); | |
289 else | |
290 h = h.replace(v[0], v[1]); | |
291 }); | |
292 } | |
293 | |
294 // Detect Word content and process it more aggressive | |
295 if (/class="?Mso|style="[^"]*\bmso-|w:WordDocument/i.test(h) || o.wordContent) { | |
296 o.wordContent = true; // Mark the pasted contents as word specific content | |
297 //console.log('Word contents detected.'); | |
298 | |
299 // Process away some basic content | |
300 process([ | |
301 /^\s*( )+/gi, // entities at the start of contents | |
302 /( |<br[^>]*>)+\s*$/gi // entities at the end of contents | |
303 ]); | |
304 | |
305 if (getParam(ed, "paste_convert_headers_to_strong")) { | |
306 h = h.replace(/<p [^>]*class="?MsoHeading"?[^>]*>(.*?)<\/p>/gi, "<p><strong>$1</strong></p>"); | |
307 } | |
308 | |
309 if (getParam(ed, "paste_convert_middot_lists")) { | |
310 process([ | |
311 [/<!--\[if !supportLists\]-->/gi, '$&__MCE_ITEM__'], // Convert supportLists to a list item marker | |
312 [/(<span[^>]+(?:mso-list:|:\s*symbol)[^>]+>)/gi, '$1__MCE_ITEM__'] // Convert mso-list and symbol spans to item markers | |
313 ]); | |
314 } | |
315 | |
316 process([ | |
317 // Word comments like conditional comments etc | |
318 /<!--[\s\S]+?-->/gi, | |
319 | |
320 // Remove comments, scripts (e.g., msoShowComment), XML tag, VML content, MS Office namespaced tags, and a few other tags | |
321 /<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi, | |
322 | |
323 // Convert <s> into <strike> for line-though | |
324 [/<(\/?)s>/gi, "<$1strike>"], | |
325 | |
326 // Replace nsbp entites to char since it's easier to handle | |
327 [/ /gi, "\u00a0"] | |
328 ]); | |
329 | |
330 // Remove bad attributes, with or without quotes, ensuring that attribute text is really inside a tag. | |
331 // If JavaScript had a RegExp look-behind, we could have integrated this with the last process() array and got rid of the loop. But alas, it does not, so we cannot. | |
332 do { | |
333 len = h.length; | |
334 h = h.replace(/(<[a-z][^>]*\s)(?:id|name|language|type|on\w+|\w+:\w+)=(?:"[^"]*"|\w+)\s?/gi, "$1"); | |
335 } while (len != h.length); | |
336 | |
337 // Remove all spans if no styles is to be retained | |
338 if (getParam(ed, "paste_retain_style_properties").replace(/^none$/i, "").length == 0) { | |
339 h = h.replace(/<\/?span[^>]*>/gi, ""); | |
340 } else { | |
341 // We're keeping styles, so at least clean them up. | |
342 // CSS Reference: http://msdn.microsoft.com/en-us/library/aa155477.aspx | |
343 | |
344 process([ | |
345 // Convert <span style="mso-spacerun:yes">___</span> to string of alternating breaking/non-breaking spaces of same length | |
346 [/<span\s+style\s*=\s*"\s*mso-spacerun\s*:\s*yes\s*;?\s*"\s*>([\s\u00a0]*)<\/span>/gi, | |
347 function(str, spaces) { | |
348 return (spaces.length > 0)? spaces.replace(/./, " ").slice(Math.floor(spaces.length/2)).split("").join("\u00a0") : ""; | |
349 } | |
350 ], | |
351 | |
352 // Examine all styles: delete junk, transform some, and keep the rest | |
353 [/(<[a-z][^>]*)\sstyle="([^"]*)"/gi, | |
354 function(str, tag, style) { | |
355 var n = [], | |
356 i = 0, | |
357 s = explode(trim(style).replace(/"/gi, "'"), ";"); | |
358 | |
359 // Examine each style definition within the tag's style attribute | |
360 each(s, function(v) { | |
361 var name, value, | |
362 parts = explode(v, ":"); | |
363 | |
364 function ensureUnits(v) { | |
365 return v + ((v !== "0") && (/\d$/.test(v)))? "px" : ""; | |
366 } | |
367 | |
368 if (parts.length == 2) { | |
369 name = parts[0].toLowerCase(); | |
370 value = parts[1].toLowerCase(); | |
371 | |
372 // Translate certain MS Office styles into their CSS equivalents | |
373 switch (name) { | |
374 case "mso-padding-alt": | |
375 case "mso-padding-top-alt": | |
376 case "mso-padding-right-alt": | |
377 case "mso-padding-bottom-alt": | |
378 case "mso-padding-left-alt": | |
379 case "mso-margin-alt": | |
380 case "mso-margin-top-alt": | |
381 case "mso-margin-right-alt": | |
382 case "mso-margin-bottom-alt": | |
383 case "mso-margin-left-alt": | |
384 case "mso-table-layout-alt": | |
385 case "mso-height": | |
386 case "mso-width": | |
387 case "mso-vertical-align-alt": | |
388 n[i++] = name.replace(/^mso-|-alt$/g, "") + ":" + ensureUnits(value); | |
389 return; | |
390 | |
391 case "horiz-align": | |
392 n[i++] = "text-align:" + value; | |
393 return; | |
394 | |
395 case "vert-align": | |
396 n[i++] = "vertical-align:" + value; | |
397 return; | |
398 | |
399 case "font-color": | |
400 case "mso-foreground": | |
401 n[i++] = "color:" + value; | |
402 return; | |
403 | |
404 case "mso-background": | |
405 case "mso-highlight": | |
406 n[i++] = "background:" + value; | |
407 return; | |
408 | |
409 case "mso-default-height": | |
410 n[i++] = "min-height:" + ensureUnits(value); | |
411 return; | |
412 | |
413 case "mso-default-width": | |
414 n[i++] = "min-width:" + ensureUnits(value); | |
415 return; | |
416 | |
417 case "mso-padding-between-alt": | |
418 n[i++] = "border-collapse:separate;border-spacing:" + ensureUnits(value); | |
419 return; | |
420 | |
421 case "text-line-through": | |
422 if ((value == "single") || (value == "double")) { | |
423 n[i++] = "text-decoration:line-through"; | |
424 } | |
425 return; | |
426 | |
427 case "mso-zero-height": | |
428 if (value == "yes") { | |
429 n[i++] = "display:none"; | |
430 } | |
431 return; | |
432 } | |
433 | |
434 // Eliminate all MS Office style definitions that have no CSS equivalent by examining the first characters in the name | |
435 if (/^(mso|column|font-emph|lang|layout|line-break|list-image|nav|panose|punct|row|ruby|sep|size|src|tab-|table-border|text-(?!align|decor|indent|trans)|top-bar|version|vnd|word-break)/.test(name)) { | |
436 return; | |
437 } | |
438 | |
439 // If it reached this point, it must be a valid CSS style | |
440 n[i++] = name + ":" + parts[1]; // Lower-case name, but keep value case | |
441 } | |
442 }); | |
443 | |
444 // If style attribute contained any valid styles the re-write it; otherwise delete style attribute. | |
445 if (i > 0) { | |
446 return tag + ' style="' + n.join(';') + '"'; | |
447 } else { | |
448 return tag; | |
449 } | |
450 } | |
451 ] | |
452 ]); | |
453 } | |
454 } | |
455 | |
456 // Replace headers with <strong> | |
457 if (getParam(ed, "paste_convert_headers_to_strong")) { | |
458 process([ | |
459 [/<h[1-6][^>]*>/gi, "<p><strong>"], | |
460 [/<\/h[1-6][^>]*>/gi, "</strong></p>"] | |
461 ]); | |
462 } | |
463 | |
464 // Class attribute options are: leave all as-is ("none"), remove all ("all"), or remove only those starting with mso ("mso"). | |
465 // Note:- paste_strip_class_attributes: "none", verify_css_classes: true is also a good variation. | |
466 stripClass = getParam(ed, "paste_strip_class_attributes"); | |
467 | |
468 if (stripClass !== "none") { | |
469 function removeClasses(match, g1) { | |
470 if (stripClass === "all") | |
471 return ''; | |
472 | |
473 var cls = grep(explode(g1.replace(/^(["'])(.*)\1$/, "$2"), " "), | |
474 function(v) { | |
475 return (/^(?!mso)/i.test(v)); | |
476 } | |
477 ); | |
478 | |
479 return cls.length ? ' class="' + cls.join(" ") + '"' : ''; | |
480 }; | |
481 | |
482 h = h.replace(/ class="([^"]+)"/gi, removeClasses); | |
483 h = h.replace(/ class=(\w+)/gi, removeClasses); | |
484 } | |
485 | |
486 // Remove spans option | |
487 if (getParam(ed, "paste_remove_spans")) { | |
488 h = h.replace(/<\/?span[^>]*>/gi, ""); | |
489 } | |
490 | |
491 //console.log('After preprocess:' + h); | |
492 | |
493 o.content = h; | |
104 }, | 494 }, |
105 | 495 |
106 _insertText : function(content, bLinebreaks) { | 496 /** |
107 content = this.editor.dom.encode(content); | 497 * Various post process items. |
108 | 498 */ |
109 if (content && content.length > 0) { | 499 _postProcess : function(pl, o) { |
500 var t = this, ed = t.editor, dom = ed.dom, styleProps; | |
501 | |
502 if (o.wordContent) { | |
503 // Remove named anchors or TOC links | |
504 each(dom.select('a', o.node), function(a) { | |
505 if (!a.href || a.href.indexOf('#_Toc') != -1) | |
506 dom.remove(a, 1); | |
507 }); | |
508 | |
509 if (getParam(ed, "paste_convert_middot_lists")) { | |
510 t._convertLists(pl, o); | |
511 } | |
512 | |
513 // Process styles | |
514 styleProps = getParam(ed, "paste_retain_style_properties"); // retained properties | |
515 | |
516 // Process only if a string was specified and not equal to "all" or "*" | |
517 if ((tinymce.is(styleProps, "string")) && (styleProps !== "all") && (styleProps !== "*")) { | |
518 styleProps = tinymce.explode(styleProps.replace(/^none$/i, "")); | |
519 | |
520 // Retains some style properties | |
521 each(dom.select('*', o.node), function(el) { | |
522 var newStyle = {}, npc = 0, i, sp, sv; | |
523 | |
524 // Store a subset of the existing styles | |
525 if (styleProps) { | |
526 for (i = 0; i < styleProps.length; i++) { | |
527 sp = styleProps[i]; | |
528 sv = dom.getStyle(el, sp); | |
529 | |
530 if (sv) { | |
531 newStyle[sp] = sv; | |
532 npc++; | |
533 } | |
534 } | |
535 } | |
536 | |
537 // Remove all of the existing styles | |
538 dom.setAttrib(el, 'style', ''); | |
539 | |
540 if (styleProps && npc > 0) | |
541 dom.setStyles(el, newStyle); // Add back the stored subset of styles | |
542 else // Remove empty span tags that do not have class attributes | |
543 if (el.nodeName == 'SPAN' && !el.className) | |
544 dom.remove(el, true); | |
545 }); | |
546 } | |
547 } | |
548 | |
549 // Remove all style information or only specifically on WebKit to avoid the style bug on that browser | |
550 if (getParam(ed, "paste_remove_styles") || (getParam(ed, "paste_remove_styles_if_webkit") && tinymce.isWebKit)) { | |
551 each(dom.select('*[style]', o.node), function(el) { | |
552 el.removeAttribute('style'); | |
553 el.removeAttribute('_mce_style'); | |
554 }); | |
555 } else { | |
556 if (tinymce.isWebKit) { | |
557 // 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 ..." /> | |
558 // Removing the mce_style that contains the real value will force the Serializer engine to compress the styles | |
559 each(dom.select('*', o.node), function(el) { | |
560 el.removeAttribute('_mce_style'); | |
561 }); | |
562 } | |
563 } | |
564 }, | |
565 | |
566 /** | |
567 * Converts the most common bullet and number formats in Office into a real semantic UL/LI list. | |
568 */ | |
569 _convertLists : function(pl, o) { | |
570 var dom = pl.editor.dom, listElm, li, lastMargin = -1, margin, levels = [], lastType, html; | |
571 | |
572 // Convert middot lists into real semantic lists | |
573 each(dom.select('p', o.node), function(p) { | |
574 var sib, val = '', type, html, idx, parents; | |
575 | |
576 // Get text node value at beginning of paragraph | |
577 for (sib = p.firstChild; sib && sib.nodeType == 3; sib = sib.nextSibling) | |
578 val += sib.nodeValue; | |
579 | |
580 val = p.innerHTML.replace(/<\/?\w+[^>]*>/gi, '').replace(/ /g, '\u00a0'); | |
581 | |
582 // Detect unordered lists look for bullets | |
583 if (/^(__MCE_ITEM__)+[\u2022\u00b7\u00a7\u00d8o]\s*\u00a0*/.test(val)) | |
584 type = 'ul'; | |
585 | |
586 // Detect ordered lists 1., a. or ixv. | |
587 if (/^__MCE_ITEM__\s*\w+\.\s*\u00a0{2,}/.test(val)) | |
588 type = 'ol'; | |
589 | |
590 // Check if node value matches the list pattern: o | |
591 if (type) { | |
592 margin = parseFloat(p.style.marginLeft || 0); | |
593 | |
594 if (margin > lastMargin) | |
595 levels.push(margin); | |
596 | |
597 if (!listElm || type != lastType) { | |
598 listElm = dom.create(type); | |
599 dom.insertAfter(listElm, p); | |
600 } else { | |
601 // Nested list element | |
602 if (margin > lastMargin) { | |
603 listElm = li.appendChild(dom.create(type)); | |
604 } else if (margin < lastMargin) { | |
605 // Find parent level based on margin value | |
606 idx = tinymce.inArray(levels, margin); | |
607 parents = dom.getParents(listElm.parentNode, type); | |
608 listElm = parents[parents.length - 1 - idx] || listElm; | |
609 } | |
610 } | |
611 | |
612 // Remove middot or number spans if they exists | |
613 each(dom.select('span', p), function(span) { | |
614 var html = span.innerHTML.replace(/<\/?\w+[^>]*>/gi, ''); | |
615 | |
616 // Remove span with the middot or the number | |
617 if (type == 'ul' && /^[\u2022\u00b7\u00a7\u00d8o]/.test(html)) | |
618 dom.remove(span); | |
619 else if (/^[\s\S]*\w+\.( |\u00a0)*\s*/.test(html)) | |
620 dom.remove(span); | |
621 }); | |
622 | |
623 html = p.innerHTML; | |
624 | |
625 // Remove middot/list items | |
626 if (type == 'ul') | |
627 html = p.innerHTML.replace(/__MCE_ITEM__/g, '').replace(/^[\u2022\u00b7\u00a7\u00d8o]\s*( |\u00a0)+\s*/, ''); | |
628 else | |
629 html = p.innerHTML.replace(/__MCE_ITEM__/g, '').replace(/^\s*\w+\.( |\u00a0)+\s*/, ''); | |
630 | |
631 // Create li and add paragraph data into the new li | |
632 li = listElm.appendChild(dom.create('li', 0, html)); | |
633 dom.remove(p); | |
634 | |
635 lastMargin = margin; | |
636 lastType = type; | |
637 } else | |
638 listElm = lastMargin = 0; // End list element | |
639 }); | |
640 | |
641 // Remove any left over makers | |
642 html = o.node.innerHTML; | |
643 if (html.indexOf('__MCE_ITEM__') != -1) | |
644 o.node.innerHTML = html.replace(/__MCE_ITEM__/g, ''); | |
645 }, | |
646 | |
647 /** | |
648 * This method will split the current block parent and insert the contents inside the split position. | |
649 * This logic can be improved so text nodes at the start/end remain in the start/end block elements | |
650 */ | |
651 _insertBlockContent : function(ed, dom, content) { | |
652 var parentBlock, marker, sel = ed.selection, last, elm, vp, y, elmHeight, markerId = 'mce_marker'; | |
653 | |
654 function select(n) { | |
655 var r; | |
656 | |
657 if (tinymce.isIE) { | |
658 r = ed.getDoc().body.createTextRange(); | |
659 r.moveToElementText(n); | |
660 r.collapse(false); | |
661 r.select(); | |
662 } else { | |
663 sel.select(n, 1); | |
664 sel.collapse(false); | |
665 } | |
666 } | |
667 | |
668 // Insert a marker for the caret position | |
669 this._insert('<span id="' + markerId + '"> </span>', 1); | |
670 marker = dom.get(markerId); | |
671 parentBlock = dom.getParent(marker, 'p,h1,h2,h3,h4,h5,h6,ul,ol,th,td'); | |
672 | |
673 // If it's a parent block but not a table cell | |
674 if (parentBlock && !/TD|TH/.test(parentBlock.nodeName)) { | |
675 // Split parent block | |
676 marker = dom.split(parentBlock, marker); | |
677 | |
678 // Insert nodes before the marker | |
679 each(dom.create('div', 0, content).childNodes, function(n) { | |
680 last = marker.parentNode.insertBefore(n.cloneNode(true), marker); | |
681 }); | |
682 | |
683 // Move caret after marker | |
684 select(last); | |
685 } else { | |
686 dom.setOuterHTML(marker, content); | |
687 sel.select(ed.getBody(), 1); | |
688 sel.collapse(0); | |
689 } | |
690 | |
691 // Remove marker if it's left | |
692 while (elm = dom.get(markerId)) | |
693 dom.remove(elm); | |
694 | |
695 // Get element, position and height | |
696 elm = sel.getStart(); | |
697 vp = dom.getViewPort(ed.getWin()); | |
698 y = ed.dom.getPos(elm).y; | |
699 elmHeight = elm.clientHeight; | |
700 | |
701 // Is element within viewport if not then scroll it into view | |
702 if (y < vp.y || y + elmHeight > vp.y + vp.h) | |
703 ed.getDoc().body.scrollTop = y < vp.y ? y : y - vp.h + 25; | |
704 }, | |
705 | |
706 /** | |
707 * Inserts the specified contents at the caret position. | |
708 */ | |
709 _insert : function(h, skip_undo) { | |
710 var ed = this.editor; | |
711 | |
712 // First delete the contents seems to work better on WebKit | |
713 if (!ed.selection.isCollapsed()) | |
714 ed.getDoc().execCommand('Delete', false, null); | |
715 | |
716 // It's better to use the insertHTML method on Gecko since it will combine paragraphs correctly before inserting the contents | |
717 ed.execCommand(tinymce.isGecko ? 'insertHTML' : 'mceInsertContent', false, h, {skip_undo : skip_undo}); | |
718 }, | |
719 | |
720 /** | |
721 * Instead of the old plain text method which tried to re-create a paste operation, the | |
722 * new approach adds a plain text mode toggle switch that changes the behavior of paste. | |
723 * This function is passed the same input that the regular paste plugin produces. | |
724 * It performs additional scrubbing and produces (and inserts) the plain text. | |
725 * This approach leverages all of the great existing functionality in the paste | |
726 * plugin, and requires minimal changes to add the new functionality. | |
727 * Speednet - June 2009 | |
728 */ | |
729 _insertPlainText : function(ed, dom, h) { | |
730 var i, len, pos, rpos, node, breakElms, before, after, | |
731 w = ed.getWin(), | |
732 d = ed.getDoc(), | |
733 sel = ed.selection, | |
734 is = tinymce.is, | |
735 inArray = tinymce.inArray, | |
736 linebr = getParam(ed, "paste_text_linebreaktype"), | |
737 rl = getParam(ed, "paste_text_replacements"); | |
738 | |
739 function process(items) { | |
740 each(items, function(v) { | |
741 if (v.constructor == RegExp) | |
742 h = h.replace(v, ""); | |
743 else | |
744 h = h.replace(v[0], v[1]); | |
745 }); | |
746 }; | |
747 | |
748 if ((typeof(h) === "string") && (h.length > 0)) { | |
749 if (!entities) | |
750 entities = ("34,quot,38,amp,39,apos,60,lt,62,gt," + ed.serializer.settings.entities).split(","); | |
751 | |
752 // If HTML content with line-breaking tags, then remove all cr/lf chars because only tags will break a line | |
753 if (/<(?:p|br|h[1-6]|ul|ol|dl|table|t[rdh]|div|blockquote|fieldset|pre|address|center)[^>]*>/i.test(h)) { | |
754 process([ | |
755 /[\n\r]+/g | |
756 ]); | |
757 } else { | |
758 // Otherwise just get rid of carriage returns (only need linefeeds) | |
759 process([ | |
760 /\r+/g | |
761 ]); | |
762 } | |
763 | |
764 process([ | |
765 [/<\/(?:p|h[1-6]|ul|ol|dl|table|div|blockquote|fieldset|pre|address|center)>/gi, "\n\n"], // Block tags get a blank line after them | |
766 [/<br[^>]*>|<\/tr>/gi, "\n"], // Single linebreak for <br /> tags and table rows | |
767 [/<\/t[dh]>\s*<t[dh][^>]*>/gi, "\t"], // Table cells get tabs betweem them | |
768 /<[a-z!\/?][^>]*>/gi, // Delete all remaining tags | |
769 [/ /gi, " "], // Convert non-break spaces to regular spaces (remember, *plain text*) | |
770 [ | |
771 // HTML entity | |
772 /&(#\d+|[a-z0-9]{1,10});/gi, | |
773 | |
774 // Replace with actual character | |
775 function(e, s) { | |
776 if (s.charAt(0) === "#") { | |
777 return String.fromCharCode(s.slice(1)); | |
778 } | |
779 else { | |
780 return ((e = inArray(entities, s)) > 0)? String.fromCharCode(entities[e-1]) : " "; | |
781 } | |
782 } | |
783 ], | |
784 [/(?:(?!\n)\s)*(\n+)(?:(?!\n)\s)*/gi, "$1"], // Cool little RegExp deletes whitespace around linebreak chars. | |
785 [/\n{3,}/g, "\n\n"], // Max. 2 consecutive linebreaks | |
786 /^\s+|\s+$/g // Trim the front & back | |
787 ]); | |
788 | |
789 h = dom.encode(h); | |
790 | |
110 // Delete any highlighted text before pasting | 791 // Delete any highlighted text before pasting |
111 if (!this.editor.selection.isCollapsed()) | 792 if (!sel.isCollapsed()) { |
112 this.editor.execCommand("Delete"); | 793 d.execCommand("Delete", false, null); |
113 | 794 } |
114 if (bLinebreaks) { | 795 |
115 // Special paragraph treatment | 796 // Perform default or custom replacements |
116 if (this.editor.getParam("paste_create_paragraphs", true)) { | 797 if (is(rl, "array") || (is(rl, "array"))) { |
117 var rl = this.editor.getParam("paste_replace_list", '\u2122,<sup>TM</sup>,\u2026,...,\u201c|\u201d,",\u2019,\',\u2013|\u2014|\u2015|\u2212,-').split(','); | 798 process(rl); |
118 for (var i=0; i<rl.length; i+=2) | 799 } |
119 content = content.replace(new RegExp(rl[i], 'gi'), rl[i+1]); | 800 else if (is(rl, "string")) { |
120 | 801 process(new RegExp(rl, "gi")); |
121 content = content.replace(/\r\n\r\n/g, '</p><p>'); | 802 } |
122 content = content.replace(/\r\r/g, '</p><p>'); | 803 |
123 content = content.replace(/\n\n/g, '</p><p>'); | 804 // Treat paragraphs as specified in the config |
124 | 805 if (linebr == "none") { |
125 // Has paragraphs | 806 process([ |
126 if ((pos = content.indexOf('</p><p>')) != -1) { | 807 [/\n+/g, " "] |
127 this.editor.execCommand("Delete"); | 808 ]); |
128 | 809 } |
129 var node = this.editor.selection.getNode(); | 810 else if (linebr == "br") { |
130 | 811 process([ |
131 // Get list of elements to break | 812 [/\n/g, "<br />"] |
132 var breakElms = []; | 813 ]); |
133 | 814 } |
134 do { | 815 else { |
135 if (node.nodeType == 1) { | 816 process([ |
136 // Don't break tables and break at body | 817 /^\s+|\s+$/g, |
137 if (node.nodeName == "TD" || node.nodeName == "BODY") | 818 [/\n\n/g, "</p><p>"], |
138 break; | 819 [/\n/g, "<br />"] |
139 | 820 ]); |
140 breakElms[breakElms.length] = node; | 821 } |
141 } | 822 |
142 } while(node = node.parentNode); | 823 // This next piece of code handles the situation where we're pasting more than one paragraph of plain |
143 | 824 // text, and we are pasting the content into the middle of a block node in the editor. The block |
144 var before = "", after = "</p>"; | 825 // node gets split at the selection point into "Para A" and "Para B" (for the purposes of explaining). |
145 before += content.substring(0, pos); | 826 // The first paragraph of the pasted text is appended to "Para A", and the last paragraph of the |
146 | 827 // pasted text is prepended to "Para B". Any other paragraphs of pasted text are placed between |
147 for (var i=0; i<breakElms.length; i++) { | 828 // "Para A" and "Para B". This code solves a host of problems with the original plain text plugin and |
148 before += "</" + breakElms[i].nodeName + ">"; | 829 // now handles styles correctly. (Pasting plain text into a styled paragraph is supposed to make the |
149 after += "<" + breakElms[(breakElms.length-1)-i].nodeName + ">"; | 830 // plain text take the same style as the existing paragraph.) |
150 } | 831 if ((pos = h.indexOf("</p><p>")) != -1) { |
151 | 832 rpos = h.lastIndexOf("</p><p>"); |
152 before += "<p>"; | 833 node = sel.getNode(); |
153 content = before + content.substring(pos+7) + after; | 834 breakElms = []; // Get list of elements to break |
154 } | 835 |
155 } | 836 do { |
156 | 837 if (node.nodeType == 1) { |
157 if (this.editor.getParam("paste_create_linebreaks", true)) { | 838 // Don't break tables and break at body |
158 content = content.replace(/\r\n/g, '<br />'); | 839 if (node.nodeName == "TD" || node.nodeName == "BODY") { |
159 content = content.replace(/\r/g, '<br />'); | 840 break; |
160 content = content.replace(/\n/g, '<br />'); | 841 } |
842 | |
843 breakElms[breakElms.length] = node; | |
844 } | |
845 } while (node = node.parentNode); | |
846 | |
847 // Are we in the middle of a block node? | |
848 if (breakElms.length > 0) { | |
849 before = h.substring(0, pos); | |
850 after = ""; | |
851 | |
852 for (i=0, len=breakElms.length; i<len; i++) { | |
853 before += "</" + breakElms[i].nodeName.toLowerCase() + ">"; | |
854 after += "<" + breakElms[breakElms.length-i-1].nodeName.toLowerCase() + ">"; | |
855 } | |
856 | |
857 if (pos == rpos) { | |
858 h = before + after + h.substring(pos+7); | |
859 } | |
860 else { | |
861 h = before + h.substring(pos+4, rpos+4) + after + h.substring(rpos+7); | |
862 } | |
161 } | 863 } |
162 } | 864 } |
163 | 865 |
164 this.editor.execCommand("mceInsertRawHTML", false, content); | 866 // Insert content at the caret, plus add a marker for repositioning the caret |
867 ed.execCommand("mceInsertRawHTML", false, h + '<span id="_plain_text_marker"> </span>'); | |
868 | |
869 // Reposition the caret to the marker, which was placed immediately after the inserted content. | |
870 // Needs to be done asynchronously (in window.setTimeout) or else it doesn't work in all browsers. | |
871 // The second part of the code scrolls the content up if the caret is positioned off-screen. | |
872 // This is only necessary for WebKit browsers, but it doesn't hurt to use for all. | |
873 window.setTimeout(function() { | |
874 var marker = dom.get('_plain_text_marker'), | |
875 elm, vp, y, elmHeight; | |
876 | |
877 sel.select(marker, false); | |
878 d.execCommand("Delete", false, null); | |
879 marker = null; | |
880 | |
881 // Get element, position and height | |
882 elm = sel.getStart(); | |
883 vp = dom.getViewPort(w); | |
884 y = dom.getPos(elm).y; | |
885 elmHeight = elm.clientHeight; | |
886 | |
887 // Is element within viewport if not then scroll it into view | |
888 if ((y < vp.y) || (y + elmHeight > vp.y + vp.h)) { | |
889 d.body.scrollTop = y < vp.y ? y : y - vp.h + 25; | |
890 } | |
891 }, 0); | |
165 } | 892 } |
166 }, | 893 }, |
167 | 894 |
168 _insertWordContent : function(content) { | 895 /** |
896 * This method will open the old style paste dialogs. Some users might want the old behavior but still use the new cleanup engine. | |
897 */ | |
898 _legacySupport : function() { | |
169 var t = this, ed = t.editor; | 899 var t = this, ed = t.editor; |
170 | 900 |
171 if (content && content.length > 0) { | 901 // Register command(s) for backwards compatibility |
172 // Cleanup Word content | 902 ed.addCommand("mcePasteWord", function() { |
173 var bull = String.fromCharCode(8226); | 903 ed.windowManager.open({ |
174 var middot = String.fromCharCode(183); | 904 file: t.url + "/pasteword.htm", |
175 | 905 width: parseInt(getParam(ed, "paste_dialog_width")), |
176 if (ed.getParam('paste_insert_word_content_callback')) | 906 height: parseInt(getParam(ed, "paste_dialog_height")), |
177 content = ed.execCallback('paste_insert_word_content_callback', 'before', content); | 907 inline: 1 |
178 | 908 }); |
179 var rl = ed.getParam("paste_replace_list", '\u2122,<sup>TM</sup>,\u2026,...,\x93|\x94|\u201c|\u201d,",\x60|\x91|\x92|\u2018|\u2019,\',\u2013|\u2014|\u2015|\u2212,-').split(','); | 909 }); |
180 for (var i=0; i<rl.length; i+=2) | 910 |
181 content = content.replace(new RegExp(rl[i], 'gi'), rl[i+1]); | 911 if (getParam(ed, "paste_text_use_dialog")) { |
182 | 912 ed.addCommand("mcePasteText", function() { |
183 if (this.editor.getParam("paste_convert_headers_to_strong", false)) { | 913 ed.windowManager.open({ |
184 content = content.replace(new RegExp('<p class=MsoHeading.*?>(.*?)<\/p>', 'gi'), '<p><b>$1</b></p>'); | 914 file : t.url + "/pastetext.htm", |
185 } | 915 width: parseInt(getParam(ed, "paste_dialog_width")), |
186 | 916 height: parseInt(getParam(ed, "paste_dialog_height")), |
187 content = content.replace(new RegExp('tab-stops: list [0-9]+.0pt">', 'gi'), '">' + "--list--"); | 917 inline : 1 |
188 content = content.replace(new RegExp(bull + "(.*?)<BR>", "gi"), "<p>" + middot + "$1</p>"); | 918 }); |
189 content = content.replace(new RegExp('<SPAN style="mso-list: Ignore">', 'gi'), "<span>" + bull); // Covert to bull list | 919 }); |
190 content = content.replace(/<o:p><\/o:p>/gi, ""); | 920 } |
191 content = content.replace(new RegExp('<br style="page-break-before: always;.*>', 'gi'), '-- page break --'); // Replace pagebreaks | 921 |
192 content = content.replace(/<!--([\s\S]*?)-->|<style>[\s\S]*?<\/style>/g, ""); // Word comments | 922 // Register button for backwards compatibility |
193 content = content.replace(/<(meta|link)[^>]+>/g, ""); // Header elements | 923 ed.addButton("pasteword", {title : "paste.paste_word_desc", cmd : "mcePasteWord"}); |
194 | |
195 if (this.editor.getParam("paste_remove_spans", true)) | |
196 content = content.replace(/<\/?span[^>]*>/gi, ""); | |
197 | |
198 if (this.editor.getParam("paste_remove_styles", true)) | |
199 content = content.replace(new RegExp('<(\\w[^>]*) style="([^"]*)"([^>]*)', 'gi'), "<$1$3"); | |
200 | |
201 content = content.replace(/<\/?font[^>]*>/gi, ""); | |
202 | |
203 // Strips class attributes. | |
204 switch (this.editor.getParam("paste_strip_class_attributes", "all")) { | |
205 case "all": | |
206 content = content.replace(/<(\w[^>]*) class=([^ |>]*)([^>]*)/gi, "<$1$3"); | |
207 break; | |
208 | |
209 case "mso": | |
210 content = content.replace(new RegExp('<(\\w[^>]*) class="?mso([^ |>]*)([^>]*)', 'gi'), "<$1$3"); | |
211 break; | |
212 } | |
213 | |
214 content = content.replace(new RegExp('href="?' + this._reEscape("" + document.location) + '', 'gi'), 'href="' + this.editor.documentBaseURI.getURI()); | |
215 content = content.replace(/<(\w[^>]*) lang=([^ |>]*)([^>]*)/gi, "<$1$3"); | |
216 content = content.replace(/<\\?\?xml[^>]*>/gi, ""); | |
217 content = content.replace(/<\/?\w+:[^>]*>/gi, ""); | |
218 content = content.replace(/-- page break --\s*<p> <\/p>/gi, ""); // Remove pagebreaks | |
219 content = content.replace(/-- page break --/gi, ""); // Remove pagebreaks | |
220 | |
221 // content = content.replace(/\/? */gi, ""); | |
222 // content = content.replace(/<p> <\/p>/gi, ''); | |
223 | |
224 if (!this.editor.getParam('force_p_newlines')) { | |
225 content = content.replace('', '' ,'gi'); | |
226 content = content.replace('</p>', '<br /><br />' ,'gi'); | |
227 } | |
228 | |
229 if (!tinymce.isIE && !this.editor.getParam('force_p_newlines')) { | |
230 content = content.replace(/<\/?p[^>]*>/gi, ""); | |
231 } | |
232 | |
233 content = content.replace(/<\/?div[^>]*>/gi, ""); | |
234 | |
235 // Convert all middlot lists to UL lists | |
236 if (this.editor.getParam("paste_convert_middot_lists", true)) { | |
237 var div = ed.dom.create("div", null, content); | |
238 | |
239 // Convert all middot paragraphs to li elements | |
240 var className = this.editor.getParam("paste_unindented_list_class", "unIndentedList"); | |
241 | |
242 while (this._convertMiddots(div, "--list--")) ; // bull | |
243 while (this._convertMiddots(div, middot, className)) ; // Middot | |
244 while (this._convertMiddots(div, bull)) ; // bull | |
245 | |
246 content = div.innerHTML; | |
247 } | |
248 | |
249 // Replace all headers with strong and fix some other issues | |
250 if (this.editor.getParam("paste_convert_headers_to_strong", false)) { | |
251 content = content.replace(/<h[1-6]> <\/h[1-6]>/gi, '<p> </p>'); | |
252 content = content.replace(/<h[1-6]>/gi, '<p><b>'); | |
253 content = content.replace(/<\/h[1-6]>/gi, '</b></p>'); | |
254 content = content.replace(/<b> <\/b>/gi, '<b> </b>'); | |
255 content = content.replace(/^( )*/gi, ''); | |
256 } | |
257 | |
258 content = content.replace(/--list--/gi, ""); // Remove --list-- | |
259 | |
260 if (ed.getParam('paste_insert_word_content_callback')) | |
261 content = ed.execCallback('paste_insert_word_content_callback', 'after', content); | |
262 | |
263 // Insert cleaned content | |
264 this.editor.execCommand("mceInsertContent", false, content); | |
265 | |
266 if (this.editor.getParam('paste_force_cleanup_wordpaste', true)) { | |
267 var ed = this.editor; | |
268 | |
269 window.setTimeout(function() { | |
270 ed.execCommand("mceCleanup"); | |
271 }, 1); // Do normal cleanup detached from this thread | |
272 } | |
273 } | |
274 }, | |
275 | |
276 _reEscape : function(s) { | |
277 var l = "?.\\*[](){}+^$:"; | |
278 var o = ""; | |
279 | |
280 for (var i=0; i<s.length; i++) { | |
281 var c = s.charAt(i); | |
282 | |
283 if (l.indexOf(c) != -1) | |
284 o += '\\' + c; | |
285 else | |
286 o += c; | |
287 } | |
288 | |
289 return o; | |
290 }, | |
291 | |
292 _convertMiddots : function(div, search, class_name) { | |
293 var ed = this.editor, mdot = String.fromCharCode(183), bull = String.fromCharCode(8226); | |
294 var nodes, prevul, i, p, ul, li, np, cp, li; | |
295 | |
296 nodes = div.getElementsByTagName("p"); | |
297 for (i=0; i<nodes.length; i++) { | |
298 p = nodes[i]; | |
299 | |
300 // Is middot | |
301 if (p.innerHTML.indexOf(search) == 0) { | |
302 ul = ed.dom.create("ul"); | |
303 | |
304 if (class_name) | |
305 ul.className = class_name; | |
306 | |
307 // Add the first one | |
308 li = ed.dom.create("li"); | |
309 li.innerHTML = p.innerHTML.replace(new RegExp('' + mdot + '|' + bull + '|--list--| ', "gi"), ''); | |
310 ul.appendChild(li); | |
311 | |
312 // Add the rest | |
313 np = p.nextSibling; | |
314 while (np) { | |
315 // If the node is whitespace, then | |
316 // ignore it and continue on. | |
317 if (np.nodeType == 3 && new RegExp('^\\s$', 'm').test(np.nodeValue)) { | |
318 np = np.nextSibling; | |
319 continue; | |
320 } | |
321 | |
322 if (search == mdot) { | |
323 if (np.nodeType == 1 && new RegExp('^o(\\s+| )').test(np.innerHTML)) { | |
324 // Second level of nesting | |
325 if (!prevul) { | |
326 prevul = ul; | |
327 ul = ed.dom.create("ul"); | |
328 prevul.appendChild(ul); | |
329 } | |
330 np.innerHTML = np.innerHTML.replace(/^o/, ''); | |
331 } else { | |
332 // Pop the stack if we're going back up to the first level | |
333 if (prevul) { | |
334 ul = prevul; | |
335 prevul = null; | |
336 } | |
337 // Not element or middot paragraph | |
338 if (np.nodeType != 1 || np.innerHTML.indexOf(search) != 0) | |
339 break; | |
340 } | |
341 } else { | |
342 // Not element or middot paragraph | |
343 if (np.nodeType != 1 || np.innerHTML.indexOf(search) != 0) | |
344 break; | |
345 } | |
346 | |
347 cp = np.nextSibling; | |
348 li = ed.dom.create("li"); | |
349 li.innerHTML = np.innerHTML.replace(new RegExp('' + mdot + '|' + bull + '|--list--| ', "gi"), ''); | |
350 np.parentNode.removeChild(np); | |
351 ul.appendChild(li); | |
352 np = cp; | |
353 } | |
354 | |
355 p.parentNode.replaceChild(ul, p); | |
356 | |
357 return true; | |
358 } | |
359 } | |
360 | |
361 return false; | |
362 }, | |
363 | |
364 _clipboardHTML : function() { | |
365 var div = document.getElementById('_TinyMCE_clipboardHTML'); | |
366 | |
367 if (!div) { | |
368 var div = document.createElement('DIV'); | |
369 div.id = '_TinyMCE_clipboardHTML'; | |
370 | |
371 with (div.style) { | |
372 visibility = 'hidden'; | |
373 overflow = 'hidden'; | |
374 position = 'absolute'; | |
375 width = 1; | |
376 height = 1; | |
377 } | |
378 | |
379 document.body.appendChild(div); | |
380 } | |
381 | |
382 div.innerHTML = ''; | |
383 var rng = document.body.createTextRange(); | |
384 rng.moveToElementText(div); | |
385 rng.execCommand('Paste'); | |
386 var html = div.innerHTML; | |
387 div.innerHTML = ''; | |
388 return html; | |
389 } | 924 } |
390 }); | 925 }); |
391 | 926 |
392 // Register plugin | 927 // Register plugin |
393 tinymce.PluginManager.add('paste', tinymce.plugins.PastePlugin); | 928 tinymce.PluginManager.add("paste", tinymce.plugins.PastePlugin); |
394 })(); | 929 })(); |