@ -1,16 +1,46 @@
/ * *
* $Id: editor_plugin _src . js 1225 2009 - 09 - 07 19 : 06 : 19 Z spocke $
* editor_plugin _src . js
*
* @ author Moxiecode
* @ copyright Copyright © 2004 - 2008 , Moxiecode Systems AB , All rights reserved .
* Copyright 2009 , Moxiecode Systems AB
* Released under LGPL License .
*
* License : http : //tinymce.moxiecode.com/license
* Contributing : http : //tinymce.moxiecode.com/contributing
* /
( function ( ) {
var each = tinymce . each ;
var each = tinymce . each ,
entities = null ,
defs = {
paste _auto _cleanup _on _paste : true ,
paste _block _drop : false ,
paste _retain _style _properties : "none" ,
paste _strip _class _attributes : "mso" ,
paste _remove _spans : false ,
paste _remove _styles : false ,
paste _remove _styles _if _webkit : true ,
paste _convert _middot _lists : true ,
paste _convert _headers _to _strong : false ,
paste _dialog _width : "450" ,
paste _dialog _height : "400" ,
paste _text _use _dialog : false ,
paste _text _sticky : false ,
paste _text _notifyalways : false ,
paste _text _linebreaktype : "p" ,
paste _text _replacements : [
[ /\u2026/g , "..." ] ,
[ /[\x93\x94\u201c\u201d]/g , '"' ] ,
[ /[\x60\x91\x92\u2018\u2019]/g , "'" ]
]
} ;
function getParam ( ed , name ) {
return ed . getParam ( name , defs [ name ] ) ;
}
tinymce . create ( 'tinymce.plugins.PastePlugin' , {
init : function ( ed , url ) {
var t = this , cb ;
var t = this ;
t . editor = ed ;
t . url = url ;
@ -33,8 +63,12 @@
ed . execCallback ( 'paste_postprocess' , pl , o ) ;
} ) ;
// Initialize plain text flag
ed . pasteAsPlainText = false ;
// This function executes the process handlers and inserts the contents
function process ( o ) {
// force_rich overrides plain text mode set by user, important for pasting with execCommand
function process ( o , force _rich ) {
var dom = ed . dom ;
// Execute pre process handlers
@ -49,18 +83,51 @@
// Serialize content
o . content = ed . serializer . serialize ( o . node , { getInner : 1 } ) ;
// Insert cleaned content. We need to handle insertion of contents containing block elements separately
if ( /<(p|h[1-6]|ul|ol)/ . test ( o . content ) )
// Plain text option active?
if ( ( ! force _rich ) && ( ed . pasteAsPlainText ) ) {
t . _insertPlainText ( ed , dom , o . content ) ;
if ( ! getParam ( ed , "paste_text_sticky" ) ) {
ed . pasteAsPlainText = false ;
ed . controlManager . setActive ( "pastetext" , false ) ;
}
} else if ( /<(p|h[1-6]|ul|ol)/ . test ( o . content ) ) {
// Handle insertion of contents containing block elements separately
t . _insertBlockContent ( ed , dom , o . content ) ;
else
} else {
t . _insert ( o . content ) ;
} ;
}
}
// Add command for external usage
ed . addCommand ( 'mceInsertClipboardContent' , function ( u , o ) {
process ( o ) ;
process ( o , true );
} ) ;
if ( ! getParam ( ed , "paste_text_use_dialog" ) ) {
ed . addCommand ( 'mcePasteText' , function ( u , v ) {
var cookie = tinymce . util . Cookie ;
ed . pasteAsPlainText = ! ed . pasteAsPlainText ;
ed . controlManager . setActive ( 'pastetext' , ed . pasteAsPlainText ) ;
if ( ( ed . pasteAsPlainText ) && ( ! cookie . get ( "tinymcePasteText" ) ) ) {
if ( getParam ( ed , "paste_text_sticky" ) ) {
ed . windowManager . alert ( "Paste is now in plain text mode. Click again to toggle back to regular paste mode. After you paste something you will be returned to regular paste mode." ) ;
} else {
ed . windowManager . alert ( "Paste is now in plain text mode. Click again to toggle back to regular paste mode." ) ;
}
if ( ! getParam ( ed , "paste_text_notifyalways" ) ) {
cookie . set ( "tinymcePasteText" , "1" , new Date ( new Date ( ) . getFullYear ( ) + 1 , 12 , 31 ) )
}
}
} ) ;
}
ed . addButton ( 'pastetext' , { title : 'paste.paste_text_desc' , cmd : 'mcePasteText' } ) ;
ed . addButton ( 'selectall' , { title : 'paste.selectall_desc' , cmd : 'selectall' } ) ;
// This function grabs the contents from the clipboard by adding a
// hidden div and placing the caret inside it and after the browser paste
// is done it grabs that contents and processes that
@ -71,7 +138,7 @@
return ;
// Create container to paste into
n = dom . add ( body , 'div' , { id : '_mcePaste' }, '\uFEFF' ) ;
n = dom . add ( body , 'div' , { id : '_mcePaste' , 'class' : 'mcePaste' }, '\uFEFF' ) ;
// If contentEditable mode we need to find out the position of the closest element
if ( body != ed . getDoc ( ) . body )
@ -112,6 +179,14 @@
// Block the real paste event
return tinymce . dom . Event . cancel ( e ) ;
} else {
function block ( e ) {
e . preventDefault ( ) ;
} ;
// Block mousedown and click to prevent selection change
dom . bind ( ed . getDoc ( ) , 'mousedown' , block ) ;
dom . bind ( ed . getDoc ( ) , 'keydown' , block ) ;
or = ed . selection . getRng ( ) ;
// Move caret into hidden div
@ -123,10 +198,16 @@
// Wait a while and grab the pasted contents
window . setTimeout ( function ( ) {
var h = '' , nl = dom . select ( 'div [id=_mcePaste] ') ;
var h = '' , nl = dom . select ( 'div .mcePaste ') ;
// WebKit will split the div into multiple ones so this will loop through then all and join them to get the whole HTML string
each ( nl , function ( n ) {
// WebKit duplicates the divs so we need to remove them
each ( dom . select ( 'div.mcePaste' , n ) , function ( n ) {
dom . remove ( n , 1 ) ;
} ) ;
// Contents in WebKit is sometimes wrapped in a apple style span so we need to grab it from that one
h += ( dom . select ( '> span.Apple-style-span div' , n ) [ 0 ] || dom . select ( '> span.Apple-style-span' , n ) [ 0 ] || n ) . innerHTML ;
} ) ;
@ -140,12 +221,16 @@
sel . setRng ( or ) ;
process ( { content : h } ) ;
// Unblock events ones we got the contents
dom . unbind ( ed . getDoc ( ) , 'mousedown' , block ) ;
dom . unbind ( ed . getDoc ( ) , 'keydown' , block ) ;
} , 0 ) ;
}
} ;
}
// Check if we should use the new auto process method
if ( ed. getParam ( 'paste_auto_cleanup_on_paste' , true ) ) {
if ( getParam( ed , "paste_auto_cleanup_on_paste" ) ) {
// Is it's Opera or older FF use key handler
if ( tinymce . isOpera || /Firefox\/2/ . test ( navigator . userAgent ) ) {
ed . onKeyDown . add ( function ( ed , e ) {
@ -161,7 +246,7 @@
}
// Block all drag/drop events
if ( ed. getParam ( 'paste_block_drop' ) ) {
if ( getParam( ed , "paste_block_drop" ) ) {
ed . onInit . add ( function ( ) {
ed . dom . bind ( ed . getBody ( ) , [ 'dragend' , 'dragover' , 'draggesture' , 'dragdrop' , 'drop' , 'drag' ] , function ( e ) {
e . preventDefault ( ) ;
@ -187,10 +272,15 @@
} ,
_preProcess : function ( pl , o ) {
var ed = this . editor , h = o . content , process , stripClass ;
//console.log('Before preprocess:' + o.content);
var ed = this . editor ,
h = o . content ,
grep = tinymce . grep ,
explode = tinymce . explode ,
trim = tinymce . trim ,
len , stripClass ;
function process ( items ) {
each ( items , function ( v ) {
// Remove or replace
@ -199,81 +289,203 @@
else
h = h . replace ( v [ 0 ] , v [ 1 ] ) ;
} ) ;
} ;
}
// Detect Word content and process it more aggressive
if ( / ( class=\ "?Mso|style=\ "[^\ "]*\bmso\ -|w:WordDocument) /. test ( h ) || o . wordContent ) {
o . wordContent = true ; // Mark the pasted contents as word specific content
if ( / class="?Mso|style="[^"]*\bmso-|w:WordDocument/i . test ( h ) || o . wordContent ) {
o . wordContent = true ; // Mark the pasted contents as word specific content
//console.log('Word contents detected.');
// Process away some basic content
process ( [
/^\s*( )+/g , // nbsp entities at the start of contents
/( |<br[^>]*>)+\s*$/g // nbsp entities at the end of contents
/^\s*( )+/g i , // & nbsp; entities at the start of contents
/( |<br[^>]*>)+\s*$/g i // & nbsp; entities at the end of contents
] ) ;
if ( ed . getParam ( 'paste_convert_middot_lists' , true ) ) {
if ( getParam ( ed , "paste_convert_headers_to_strong" ) ) {
h = h . replace ( /<p [^>]*class="?MsoHeading"?[^>]*>(.*?)<\/p>/gi , "<p><strong>$1</strong></p>" ) ;
}
if ( getParam ( ed , "paste_convert_middot_lists" ) ) {
process ( [
[ /<!--\[if !supportLists\]-->/gi , '$&__MCE_ITEM__' ] , // Convert supportLists to a list item marker
[ /(<span[^>]+:\s*symbol[^>]+>)/gi , '$1__MCE_ITEM__' ] , // Convert symbol spans to list items
[ /(<span[^>]+mso-list:[^>]+>)/gi , '$1__MCE_ITEM__' ] // Convert mso-list to item marker
[ /<!--\[if !supportLists\]-->/gi , '$&__MCE_ITEM__' ] , // Convert supportLists to a list item marker
[ /(<span[^>]+(?:mso-list:|:\s*symbol)[^>]+>)/gi , '$1__MCE_ITEM__' ] // Convert mso-list and symbol spans to item markers
] ) ;
}
process ( [
/<!--[\s\S]+?-->/gi , // Word comments
/<\/?(img|font|meta|link|style|div|v:\w+)[^>]*>/gi , // Remove some tags including VML content
/<\\?\?xml[^>]*>/gi , // XML namespace declarations
/<\/?o:[^>]*>/gi , // MS namespaced elements <o:tag>
/ (id|name|language|type|on\w+|v:\w+)=\"([^\"]*)\"/gi , // on.., class, style and language attributes with quotes
/ (id|name|language|type|on\w+|v:\w+)=(\w+)/gi , // on.., class, style and language attributes without quotes (IE)
[ /<(\/?)s>/gi , '<$1strike>' ] , // Convert <s> into <strike> for line-though
/<script[^>]+>[\s\S]*?<\/script>/gi , // All scripts elements for msoShowComment for example
[ / /g , '\u00a0' ] // Replace nsbp entites to char since it's easier to handle
// Word comments like conditional comments etc
/<!--[\s\S]+?-->/gi ,
// Remove comments, scripts (e.g., msoShowComment), XML tag, VML content, MS Office namespaced tags, and a few other tags
/<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi ,
// Convert <s> into <strike> for line-though
[ /<(\/?)s>/gi , "<$1strike>" ] ,
// Replace nsbp entites to char since it's easier to handle
[ / /gi , "\u00a0" ]
] ) ;
// Remove bad attributes, with or without quotes, ensuring that attribute text is really inside a tag.
// 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.
do {
len = h . length ;
h = h . replace ( /(<[a-z][^>]*\s)(?:id|name|language|type|on\w+|\w+:\w+)=(?:"[^"]*"|\w+)\s?/gi , "$1" ) ;
} while ( len != h . length ) ;
// Remove all spans if no styles is to be retained
if ( ! ed . getParam ( 'paste_retain_style_properties' ) ) {
if ( getParam ( ed , "paste_retain_style_properties" ) . replace ( /^none$/i , "" ) . length == 0 ) {
h = h . replace ( /<\/?span[^>]*>/gi , "" ) ;
} else {
// We're keeping styles, so at least clean them up.
// CSS Reference: http://msdn.microsoft.com/en-us/library/aa155477.aspx
process ( [
/<\/?(span)[^>]*>/gi
// Convert <span style="mso-spacerun:yes">___</span> to string of alternating breaking/non-breaking spaces of same length
[ /<span\s+style\s*=\s*"\s*mso-spacerun\s*:\s*yes\s*;?\s*"\s*>([\s\u00a0]*)<\/span>/gi ,
function ( str , spaces ) {
return ( spaces . length > 0 ) ? spaces . replace ( /./ , " " ) . slice ( Math . floor ( spaces . length / 2 ) ) . split ( "" ) . join ( "\u00a0" ) : "" ;
}
] ,
// Examine all styles: delete junk, transform some, and keep the rest
[ /(<[a-z][^>]*)\sstyle="([^"]*)"/gi ,
function ( str , tag , style ) {
var n = [ ] ,
i = 0 ,
s = explode ( trim ( style ) . replace ( /"/gi , "'" ) , ";" ) ;
// Examine each style definition within the tag's style attribute
each ( s , function ( v ) {
var name , value ,
parts = explode ( v , ":" ) ;
function ensureUnits ( v ) {
return v + ( ( v !== "0" ) && ( /\d$/ . test ( v ) ) ) ? "px" : "" ;
}
if ( parts . length == 2 ) {
name = parts [ 0 ] . toLowerCase ( ) ;
value = parts [ 1 ] . toLowerCase ( ) ;
// Translate certain MS Office styles into their CSS equivalents
switch ( name ) {
case "mso-padding-alt" :
case "mso-padding-top-alt" :
case "mso-padding-right-alt" :
case "mso-padding-bottom-alt" :
case "mso-padding-left-alt" :
case "mso-margin-alt" :
case "mso-margin-top-alt" :
case "mso-margin-right-alt" :
case "mso-margin-bottom-alt" :
case "mso-margin-left-alt" :
case "mso-table-layout-alt" :
case "mso-height" :
case "mso-width" :
case "mso-vertical-align-alt" :
n [ i ++ ] = name . replace ( /^mso-|-alt$/g , "" ) + ":" + ensureUnits ( value ) ;
return ;
case "horiz-align" :
n [ i ++ ] = "text-align:" + value ;
return ;
case "vert-align" :
n [ i ++ ] = "vertical-align:" + value ;
return ;
case "font-color" :
case "mso-foreground" :
n [ i ++ ] = "color:" + value ;
return ;
case "mso-background" :
case "mso-highlight" :
n [ i ++ ] = "background:" + value ;
return ;
case "mso-default-height" :
n [ i ++ ] = "min-height:" + ensureUnits ( value ) ;
return ;
case "mso-default-width" :
n [ i ++ ] = "min-width:" + ensureUnits ( value ) ;
return ;
case "mso-padding-between-alt" :
n [ i ++ ] = "border-collapse:separate;border-spacing:" + ensureUnits ( value ) ;
return ;
case "text-line-through" :
if ( ( value == "single" ) || ( value == "double" ) ) {
n [ i ++ ] = "text-decoration:line-through" ;
}
return ;
case "mso-zero-height" :
if ( value == "yes" ) {
n [ i ++ ] = "display:none" ;
}
return ;
}
// Eliminate all MS Office style definitions that have no CSS equivalent by examining the first characters in the name
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 ) ) {
return ;
}
// If it reached this point, it must be a valid CSS style
n [ i ++ ] = name + ":" + parts [ 1 ] ; // Lower-case name, but keep value case
}
} ) ;
// If style attribute contained any valid styles the re-write it; otherwise delete style attribute.
if ( i > 0 ) {
return tag + ' style="' + n . join ( ';' ) + '"' ;
} else {
return tag ;
}
}
]
] ) ;
}
}
// Allow for class names to be retained if desired; either all, or just the ones from Word
// Note that the paste_strip_class_attributes: 'none, verify_css_classes: true is also a good variation.
stripClass = ed . getParam ( 'paste_strip_class_attributes' ) ;
if ( stripClass != 'none' ) {
// Cleans everything but mceItem... classes
function cleanClasses ( str , cls ) {
var i , out = '' ;
// Replace headers with <strong>
if ( getParam ( ed , "paste_convert_headers_to_strong" ) ) {
process ( [
[ /<h[1-6][^>]*>/gi , "<p><strong>" ] ,
[ /<\/h[1-6][^>]*>/gi , "</strong></p>" ]
] ) ;
}
// Remove all classes
if ( stripClass == 'all' )
return '' ;
// Class attribute options are: leave all as-is ("none"), remove all ("all"), or remove only those starting with mso ("mso").
// Note:- paste_strip_class_attributes: "none", verify_css_classes: true is also a good variation.
stripClass = getParam ( ed , "paste_strip_class_attributes" ) ;
cls = tinymce . explode ( cls , ' ' ) ;
if ( stripClass !== "none" ) {
function removeClasses ( match , g1 ) {
if ( stripClass === "all" )
return '' ;
for ( i = cls . length - 1 ; i >= 0 ; i -- ) {
// Remove Mso classes
if ( ! /^(Mso)/i . test ( cls [ i ] ) )
out += ( ! out ? '' : ' ' ) + cls [ i ] ;
}
var cls = grep ( explode ( g1 . replace ( /^(["'])(.*)\1$/ , "$2" ) , " " ) ,
function ( v ) {
return ( /^(?!mso)/i . test ( v ) ) ;
}
) ;
return ' class="' + out + '"' ;
return cls . length ? ' class="' + cls . join ( " " ) + '"' : ' ';
} ;
process ( [
[ / class=\"([^\"]*)\"/gi , cleanClasses ] , // class attributes with quotes
[ / class=(\w+)/gi , cleanClasses ] // class attributes without quotes (IE)
] ) ;
h = h . replace ( / class="([^"]+)"/gi , removeClasses ) ;
h = h . replace ( / class=(\w+)/gi , removeClasses ) ;
}
// Remove spans option
if ( ed . getParam ( 'paste_remove_spans' ) ) {
process ( [
/<\/?(span)[^>]*>/gi
] ) ;
if ( getParam ( ed , "paste_remove_spans" ) ) {
h = h . replace ( /<\/?span[^>]*>/gi , "" ) ;
}
//console.log('After preprocess:' + h);
@ -294,56 +506,58 @@
dom . remove ( a , 1 ) ;
} ) ;
if ( t. editor . getParam ( 'paste_convert_middot_lists' , true ) )
if ( getParam( ed , "paste_convert_middot_lists" ) ) {
t . _convertLists ( pl , o ) ;
}
// Process styles
styleProps = ed. getParam ( 'paste_retain_style_properties' ) ; // retained properties
styleProps = getParam( ed , "paste_retain_style_properties" ) ; // retained properties
// If string property then split it
if ( tinymce . is ( styleProps , 'string' ) )
styleProps = tinymce . explode ( styleProps );
// Process only if a string was specified and not equal to "all" or "*"
if ( ( tinymce . is ( styleProps , "string" ) ) && ( styleProps !== "all" ) && ( styleProps !== "*" ) ) {
styleProps = tinymce . explode ( styleProps .replace ( /^none$/i , "" ) );
// Retains some style properties
each ( dom . select ( '*' , o . node ) , function ( el ) {
var newStyle = { } , npc = 0 , i , sp , sv ;
// Store a subset of the existing styles
if ( styleProps ) {
for ( i = 0 ; i < styleProps . length ; i ++ ) {
sp = styleProps [ i ] ;
sv = dom . getStyle ( el , sp ) ;
if ( sv ) {
newStyle [ sp ] = sv ;
npc ++ ;
// Retains some style properties
each ( dom . select ( '*' , o . node ) , function ( el ) {
var newStyle = { } , npc = 0 , i , sp , sv ;
// Store a subset of the existing styles
if ( styleProps ) {
for ( i = 0 ; i < styleProps . length ; i ++ ) {
sp = styleProps [ i ] ;
sv = dom . getStyle ( el , sp ) ;
if ( sv ) {
newStyle [ sp ] = sv ;
npc ++ ;
}
}
}
}
// Remove all of the existing styles
dom . setAttrib ( el , 'style' , '' ) ;
// Remove all of the existing styles
dom . setAttrib ( el , 'style' , '' ) ;
if ( styleProps && npc > 0 )
dom . setStyles ( el , newStyle ) ; // Add back the stored subset of styles
else // Remove empty span tags that do not have class attributes
if ( el . nodeName == 'SPAN' && ! el . className )
dom . remove ( el , true ) ;
} ) ;
if ( styleProps && npc > 0 )
dom . setStyles ( el , newStyle ) ; // Add back the stored subset of styles
else // Remove empty span tags that do not have class attributes
if ( el . nodeName == 'SPAN' && ! el . className )
dom . remove ( el , true ) ;
} ) ;
}
}
// Remove all style information or only specifically on WebKit to avoid the style bug on that browser
if ( ed. getParam( "paste_remove_styles" ) || ( ed. getParam( "paste_remove_styles_if_webkit" ) && tinymce . isWebKit ) ) {
if ( getParam( ed , "paste_remove_styles" ) || ( getParam( ed , "paste_remove_styles_if_webkit" ) && tinymce . isWebKit ) ) {
each ( dom . select ( '*[style]' , o . node ) , function ( el ) {
el . removeAttribute ( 'style' ) ;
el . removeAttribute ( ' mce_style') ;
el . removeAttribute ( ' _ mce_style') ;
} ) ;
} else {
if ( tinymce . isWebKit ) {
// 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 ..." />
// Removing the mce_style that contains the real value will force the Serializer engine to compress the styles
each ( dom . select ( '*' , o . node ) , function ( el ) {
el . removeAttribute ( ' mce_style') ;
el . removeAttribute ( ' _ mce_style') ;
} ) ;
}
}
@ -435,7 +649,7 @@
* This logic can be improved so text nodes at the start / end remain in the start / end block elements
* /
_insertBlockContent : function ( ed , dom , content ) {
var parentBlock , marker , sel = ed . selection , last , elm , vp , y , elmHeight ;
var parentBlock , marker , sel = ed . selection , last , elm , vp , y , elmHeight , markerId = 'mce_marker' ;
function select ( n ) {
var r ;
@ -449,11 +663,11 @@
sel . select ( n , 1 ) ;
sel . collapse ( false ) ;
}
} ;
}
// Insert a marker for the caret position
this . _insert ( '<span id=" _marker "> </span>', 1 ) ;
marker = dom . get ( '_marker' ) ;
this . _insert ( '<span id=" ' + markerId + ' "> </span>', 1 ) ;
marker = dom . get ( markerId ) ;
parentBlock = dom . getParent ( marker , 'p,h1,h2,h3,h4,h5,h6,ul,ol,th,td' ) ;
// If it's a parent block but not a table cell
@ -474,7 +688,9 @@
sel . collapse ( 0 ) ;
}
dom . remove ( '_marker' ) ; // Remove marker if it's left
// Remove marker if it's left
while ( elm = dom . get ( markerId ) )
dom . remove ( elm ) ;
// Get element, position and height
elm = sel . getStart ( ) ;
@ -501,31 +717,213 @@
ed . execCommand ( tinymce . isGecko ? 'insertHTML' : 'mceInsertContent' , false , h , { skip _undo : skip _undo } ) ;
} ,
/ * *
* Instead of the old plain text method which tried to re - create a paste operation , the
* new approach adds a plain text mode toggle switch that changes the behavior of paste .
* This function is passed the same input that the regular paste plugin produces .
* It performs additional scrubbing and produces ( and inserts ) the plain text .
* This approach leverages all of the great existing functionality in the paste
* plugin , and requires minimal changes to add the new functionality .
* Speednet - June 2009
* /
_insertPlainText : function ( ed , dom , h ) {
var i , len , pos , rpos , node , breakElms , before , after ,
w = ed . getWin ( ) ,
d = ed . getDoc ( ) ,
sel = ed . selection ,
is = tinymce . is ,
inArray = tinymce . inArray ,
linebr = getParam ( ed , "paste_text_linebreaktype" ) ,
rl = getParam ( ed , "paste_text_replacements" ) ;
function process ( items ) {
each ( items , function ( v ) {
if ( v . constructor == RegExp )
h = h . replace ( v , "" ) ;
else
h = h . replace ( v [ 0 ] , v [ 1 ] ) ;
} ) ;
} ;
if ( ( typeof ( h ) === "string" ) && ( h . length > 0 ) ) {
if ( ! entities )
entities = ( "34,quot,38,amp,39,apos,60,lt,62,gt," + ed . serializer . settings . entities ) . split ( "," ) ;
// If HTML content with line-breaking tags, then remove all cr/lf chars because only tags will break a line
if ( /<(?:p|br|h[1-6]|ul|ol|dl|table|t[rdh]|div|blockquote|fieldset|pre|address|center)[^>]*>/i . test ( h ) ) {
process ( [
/[\n\r]+/g
] ) ;
} else {
// Otherwise just get rid of carriage returns (only need linefeeds)
process ( [
/\r+/g
] ) ;
}
process ( [
[ /<\/(?: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
[ /<br[^>]*>|<\/tr>/gi , "\n" ] , // Single linebreak for <br /> tags and table rows
[ /<\/t[dh]>\s*<t[dh][^>]*>/gi , "\t" ] , // Table cells get tabs betweem them
/<[a-z!\/?][^>]*>/gi , // Delete all remaining tags
[ / /gi , " " ] , // Convert non-break spaces to regular spaces (remember, *plain text*)
[
// HTML entity
/&(#\d+|[a-z0-9]{1,10});/gi ,
// Replace with actual character
function ( e , s ) {
if ( s . charAt ( 0 ) === "#" ) {
return String . fromCharCode ( s . slice ( 1 ) ) ;
}
else {
return ( ( e = inArray ( entities , s ) ) > 0 ) ? String . fromCharCode ( entities [ e - 1 ] ) : " " ;
}
}
] ,
[ /(?:(?!\n)\s)*(\n+)(?:(?!\n)\s)*/gi , "$1" ] , // Cool little RegExp deletes whitespace around linebreak chars.
[ /\n{3,}/g , "\n\n" ] , // Max. 2 consecutive linebreaks
/^\s+|\s+$/g // Trim the front & back
] ) ;
h = dom . encode ( h ) ;
// Delete any highlighted text before pasting
if ( ! sel . isCollapsed ( ) ) {
d . execCommand ( "Delete" , false , null ) ;
}
// Perform default or custom replacements
if ( is ( rl , "array" ) || ( is ( rl , "array" ) ) ) {
process ( rl ) ;
}
else if ( is ( rl , "string" ) ) {
process ( new RegExp ( rl , "gi" ) ) ;
}
// Treat paragraphs as specified in the config
if ( linebr == "none" ) {
process ( [
[ /\n+/g , " " ]
] ) ;
}
else if ( linebr == "br" ) {
process ( [
[ /\n/g , "<br />" ]
] ) ;
}
else {
process ( [
/^\s+|\s+$/g ,
[ /\n\n/g , "</p><p>" ] ,
[ /\n/g , "<br />" ]
] ) ;
}
// This next piece of code handles the situation where we're pasting more than one paragraph of plain
// text, and we are pasting the content into the middle of a block node in the editor. The block
// node gets split at the selection point into "Para A" and "Para B" (for the purposes of explaining).
// The first paragraph of the pasted text is appended to "Para A", and the last paragraph of the
// pasted text is prepended to "Para B". Any other paragraphs of pasted text are placed between
// "Para A" and "Para B". This code solves a host of problems with the original plain text plugin and
// now handles styles correctly. (Pasting plain text into a styled paragraph is supposed to make the
// plain text take the same style as the existing paragraph.)
if ( ( pos = h . indexOf ( "</p><p>" ) ) != - 1 ) {
rpos = h . lastIndexOf ( "</p><p>" ) ;
node = sel . getNode ( ) ;
breakElms = [ ] ; // Get list of elements to break
do {
if ( node . nodeType == 1 ) {
// Don't break tables and break at body
if ( node . nodeName == "TD" || node . nodeName == "BODY" ) {
break ;
}
breakElms [ breakElms . length ] = node ;
}
} while ( node = node . parentNode ) ;
// Are we in the middle of a block node?
if ( breakElms . length > 0 ) {
before = h . substring ( 0 , pos ) ;
after = "" ;
for ( i = 0 , len = breakElms . length ; i < len ; i ++ ) {
before += "</" + breakElms [ i ] . nodeName . toLowerCase ( ) + ">" ;
after += "<" + breakElms [ breakElms . length - i - 1 ] . nodeName . toLowerCase ( ) + ">" ;
}
if ( pos == rpos ) {
h = before + after + h . substring ( pos + 7 ) ;
}
else {
h = before + h . substring ( pos + 4 , rpos + 4 ) + after + h . substring ( rpos + 7 ) ;
}
}
}
// Insert content at the caret, plus add a marker for repositioning the caret
ed . execCommand ( "mceInsertRawHTML" , false , h + '<span id="_plain_text_marker"> </span>' ) ;
// Reposition the caret to the marker, which was placed immediately after the inserted content.
// Needs to be done asynchronously (in window.setTimeout) or else it doesn't work in all browsers.
// The second part of the code scrolls the content up if the caret is positioned off-screen.
// This is only necessary for WebKit browsers, but it doesn't hurt to use for all.
window . setTimeout ( function ( ) {
var marker = dom . get ( '_plain_text_marker' ) ,
elm , vp , y , elmHeight ;
sel . select ( marker , false ) ;
d . execCommand ( "Delete" , false , null ) ;
marker = null ;
// Get element, position and height
elm = sel . getStart ( ) ;
vp = dom . getViewPort ( w ) ;
y = dom . getPos ( elm ) . y ;
elmHeight = elm . clientHeight ;
// Is element within viewport if not then scroll it into view
if ( ( y < vp . y ) || ( y + elmHeight > vp . y + vp . h ) ) {
d . body . scrollTop = y < vp . y ? y : y - vp . h + 25 ;
}
} , 0 ) ;
}
} ,
/ * *
* This method will open the old style paste dialogs . Some users might want the old behavior but still use the new cleanup engine .
* /
_legacySupport : function ( ) {
var t = this , ed = t . editor ;
// Register commands for backwards compatibility
each ( [ 'mcePasteText' , 'mcePasteWord' ] , function ( cmd ) {
ed . addCommand ( cmd , function ( ) {
// Register command(s) for backwards compatibility
ed . addCommand ( "mcePasteWord" , function ( ) {
ed . windowManager . open ( {
file : t . url + "/pasteword.htm" ,
width : parseInt ( getParam ( ed , "paste_dialog_width" ) ) ,
height : parseInt ( getParam ( ed , "paste_dialog_height" ) ) ,
inline : 1
} ) ;
} ) ;
if ( getParam ( ed , "paste_text_use_dialog" ) ) {
ed . addCommand ( "mcePasteText" , function ( ) {
ed . windowManager . open ( {
file : t . url + ( cmd == 'mcePasteText' ? '/pastetext.htm' : '/pasteword.htm' ) ,
width : parseInt ( ed . getParam ( "paste_dialog_width" , "450" ) ) ,
height : parseInt ( ed . getParam ( "paste_dialog_height" , "400" ) ) ,
file : t . url + "/pastetext.htm" ,
width : parseInt ( getParam( ed , "paste_dialog_width ") ) ,
height : parseInt ( getParam( ed , "paste_dialog_height ") ) ,
inline : 1
} ) ;
} ) ;
} ) ;
}
// Register buttons for backwards compatibility
ed . addButton ( 'pastetext' , { title : 'paste.paste_text_desc' , cmd : 'mcePasteText' } ) ;
ed . addButton ( 'pasteword' , { title : 'paste.paste_word_desc' , cmd : 'mcePasteWord' } ) ;
ed . addButton ( 'selectall' , { title : 'paste.selectall_desc' , cmd : 'selectall' } ) ;
// Register button for backwards compatibility
ed . addButton ( "pasteword" , { title : "paste.paste_word_desc" , cmd : "mcePasteWord" } ) ;
}
} ) ;
// Register plugin
tinymce . PluginManager . add ( 'paste' , tinymce . plugins . PastePlugin ) ;
} ) ( ) ;
tinymce . PluginManager . add ( "paste" , tinymce . plugins . PastePlugin ) ;
} ) ( ) ;