Mercurial > public > sg101
comparison static/js/tiny_mce/plugins/paste/editor_plugin_src.js @ 442:6c182ceb7147
Fixing #217; upgrade TinyMCE to 3.4.2 and enable the paste plugin.
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Thu, 26 May 2011 00:43:49 +0000 |
parents | 88b2b9cb8c1f |
children |
comparison
equal
deleted
inserted
replaced
441:33d0c55e57a9 | 442:6c182ceb7147 |
---|---|
8 * Contributing: http://tinymce.moxiecode.com/contributing | 8 * Contributing: http://tinymce.moxiecode.com/contributing |
9 */ | 9 */ |
10 | 10 |
11 (function() { | 11 (function() { |
12 var each = tinymce.each, | 12 var each = tinymce.each, |
13 entities = null, | |
14 defs = { | 13 defs = { |
15 paste_auto_cleanup_on_paste : true, | 14 paste_auto_cleanup_on_paste : true, |
15 paste_enable_default_filters : true, | |
16 paste_block_drop : false, | 16 paste_block_drop : false, |
17 paste_retain_style_properties : "none", | 17 paste_retain_style_properties : "none", |
18 paste_strip_class_attributes : "mso", | 18 paste_strip_class_attributes : "mso", |
19 paste_remove_spans : false, | 19 paste_remove_spans : false, |
20 paste_remove_styles : false, | 20 paste_remove_styles : false, |
23 paste_convert_headers_to_strong : false, | 23 paste_convert_headers_to_strong : false, |
24 paste_dialog_width : "450", | 24 paste_dialog_width : "450", |
25 paste_dialog_height : "400", | 25 paste_dialog_height : "400", |
26 paste_text_use_dialog : false, | 26 paste_text_use_dialog : false, |
27 paste_text_sticky : false, | 27 paste_text_sticky : false, |
28 paste_text_sticky_default : false, | |
28 paste_text_notifyalways : false, | 29 paste_text_notifyalways : false, |
29 paste_text_linebreaktype : "p", | 30 paste_text_linebreaktype : "p", |
30 paste_text_replacements : [ | 31 paste_text_replacements : [ |
31 [/\u2026/g, "..."], | 32 [/\u2026/g, "..."], |
32 [/[\x93\x94\u201c\u201d]/g, '"'], | 33 [/[\x93\x94\u201c\u201d]/g, '"'], |
61 // Register optional postprocess | 62 // Register optional postprocess |
62 t.onPostProcess.add(function(pl, o) { | 63 t.onPostProcess.add(function(pl, o) { |
63 ed.execCallback('paste_postprocess', pl, o); | 64 ed.execCallback('paste_postprocess', pl, o); |
64 }); | 65 }); |
65 | 66 |
67 ed.onKeyDown.addToTop(function(ed, e) { | |
68 // Block ctrl+v from adding an undo level since the default logic in tinymce.Editor will add that | |
69 if (((tinymce.isMac ? e.metaKey : e.ctrlKey) && e.keyCode == 86) || (e.shiftKey && e.keyCode == 45)) | |
70 return false; // Stop other listeners | |
71 }); | |
72 | |
66 // Initialize plain text flag | 73 // Initialize plain text flag |
67 ed.pasteAsPlainText = false; | 74 ed.pasteAsPlainText = getParam(ed, 'paste_text_sticky_default'); |
68 | 75 |
69 // This function executes the process handlers and inserts the contents | 76 // 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 | 77 // force_rich overrides plain text mode set by user, important for pasting with execCommand |
71 function process(o, force_rich) { | 78 function process(o, force_rich) { |
72 var dom = ed.dom; | 79 var dom = ed.dom, rng, nodes; |
73 | 80 |
74 // Execute pre process handlers | 81 // Execute pre process handlers |
75 t.onPreProcess.dispatch(t, o); | 82 t.onPreProcess.dispatch(t, o); |
76 | 83 |
77 // Create DOM structure | 84 // Create DOM structure |
78 o.node = dom.create('div', 0, o.content); | 85 o.node = dom.create('div', 0, o.content); |
86 | |
87 // If pasting inside the same element and the contents is only one block | |
88 // remove the block and keep the text since Firefox will copy parts of pre and h1-h6 as a pre element | |
89 if (tinymce.isGecko) { | |
90 rng = ed.selection.getRng(true); | |
91 if (rng.startContainer == rng.endContainer && rng.startContainer.nodeType == 3) { | |
92 nodes = dom.select('p,h1,h2,h3,h4,h5,h6,pre', o.node); | |
93 | |
94 // Is only one block node and it doesn't contain word stuff | |
95 if (nodes.length == 1 && o.content.indexOf('__MCE_ITEM__') === -1) | |
96 dom.remove(nodes.reverse(), true); | |
97 } | |
98 } | |
79 | 99 |
80 // Execute post process handlers | 100 // Execute post process handlers |
81 t.onPostProcess.dispatch(t, o); | 101 t.onPostProcess.dispatch(t, o); |
82 | 102 |
83 // Serialize content | 103 // Serialize content |
89 | 109 |
90 if (!getParam(ed, "paste_text_sticky")) { | 110 if (!getParam(ed, "paste_text_sticky")) { |
91 ed.pasteAsPlainText = false; | 111 ed.pasteAsPlainText = false; |
92 ed.controlManager.setActive("pastetext", false); | 112 ed.controlManager.setActive("pastetext", false); |
93 } | 113 } |
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 { | 114 } else { |
98 t._insert(o.content); | 115 t._insert(o.content); |
99 } | 116 } |
100 } | 117 } |
101 | 118 |
130 | 147 |
131 // This function grabs the contents from the clipboard by adding a | 148 // 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 | 149 // hidden div and placing the caret inside it and after the browser paste |
133 // is done it grabs that contents and processes that | 150 // is done it grabs that contents and processes that |
134 function grabContent(e) { | 151 function grabContent(e) { |
135 var n, or, rng, sel = ed.selection, dom = ed.dom, body = ed.getBody(), posY; | 152 var n, or, rng, oldRng, sel = ed.selection, dom = ed.dom, body = ed.getBody(), posY, textContent; |
136 | 153 |
137 // Check if browser supports direct plaintext access | 154 // Check if browser supports direct plaintext access |
138 if (ed.pasteAsPlainText && (e.clipboardData || dom.doc.dataTransfer)) { | 155 if (e.clipboardData || dom.doc.dataTransfer) { |
139 e.preventDefault(); | 156 textContent = (e.clipboardData || dom.doc.dataTransfer).getData('Text'); |
140 process({content : (e.clipboardData || dom.doc.dataTransfer).getData('Text')}, true); | 157 |
141 return; | 158 if (ed.pasteAsPlainText) { |
159 e.preventDefault(); | |
160 process({content : textContent.replace(/\r?\n/g, '<br />')}); | |
161 return; | |
162 } | |
142 } | 163 } |
143 | 164 |
144 if (dom.get('_mcePaste')) | 165 if (dom.get('_mcePaste')) |
145 return; | 166 return; |
146 | 167 |
147 // Create container to paste into | 168 // Create container to paste into |
148 n = dom.add(body, 'div', {id : '_mcePaste', 'class' : 'mcePaste'}, '\uFEFF<br _mce_bogus="1">'); | 169 n = dom.add(body, 'div', {id : '_mcePaste', 'class' : 'mcePaste', 'data-mce-bogus' : '1'}, '\uFEFF\uFEFF'); |
149 | 170 |
150 // If contentEditable mode we need to find out the position of the closest element | 171 // If contentEditable mode we need to find out the position of the closest element |
151 if (body != ed.getDoc().body) | 172 if (body != ed.getDoc().body) |
152 posY = dom.getPos(ed.selection.getStart(), body).y; | 173 posY = dom.getPos(ed.selection.getStart(), body).y; |
153 else | 174 else |
154 posY = body.scrollTop; | 175 posY = body.scrollTop + dom.getViewPort().y; |
155 | 176 |
156 // Styles needs to be applied after the element is added to the document since WebKit will otherwise remove all styles | 177 // Styles needs to be applied after the element is added to the document since WebKit will otherwise remove all styles |
157 dom.setStyles(n, { | 178 dom.setStyles(n, { |
158 position : 'absolute', | 179 position : 'absolute', |
159 left : -10000, | 180 left : -10000, |
162 height : 1, | 183 height : 1, |
163 overflow : 'hidden' | 184 overflow : 'hidden' |
164 }); | 185 }); |
165 | 186 |
166 if (tinymce.isIE) { | 187 if (tinymce.isIE) { |
188 // Store away the old range | |
189 oldRng = sel.getRng(); | |
190 | |
167 // Select the container | 191 // Select the container |
168 rng = dom.doc.body.createTextRange(); | 192 rng = dom.doc.body.createTextRange(); |
169 rng.moveToElementText(n); | 193 rng.moveToElementText(n); |
170 rng.execCommand('Paste'); | 194 rng.execCommand('Paste'); |
171 | 195 |
172 // Remove container | 196 // Remove container |
173 dom.remove(n); | 197 dom.remove(n); |
174 | 198 |
175 // Check if the contents was changed, if it wasn't then clipboard extraction failed probably due | 199 // Check if the contents was changed, if it wasn't then clipboard extraction failed probably due |
176 // to IE security settings so we pass the junk though better than nothing right | 200 // to IE security settings so we pass the junk though better than nothing right |
177 if (n.innerHTML === '\uFEFF') { | 201 if (n.innerHTML === '\uFEFF\uFEFF') { |
178 ed.execCommand('mcePasteWord'); | 202 ed.execCommand('mcePasteWord'); |
179 e.preventDefault(); | 203 e.preventDefault(); |
180 return; | 204 return; |
181 } | 205 } |
182 | 206 |
183 // Process contents | 207 // Restore the old range and clear the contents before pasting |
184 process({content : n.innerHTML}); | 208 sel.setRng(oldRng); |
209 sel.setContent(''); | |
210 | |
211 // For some odd reason we need to detach the the mceInsertContent call from the paste event | |
212 // It's like IE has a reference to the parent element that you paste in and the selection gets messed up | |
213 // when it tries to restore the selection | |
214 setTimeout(function() { | |
215 // Process contents | |
216 process({content : n.innerHTML}); | |
217 }, 0); | |
185 | 218 |
186 // Block the real paste event | 219 // Block the real paste event |
187 return tinymce.dom.Event.cancel(e); | 220 return tinymce.dom.Event.cancel(e); |
188 } else { | 221 } else { |
189 function block(e) { | 222 function block(e) { |
194 dom.bind(ed.getDoc(), 'mousedown', block); | 227 dom.bind(ed.getDoc(), 'mousedown', block); |
195 dom.bind(ed.getDoc(), 'keydown', block); | 228 dom.bind(ed.getDoc(), 'keydown', block); |
196 | 229 |
197 or = ed.selection.getRng(); | 230 or = ed.selection.getRng(); |
198 | 231 |
199 // Move caret into hidden div | 232 // Move select contents inside DIV |
200 n = n.firstChild; | 233 n = n.firstChild; |
201 rng = ed.getDoc().createRange(); | 234 rng = ed.getDoc().createRange(); |
202 rng.setStart(n, 0); | 235 rng.setStart(n, 0); |
203 rng.setEnd(n, 1); | 236 rng.setEnd(n, 2); |
204 sel.setRng(rng); | 237 sel.setRng(rng); |
205 | 238 |
206 // Wait a while and grab the pasted contents | 239 // Wait a while and grab the pasted contents |
207 window.setTimeout(function() { | 240 window.setTimeout(function() { |
208 var h = '', nl = dom.select('div.mcePaste'); | 241 var h = '', nl; |
209 | 242 |
210 // WebKit will split the div into multiple ones so this will loop through then all and join them to get the whole HTML string | 243 // Paste divs duplicated in paste divs seems to happen when you paste plain text so lets first look for that broken behavior in WebKit |
211 each(nl, function(n) { | 244 if (!dom.select('div.mcePaste > div.mcePaste').length) { |
212 var child = n.firstChild; | 245 nl = dom.select('div.mcePaste'); |
213 | 246 |
214 // WebKit inserts a DIV container with lots of odd styles | 247 // WebKit will split the div into multiple ones so this will loop through then all and join them to get the whole HTML string |
215 if (child && child.nodeName == 'DIV' && child.style.marginTop && child.style.backgroundColor) { | 248 each(nl, function(n) { |
216 dom.remove(child, 1); | 249 var child = n.firstChild; |
217 } | 250 |
218 | 251 // WebKit inserts a DIV container with lots of odd styles |
219 // WebKit duplicates the divs so we need to remove them | 252 if (child && child.nodeName == 'DIV' && child.style.marginTop && child.style.backgroundColor) { |
220 each(dom.select('div.mcePaste', n), function(n) { | 253 dom.remove(child, 1); |
221 dom.remove(n, 1); | 254 } |
255 | |
256 // Remove apply style spans | |
257 each(dom.select('span.Apple-style-span', n), function(n) { | |
258 dom.remove(n, 1); | |
259 }); | |
260 | |
261 // Remove bogus br elements | |
262 each(dom.select('br[data-mce-bogus]', n), function(n) { | |
263 dom.remove(n); | |
264 }); | |
265 | |
266 // WebKit will make a copy of the DIV for each line of plain text pasted and insert them into the DIV | |
267 if (n.parentNode.className != 'mcePaste') | |
268 h += n.innerHTML; | |
222 }); | 269 }); |
223 | 270 } else { |
224 // Remove apply style spans | 271 // Found WebKit weirdness so force the content into plain text mode |
225 each(dom.select('span.Apple-style-span', n), function(n) { | 272 h = '<pre>' + dom.encode(textContent).replace(/\r?\n/g, '<br />') + '</pre>'; |
226 dom.remove(n, 1); | 273 } |
227 }); | |
228 | |
229 // Remove bogus br elements | |
230 each(dom.select('br[_mce_bogus]', n), function(n) { | |
231 dom.remove(n); | |
232 }); | |
233 | |
234 h += n.innerHTML; | |
235 }); | |
236 | 274 |
237 // Remove the nodes | 275 // Remove the nodes |
238 each(nl, function(n) { | 276 each(dom.select('div.mcePaste'), function(n) { |
239 dom.remove(n); | 277 dom.remove(n); |
240 }); | 278 }); |
241 | 279 |
242 // Restore the old selection | 280 // Restore the old selection |
243 if (or) | 281 if (or) |
254 | 292 |
255 // Check if we should use the new auto process method | 293 // Check if we should use the new auto process method |
256 if (getParam(ed, "paste_auto_cleanup_on_paste")) { | 294 if (getParam(ed, "paste_auto_cleanup_on_paste")) { |
257 // Is it's Opera or older FF use key handler | 295 // Is it's Opera or older FF use key handler |
258 if (tinymce.isOpera || /Firefox\/2/.test(navigator.userAgent)) { | 296 if (tinymce.isOpera || /Firefox\/2/.test(navigator.userAgent)) { |
259 ed.onKeyDown.add(function(ed, e) { | 297 ed.onKeyDown.addToTop(function(ed, e) { |
260 if (((tinymce.isMac ? e.metaKey : e.ctrlKey) && e.keyCode == 86) || (e.shiftKey && e.keyCode == 45)) | 298 if (((tinymce.isMac ? e.metaKey : e.ctrlKey) && e.keyCode == 86) || (e.shiftKey && e.keyCode == 45)) |
261 grabContent(e); | 299 grabContent(e); |
262 }); | 300 }); |
263 } else { | 301 } else { |
264 // Grab contents on paste event on Gecko and WebKit | 302 // Grab contents on paste event on Gecko and WebKit |
266 return grabContent(e); | 304 return grabContent(e); |
267 }); | 305 }); |
268 } | 306 } |
269 } | 307 } |
270 | 308 |
271 // Block all drag/drop events | 309 ed.onInit.add(function() { |
272 if (getParam(ed, "paste_block_drop")) { | 310 ed.controlManager.setActive("pastetext", ed.pasteAsPlainText); |
273 ed.onInit.add(function() { | 311 |
312 // Block all drag/drop events | |
313 if (getParam(ed, "paste_block_drop")) { | |
274 ed.dom.bind(ed.getBody(), ['dragend', 'dragover', 'draggesture', 'dragdrop', 'drop', 'drag'], function(e) { | 314 ed.dom.bind(ed.getBody(), ['dragend', 'dragover', 'draggesture', 'dragdrop', 'drop', 'drag'], function(e) { |
275 e.preventDefault(); | 315 e.preventDefault(); |
276 e.stopPropagation(); | 316 e.stopPropagation(); |
277 | 317 |
278 return false; | 318 return false; |
279 }); | 319 }); |
280 }); | 320 } |
281 } | 321 }); |
282 | 322 |
283 // Add legacy support | 323 // Add legacy support |
284 t._legacySupport(); | 324 t._legacySupport(); |
285 }, | 325 }, |
286 | 326 |
293 version : tinymce.majorVersion + "." + tinymce.minorVersion | 333 version : tinymce.majorVersion + "." + tinymce.minorVersion |
294 }; | 334 }; |
295 }, | 335 }, |
296 | 336 |
297 _preProcess : function(pl, o) { | 337 _preProcess : function(pl, o) { |
298 //console.log('Before preprocess:' + o.content); | |
299 | |
300 var ed = this.editor, | 338 var ed = this.editor, |
301 h = o.content, | 339 h = o.content, |
302 grep = tinymce.grep, | 340 grep = tinymce.grep, |
303 explode = tinymce.explode, | 341 explode = tinymce.explode, |
304 trim = tinymce.trim, | 342 trim = tinymce.trim, |
305 len, stripClass; | 343 len, stripClass; |
344 | |
345 //console.log('Before preprocess:' + o.content); | |
306 | 346 |
307 function process(items) { | 347 function process(items) { |
308 each(items, function(v) { | 348 each(items, function(v) { |
309 // Remove or replace | 349 // Remove or replace |
310 if (v.constructor == RegExp) | 350 if (v.constructor == RegExp) |
311 h = h.replace(v, ''); | 351 h = h.replace(v, ''); |
312 else | 352 else |
313 h = h.replace(v[0], v[1]); | 353 h = h.replace(v[0], v[1]); |
314 }); | 354 }); |
315 } | 355 } |
356 | |
357 if (ed.settings.paste_enable_default_filters == false) { | |
358 return; | |
359 } | |
360 | |
361 // IE9 adds BRs before/after block elements when contents is pasted from word or for example another browser | |
362 if (tinymce.isIE && document.documentMode >= 9) | |
363 process([[/(?:<br> [\s\r\n]+|<br>)*(<\/?(h[1-6r]|p|div|address|pre|form|table|tbody|thead|tfoot|th|tr|td|li|ol|ul|caption|blockquote|center|dl|dt|dd|dir|fieldset)[^>]*>)(?:<br> [\s\r\n]+|<br>)*/g, '$1']]); | |
316 | 364 |
317 // Detect Word content and process it more aggressive | 365 // Detect Word content and process it more aggressive |
318 if (/class="?Mso|style="[^"]*\bmso-|w:WordDocument/i.test(h) || o.wordContent) { | 366 if (/class="?Mso|style="[^"]*\bmso-|w:WordDocument/i.test(h) || o.wordContent) { |
319 o.wordContent = true; // Mark the pasted contents as word specific content | 367 o.wordContent = true; // Mark the pasted contents as word specific content |
320 //console.log('Word contents detected.'); | 368 //console.log('Word contents detected.'); |
330 } | 378 } |
331 | 379 |
332 if (getParam(ed, "paste_convert_middot_lists")) { | 380 if (getParam(ed, "paste_convert_middot_lists")) { |
333 process([ | 381 process([ |
334 [/<!--\[if !supportLists\]-->/gi, '$&__MCE_ITEM__'], // Convert supportLists to a list item marker | 382 [/<!--\[if !supportLists\]-->/gi, '$&__MCE_ITEM__'], // Convert supportLists to a list item marker |
335 [/(<span[^>]+(?:mso-list:|:\s*symbol)[^>]+>)/gi, '$1__MCE_ITEM__'] // Convert mso-list and symbol spans to item markers | 383 [/(<span[^>]+(?:mso-list:|:\s*symbol)[^>]+>)/gi, '$1__MCE_ITEM__'], // Convert mso-list and symbol spans to item markers |
384 [/(<p[^>]+(?:MsoListParagraph)[^>]+>)/gi, '$1__MCE_ITEM__'] // Convert mso-list and symbol paragraphs to item markers (FF) | |
336 ]); | 385 ]); |
337 } | 386 } |
338 | 387 |
339 process([ | 388 process([ |
340 // Word comments like conditional comments etc | 389 // Word comments like conditional comments etc |
482 [/<h[1-6][^>]*>/gi, "<p><strong>"], | 531 [/<h[1-6][^>]*>/gi, "<p><strong>"], |
483 [/<\/h[1-6][^>]*>/gi, "</strong></p>"] | 532 [/<\/h[1-6][^>]*>/gi, "</strong></p>"] |
484 ]); | 533 ]); |
485 } | 534 } |
486 | 535 |
536 process([ | |
537 // Copy paste from Java like Open Office will produce this junk on FF | |
538 [/Version:[\d.]+\nStartHTML:\d+\nEndHTML:\d+\nStartFragment:\d+\nEndFragment:\d+/gi, ''] | |
539 ]); | |
540 | |
487 // Class attribute options are: leave all as-is ("none"), remove all ("all"), or remove only those starting with mso ("mso"). | 541 // Class attribute options are: leave all as-is ("none"), remove all ("all"), or remove only those starting with mso ("mso"). |
488 // Note:- paste_strip_class_attributes: "none", verify_css_classes: true is also a good variation. | 542 // Note:- paste_strip_class_attributes: "none", verify_css_classes: true is also a good variation. |
489 stripClass = getParam(ed, "paste_strip_class_attributes"); | 543 stripClass = getParam(ed, "paste_strip_class_attributes"); |
490 | 544 |
491 if (stripClass !== "none") { | 545 if (stripClass !== "none") { |
501 | 555 |
502 return cls.length ? ' class="' + cls.join(" ") + '"' : ''; | 556 return cls.length ? ' class="' + cls.join(" ") + '"' : ''; |
503 }; | 557 }; |
504 | 558 |
505 h = h.replace(/ class="([^"]+)"/gi, removeClasses); | 559 h = h.replace(/ class="([^"]+)"/gi, removeClasses); |
506 h = h.replace(/ class=(\w+)/gi, removeClasses); | 560 h = h.replace(/ class=([\-\w]+)/gi, removeClasses); |
507 } | 561 } |
508 | 562 |
509 // Remove spans option | 563 // Remove spans option |
510 if (getParam(ed, "paste_remove_spans")) { | 564 if (getParam(ed, "paste_remove_spans")) { |
511 h = h.replace(/<\/?span[^>]*>/gi, ""); | 565 h = h.replace(/<\/?span[^>]*>/gi, ""); |
520 * Various post process items. | 574 * Various post process items. |
521 */ | 575 */ |
522 _postProcess : function(pl, o) { | 576 _postProcess : function(pl, o) { |
523 var t = this, ed = t.editor, dom = ed.dom, styleProps; | 577 var t = this, ed = t.editor, dom = ed.dom, styleProps; |
524 | 578 |
579 if (ed.settings.paste_enable_default_filters == false) { | |
580 return; | |
581 } | |
582 | |
525 if (o.wordContent) { | 583 if (o.wordContent) { |
526 // Remove named anchors or TOC links | 584 // Remove named anchors or TOC links |
527 each(dom.select('a', o.node), function(a) { | 585 each(dom.select('a', o.node), function(a) { |
528 if (!a.href || a.href.indexOf('#_Toc') != -1) | 586 if (!a.href || a.href.indexOf('#_Toc') != -1) |
529 dom.remove(a, 1); | 587 dom.remove(a, 1); |
571 | 629 |
572 // Remove all style information or only specifically on WebKit to avoid the style bug on that browser | 630 // Remove all style information or only specifically on WebKit to avoid the style bug on that browser |
573 if (getParam(ed, "paste_remove_styles") || (getParam(ed, "paste_remove_styles_if_webkit") && tinymce.isWebKit)) { | 631 if (getParam(ed, "paste_remove_styles") || (getParam(ed, "paste_remove_styles_if_webkit") && tinymce.isWebKit)) { |
574 each(dom.select('*[style]', o.node), function(el) { | 632 each(dom.select('*[style]', o.node), function(el) { |
575 el.removeAttribute('style'); | 633 el.removeAttribute('style'); |
576 el.removeAttribute('_mce_style'); | 634 el.removeAttribute('data-mce-style'); |
577 }); | 635 }); |
578 } else { | 636 } else { |
579 if (tinymce.isWebKit) { | 637 if (tinymce.isWebKit) { |
580 // 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 ..." /> | 638 // 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 ..." /> |
581 // Removing the mce_style that contains the real value will force the Serializer engine to compress the styles | 639 // Removing the mce_style that contains the real value will force the Serializer engine to compress the styles |
582 each(dom.select('*', o.node), function(el) { | 640 each(dom.select('*', o.node), function(el) { |
583 el.removeAttribute('_mce_style'); | 641 el.removeAttribute('data-mce-style'); |
584 }); | 642 }); |
585 } | 643 } |
586 } | 644 } |
587 }, | 645 }, |
588 | 646 |
601 val += sib.nodeValue; | 659 val += sib.nodeValue; |
602 | 660 |
603 val = p.innerHTML.replace(/<\/?\w+[^>]*>/gi, '').replace(/ /g, '\u00a0'); | 661 val = p.innerHTML.replace(/<\/?\w+[^>]*>/gi, '').replace(/ /g, '\u00a0'); |
604 | 662 |
605 // Detect unordered lists look for bullets | 663 // Detect unordered lists look for bullets |
606 if (/^(__MCE_ITEM__)+[\u2022\u00b7\u00a7\u00d8o]\s*\u00a0*/.test(val)) | 664 if (/^(__MCE_ITEM__)+[\u2022\u00b7\u00a7\u00d8o\u25CF]\s*\u00a0*/.test(val)) |
607 type = 'ul'; | 665 type = 'ul'; |
608 | 666 |
609 // Detect ordered lists 1., a. or ixv. | 667 // Detect ordered lists 1., a. or ixv. |
610 if (/^__MCE_ITEM__\s*\w+\.\s*\u00a0{2,}/.test(val)) | 668 if (/^__MCE_ITEM__\s*\w+\.\s*\u00a0+/.test(val)) |
611 type = 'ol'; | 669 type = 'ol'; |
612 | 670 |
613 // Check if node value matches the list pattern: o | 671 // Check if node value matches the list pattern: o |
614 if (type) { | 672 if (type) { |
615 margin = parseFloat(p.style.marginLeft || 0); | 673 margin = parseFloat(p.style.marginLeft || 0); |
635 // Remove middot or number spans if they exists | 693 // Remove middot or number spans if they exists |
636 each(dom.select('span', p), function(span) { | 694 each(dom.select('span', p), function(span) { |
637 var html = span.innerHTML.replace(/<\/?\w+[^>]*>/gi, ''); | 695 var html = span.innerHTML.replace(/<\/?\w+[^>]*>/gi, ''); |
638 | 696 |
639 // Remove span with the middot or the number | 697 // Remove span with the middot or the number |
640 if (type == 'ul' && /^[\u2022\u00b7\u00a7\u00d8o]/.test(html)) | 698 if (type == 'ul' && /^__MCE_ITEM__[\u2022\u00b7\u00a7\u00d8o\u25CF]/.test(html)) |
641 dom.remove(span); | 699 dom.remove(span); |
642 else if (/^[\s\S]*\w+\.( |\u00a0)*\s*/.test(html)) | 700 else if (/^__MCE_ITEM__[\s\S]*\w+\.( |\u00a0)*\s*/.test(html)) |
643 dom.remove(span); | 701 dom.remove(span); |
644 }); | 702 }); |
645 | 703 |
646 html = p.innerHTML; | 704 html = p.innerHTML; |
647 | 705 |
648 // Remove middot/list items | 706 // Remove middot/list items |
649 if (type == 'ul') | 707 if (type == 'ul') |
650 html = p.innerHTML.replace(/__MCE_ITEM__/g, '').replace(/^[\u2022\u00b7\u00a7\u00d8o]\s*( |\u00a0)+\s*/, ''); | 708 html = p.innerHTML.replace(/__MCE_ITEM__/g, '').replace(/^[\u2022\u00b7\u00a7\u00d8o\u25CF]\s*( |\u00a0)+\s*/, ''); |
651 else | 709 else |
652 html = p.innerHTML.replace(/__MCE_ITEM__/g, '').replace(/^\s*\w+\.( |\u00a0)+\s*/, ''); | 710 html = p.innerHTML.replace(/__MCE_ITEM__/g, '').replace(/^\s*\w+\.( |\u00a0)+\s*/, ''); |
653 | 711 |
654 // Create li and add paragraph data into the new li | 712 // Create li and add paragraph data into the new li |
655 li = listElm.appendChild(dom.create('li', 0, html)); | 713 li = listElm.appendChild(dom.create('li', 0, html)); |
666 if (html.indexOf('__MCE_ITEM__') != -1) | 724 if (html.indexOf('__MCE_ITEM__') != -1) |
667 o.node.innerHTML = html.replace(/__MCE_ITEM__/g, ''); | 725 o.node.innerHTML = html.replace(/__MCE_ITEM__/g, ''); |
668 }, | 726 }, |
669 | 727 |
670 /** | 728 /** |
671 * This method will split the current block parent and insert the contents inside the split position. | |
672 * This logic can be improved so text nodes at the start/end remain in the start/end block elements | |
673 */ | |
674 _insertBlockContent : function(ed, dom, content) { | |
675 var parentBlock, marker, sel = ed.selection, last, elm, vp, y, elmHeight, markerId = 'mce_marker'; | |
676 | |
677 function select(n) { | |
678 var r; | |
679 | |
680 if (tinymce.isIE) { | |
681 r = ed.getDoc().body.createTextRange(); | |
682 r.moveToElementText(n); | |
683 r.collapse(false); | |
684 r.select(); | |
685 } else { | |
686 sel.select(n, 1); | |
687 sel.collapse(false); | |
688 } | |
689 } | |
690 | |
691 // Insert a marker for the caret position | |
692 this._insert('<span id="' + markerId + '"></span>', 1); | |
693 marker = dom.get(markerId); | |
694 parentBlock = dom.getParent(marker, 'p,h1,h2,h3,h4,h5,h6,ul,ol,th,td'); | |
695 | |
696 // If it's a parent block but not a table cell | |
697 if (parentBlock && !/TD|TH/.test(parentBlock.nodeName)) { | |
698 // Split parent block | |
699 marker = dom.split(parentBlock, marker); | |
700 | |
701 // Insert nodes before the marker | |
702 each(dom.create('div', 0, content).childNodes, function(n) { | |
703 last = marker.parentNode.insertBefore(n.cloneNode(true), marker); | |
704 }); | |
705 | |
706 // Move caret after marker | |
707 select(last); | |
708 } else { | |
709 dom.setOuterHTML(marker, content); | |
710 sel.select(ed.getBody(), 1); | |
711 sel.collapse(0); | |
712 } | |
713 | |
714 // Remove marker if it's left | |
715 while (elm = dom.get(markerId)) | |
716 dom.remove(elm); | |
717 | |
718 // Get element, position and height | |
719 elm = sel.getStart(); | |
720 vp = dom.getViewPort(ed.getWin()); | |
721 y = ed.dom.getPos(elm).y; | |
722 elmHeight = elm.clientHeight; | |
723 | |
724 // Is element within viewport if not then scroll it into view | |
725 if (y < vp.y || y + elmHeight > vp.y + vp.h) | |
726 ed.getDoc().body.scrollTop = y < vp.y ? y : y - vp.h + 25; | |
727 }, | |
728 | |
729 /** | |
730 * Inserts the specified contents at the caret position. | 729 * Inserts the specified contents at the caret position. |
731 */ | 730 */ |
732 _insert : function(h, skip_undo) { | 731 _insert : function(h, skip_undo) { |
733 var ed = this.editor, r = ed.selection.getRng(); | 732 var ed = this.editor, r = ed.selection.getRng(); |
734 | 733 |
735 // First delete the contents seems to work better on WebKit when the selection spans multiple list items or multiple table cells. | 734 // First delete the contents seems to work better on WebKit when the selection spans multiple list items or multiple table cells. |
736 if (!ed.selection.isCollapsed() && r.startContainer != r.endContainer) | 735 if (!ed.selection.isCollapsed() && r.startContainer != r.endContainer) |
737 ed.getDoc().execCommand('Delete', false, null); | 736 ed.getDoc().execCommand('Delete', false, null); |
738 | 737 |
739 // It's better to use the insertHTML method on Gecko since it will combine paragraphs correctly before inserting the contents | 738 ed.execCommand('mceInsertContent', false, h, {skip_undo : skip_undo}); |
740 ed.execCommand(tinymce.isGecko ? 'insertHTML' : 'mceInsertContent', false, h, {skip_undo : skip_undo}); | |
741 }, | 739 }, |
742 | 740 |
743 /** | 741 /** |
744 * Instead of the old plain text method which tried to re-create a paste operation, the | 742 * Instead of the old plain text method which tried to re-create a paste operation, the |
745 * new approach adds a plain text mode toggle switch that changes the behavior of paste. | 743 * new approach adds a plain text mode toggle switch that changes the behavior of paste. |
767 h = h.replace(v[0], v[1]); | 765 h = h.replace(v[0], v[1]); |
768 }); | 766 }); |
769 }; | 767 }; |
770 | 768 |
771 if ((typeof(h) === "string") && (h.length > 0)) { | 769 if ((typeof(h) === "string") && (h.length > 0)) { |
772 if (!entities) | |
773 entities = ("34,quot,38,amp,39,apos,60,lt,62,gt," + ed.serializer.settings.entities).split(","); | |
774 | |
775 // If HTML content with line-breaking tags, then remove all cr/lf chars because only tags will break a line | 770 // If HTML content with line-breaking tags, then remove all cr/lf chars because only tags will break a line |
776 if (/<(?:p|br|h[1-6]|ul|ol|dl|table|t[rdh]|div|blockquote|fieldset|pre|address|center)[^>]*>/i.test(h)) { | 771 if (/<(?:p|br|h[1-6]|ul|ol|dl|table|t[rdh]|div|blockquote|fieldset|pre|address|center)[^>]*>/i.test(h)) { |
777 process([ | 772 process([ |
778 /[\n\r]+/g | 773 /[\n\r]+/g |
779 ]); | 774 ]); |
788 [/<\/(?: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 | 783 [/<\/(?: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 |
789 [/<br[^>]*>|<\/tr>/gi, "\n"], // Single linebreak for <br /> tags and table rows | 784 [/<br[^>]*>|<\/tr>/gi, "\n"], // Single linebreak for <br /> tags and table rows |
790 [/<\/t[dh]>\s*<t[dh][^>]*>/gi, "\t"], // Table cells get tabs betweem them | 785 [/<\/t[dh]>\s*<t[dh][^>]*>/gi, "\t"], // Table cells get tabs betweem them |
791 /<[a-z!\/?][^>]*>/gi, // Delete all remaining tags | 786 /<[a-z!\/?][^>]*>/gi, // Delete all remaining tags |
792 [/ /gi, " "], // Convert non-break spaces to regular spaces (remember, *plain text*) | 787 [/ /gi, " "], // Convert non-break spaces to regular spaces (remember, *plain text*) |
793 [ | |
794 // HTML entity | |
795 /&(#\d+|[a-z0-9]{1,10});/gi, | |
796 | |
797 // Replace with actual character | |
798 function(e, s) { | |
799 if (s.charAt(0) === "#") { | |
800 return String.fromCharCode(s.slice(1)); | |
801 } | |
802 else { | |
803 return ((e = inArray(entities, s)) > 0)? String.fromCharCode(entities[e-1]) : " "; | |
804 } | |
805 } | |
806 ], | |
807 [/(?:(?!\n)\s)*(\n+)(?:(?!\n)\s)*/gi, "$1"], // Cool little RegExp deletes whitespace around linebreak chars. | 788 [/(?:(?!\n)\s)*(\n+)(?:(?!\n)\s)*/gi, "$1"], // Cool little RegExp deletes whitespace around linebreak chars. |
808 [/\n{3,}/g, "\n\n"], // Max. 2 consecutive linebreaks | 789 [/\n{3,}/g, "\n\n"], // Max. 2 consecutive linebreaks |
809 /^\s+|\s+$/g // Trim the front & back | 790 /^\s+|\s+$/g // Trim the front & back |
810 ]); | 791 ]); |
811 | 792 |
812 h = dom.encode(h); | 793 h = dom.decode(tinymce.html.Entities.encodeRaw(h)); |
813 | 794 |
814 // Delete any highlighted text before pasting | 795 // Delete any highlighted text before pasting |
815 if (!sel.isCollapsed()) { | 796 if (!sel.isCollapsed()) { |
816 d.execCommand("Delete", false, null); | 797 d.execCommand("Delete", false, null); |
817 } | 798 } |