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>&nbsp;[\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>&nbsp;[\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(/&nbsp;/g, '\u00a0'); 661 val = p.innerHTML.replace(/<\/?\w+[^>]*>/gi, '').replace(/&nbsp;/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&nbsp;&nbsp; 671 // Check if node value matches the list pattern: o&nbsp;&nbsp;
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+\.(&nbsp;|\u00a0)*\s*/.test(html)) 700 else if (/^__MCE_ITEM__[\s\S]*\w+\.(&nbsp;|\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*(&nbsp;|\u00a0)+\s*/, ''); 708 html = p.innerHTML.replace(/__MCE_ITEM__/g, '').replace(/^[\u2022\u00b7\u00a7\u00d8o\u25CF]\s*(&nbsp;|\u00a0)+\s*/, '');
651 else 709 else
652 html = p.innerHTML.replace(/__MCE_ITEM__/g, '').replace(/^\s*\w+\.(&nbsp;|\u00a0)+\s*/, ''); 710 html = p.innerHTML.replace(/__MCE_ITEM__/g, '').replace(/^\s*\w+\.(&nbsp;|\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 [/&nbsp;/gi, " "], // Convert non-break spaces to regular spaces (remember, *plain text*) 787 [/&nbsp;/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 }