annotate media/js/tiny_mce/plugins/paste/editor_plugin_src.js @ 265:1ba2c6bf6eb7

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