2012-05-16 02:18:46 +02:00
// FILE IS GENERATED BY COMBINING THE SOURCES IN THE "classes" DIRECTORY SO DON'T MODIFY THIS FILE DIRECTLY
2010-07-02 01:48:07 +02:00
( function ( win ) {
var whiteSpaceRe = /^\s*|\s*$/g ,
2012-05-16 02:18:46 +02:00
undef , isRegExpBroken = 'B' . replace ( /A(.)|B/ , '$1' ) === '$1' ;
2010-07-02 01:48:07 +02:00
var tinymce = {
majorVersion : '3' ,
2012-05-16 02:18:46 +02:00
minorVersion : '5.0.1' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
releaseDate : '2012-05-10' ,
2010-07-02 01:48:07 +02:00
_init : function ( ) {
var t = this , d = document , na = navigator , ua = na . userAgent , i , nl , n , base , p , v ;
t . isOpera = win . opera && opera . buildNumber ;
t . isWebKit = /WebKit/ . test ( ua ) ;
t . isIE = ! t . isWebKit && ! t . isOpera && ( /MSIE/gi ) . test ( ua ) && ( /Explorer/gi ) . test ( na . appName ) ;
t . isIE6 = t . isIE && /MSIE [56]/ . test ( ua ) ;
2012-03-21 04:47:31 +01:00
t . isIE7 = t . isIE && /MSIE [7]/ . test ( ua ) ;
t . isIE8 = t . isIE && /MSIE [8]/ . test ( ua ) ;
t . isIE9 = t . isIE && /MSIE [9]/ . test ( ua ) ;
2010-07-02 01:48:07 +02:00
t . isGecko = ! t . isWebKit && /Gecko/ . test ( ua ) ;
t . isMac = ua . indexOf ( 'Mac' ) != - 1 ;
t . isAir = /adobeair/i . test ( ua ) ;
t . isIDevice = /(iPad|iPhone)/ . test ( ua ) ;
2012-03-21 04:47:31 +01:00
t . isIOS5 = t . isIDevice && ua . match ( /AppleWebKit\/(\d*)/ ) [ 1 ] >= 534 ;
2010-07-02 01:48:07 +02:00
// TinyMCE .NET webcontrol might be setting the values for TinyMCE
if ( win . tinyMCEPreInit ) {
t . suffix = tinyMCEPreInit . suffix ;
t . baseURL = tinyMCEPreInit . base ;
t . query = tinyMCEPreInit . query ;
return ;
}
// Get suffix and base
t . suffix = '' ;
// If base element found, add that infront of baseURL
nl = d . getElementsByTagName ( 'base' ) ;
for ( i = 0 ; i < nl . length ; i ++ ) {
2012-05-16 02:18:46 +02:00
v = nl [ i ] . href ;
if ( v ) {
2010-07-02 01:48:07 +02:00
// Host only value like http://site.com or http://site.com:8008
if ( /^https?:\/\/[^\/]+$/ . test ( v ) )
v += '/' ;
base = v ? v . match ( /.*\// ) [ 0 ] : '' ; // Get only directory
}
}
function getBase ( n ) {
2012-03-21 04:47:31 +01:00
if ( n . src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/ . test ( n . src ) ) {
2010-07-02 01:48:07 +02:00
if ( /_(src|dev)\.js/g . test ( n . src ) )
t . suffix = '_src' ;
if ( ( p = n . src . indexOf ( '?' ) ) != - 1 )
t . query = n . src . substring ( p + 1 ) ;
t . baseURL = n . src . substring ( 0 , n . src . lastIndexOf ( '/' ) ) ;
// If path to script is relative and a base href was found add that one infront
// the src property will always be an absolute one on non IE browsers and IE 8
// so this logic will basically only be executed on older IE versions
if ( base && t . baseURL . indexOf ( '://' ) == - 1 && t . baseURL . indexOf ( '/' ) !== 0 )
t . baseURL = base + t . baseURL ;
return t . baseURL ;
}
return null ;
} ;
// Check document
nl = d . getElementsByTagName ( 'script' ) ;
for ( i = 0 ; i < nl . length ; i ++ ) {
if ( getBase ( nl [ i ] ) )
return ;
}
// Check head
n = d . getElementsByTagName ( 'head' ) [ 0 ] ;
if ( n ) {
nl = n . getElementsByTagName ( 'script' ) ;
for ( i = 0 ; i < nl . length ; i ++ ) {
if ( getBase ( nl [ i ] ) )
return ;
}
}
return ;
} ,
is : function ( o , t ) {
if ( ! t )
2012-05-16 02:18:46 +02:00
return o !== undef ;
2010-07-02 01:48:07 +02:00
if ( t == 'array' && ( o . hasOwnProperty && o instanceof Array ) )
return true ;
return typeof ( o ) == t ;
} ,
2012-03-21 04:47:31 +01:00
makeMap : function ( items , delim , map ) {
var i ;
items = items || [ ] ;
delim = delim || ',' ;
if ( typeof ( items ) == "string" )
items = items . split ( delim ) ;
map = map || { } ;
i = items . length ;
while ( i -- )
map [ items [ i ] ] = { } ;
return map ;
} ,
2010-07-02 01:48:07 +02:00
each : function ( o , cb , s ) {
var n , l ;
if ( ! o )
return 0 ;
s = s || o ;
2012-05-16 02:18:46 +02:00
if ( o . length !== undef ) {
2010-07-02 01:48:07 +02:00
// Indexed arrays, needed for Safari
for ( n = 0 , l = o . length ; n < l ; n ++ ) {
if ( cb . call ( s , o [ n ] , n , o ) === false )
return 0 ;
}
} else {
// Hashtables
for ( n in o ) {
if ( o . hasOwnProperty ( n ) ) {
if ( cb . call ( s , o [ n ] , n , o ) === false )
return 0 ;
}
}
}
return 1 ;
} ,
map : function ( a , f ) {
var o = [ ] ;
tinymce . each ( a , function ( v ) {
o . push ( f ( v ) ) ;
} ) ;
return o ;
} ,
grep : function ( a , f ) {
var o = [ ] ;
tinymce . each ( a , function ( v ) {
if ( ! f || f ( v ) )
o . push ( v ) ;
} ) ;
return o ;
} ,
inArray : function ( a , v ) {
var i , l ;
if ( a ) {
for ( i = 0 , l = a . length ; i < l ; i ++ ) {
if ( a [ i ] === v )
return i ;
}
}
return - 1 ;
} ,
2012-05-16 02:18:46 +02:00
extend : function ( obj , ext ) {
var i , l , name , args = arguments , value ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
for ( i = 1 , l = args . length ; i < l ; i ++ ) {
ext = args [ i ] ;
for ( name in ext ) {
if ( ext . hasOwnProperty ( name ) ) {
value = ext [ name ] ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( value !== undef ) {
obj [ name ] = value ;
}
}
}
2010-07-02 01:48:07 +02:00
}
2012-05-16 02:18:46 +02:00
return obj ;
2010-07-02 01:48:07 +02:00
} ,
trim : function ( s ) {
return ( s ? '' + s : '' ) . replace ( whiteSpaceRe , '' ) ;
} ,
2012-03-21 04:47:31 +01:00
create : function ( s , p , root ) {
2010-07-02 01:48:07 +02:00
var t = this , sp , ns , cn , scn , c , de = 0 ;
// Parse : <prefix> <class>:<super class>
s = /^((static) )?([\w.]+)(:([\w.]+))?/ . exec ( s ) ;
cn = s [ 3 ] . match ( /(^|\.)(\w+)$/i ) [ 2 ] ; // Class name
// Create namespace for new class
2012-03-21 04:47:31 +01:00
ns = t . createNS ( s [ 3 ] . replace ( /\.\w+$/ , '' ) , root ) ;
2010-07-02 01:48:07 +02:00
// Class already exists
if ( ns [ cn ] )
return ;
// Make pure static class
if ( s [ 2 ] == 'static' ) {
ns [ cn ] = p ;
if ( this . onCreate )
this . onCreate ( s [ 2 ] , s [ 3 ] , ns [ cn ] ) ;
return ;
}
// Create default constructor
if ( ! p [ cn ] ) {
p [ cn ] = function ( ) { } ;
de = 1 ;
}
// Add constructor and methods
ns [ cn ] = p [ cn ] ;
t . extend ( ns [ cn ] . prototype , p ) ;
// Extend
if ( s [ 5 ] ) {
sp = t . resolve ( s [ 5 ] ) . prototype ;
scn = s [ 5 ] . match ( /\.(\w+)$/i ) [ 1 ] ; // Class name
// Extend constructor
c = ns [ cn ] ;
if ( de ) {
// Add passthrough constructor
ns [ cn ] = function ( ) {
return sp [ scn ] . apply ( this , arguments ) ;
} ;
} else {
// Add inherit constructor
ns [ cn ] = function ( ) {
this . parent = sp [ scn ] ;
return c . apply ( this , arguments ) ;
} ;
}
ns [ cn ] . prototype [ cn ] = ns [ cn ] ;
// Add super methods
t . each ( sp , function ( f , n ) {
ns [ cn ] . prototype [ n ] = sp [ n ] ;
} ) ;
// Add overridden methods
t . each ( p , function ( f , n ) {
// Extend methods if needed
if ( sp [ n ] ) {
ns [ cn ] . prototype [ n ] = function ( ) {
this . parent = sp [ n ] ;
return f . apply ( this , arguments ) ;
} ;
} else {
if ( n != cn )
ns [ cn ] . prototype [ n ] = f ;
}
} ) ;
}
// Add static methods
t . each ( p [ 'static' ] , function ( f , n ) {
ns [ cn ] [ n ] = f ;
} ) ;
if ( this . onCreate )
this . onCreate ( s [ 2 ] , s [ 3 ] , ns [ cn ] . prototype ) ;
} ,
walk : function ( o , f , n , s ) {
s = s || this ;
if ( o ) {
if ( n )
o = o [ n ] ;
tinymce . each ( o , function ( o , i ) {
if ( f . call ( s , o , i , n ) === false )
return false ;
tinymce . walk ( o , f , n , s ) ;
} ) ;
}
} ,
createNS : function ( n , o ) {
var i , v ;
o = o || win ;
n = n . split ( '.' ) ;
for ( i = 0 ; i < n . length ; i ++ ) {
v = n [ i ] ;
if ( ! o [ v ] )
o [ v ] = { } ;
o = o [ v ] ;
}
return o ;
} ,
resolve : function ( n , o ) {
var i , l ;
o = o || win ;
n = n . split ( '.' ) ;
for ( i = 0 , l = n . length ; i < l ; i ++ ) {
o = o [ n [ i ] ] ;
if ( ! o )
break ;
}
return o ;
} ,
addUnload : function ( f , s ) {
2012-05-16 02:18:46 +02:00
var t = this , unload ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
unload = function ( ) {
var li = t . unloads , o , n ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( li ) {
// Call unload handlers
for ( n in li ) {
o = li [ n ] ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( o && o . func )
o . func . call ( o . scope , 1 ) ; // Send in one arg to distinct unload and user destroy
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Detach unload function
if ( win . detachEvent ) {
win . detachEvent ( 'onbeforeunload' , fakeUnload ) ;
win . detachEvent ( 'onunload' , unload ) ;
} else if ( win . removeEventListener )
win . removeEventListener ( 'unload' , unload , false ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Destroy references
t . unloads = o = li = w = unload = 0 ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Run garbarge collector on IE
if ( win . CollectGarbage )
CollectGarbage ( ) ;
}
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
function fakeUnload ( ) {
var d = document ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
function stop ( ) {
// Prevent memory leak
d . detachEvent ( 'onstop' , stop ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Call unload handler
if ( unload )
unload ( ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
d = 0 ;
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Is there things still loading, then do some magic
if ( d . readyState == 'interactive' ) {
// Fire unload when the currently loading page is stopped
if ( d )
d . attachEvent ( 'onstop' , stop ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Remove onstop listener after a while to prevent the unload function
// to execute if the user presses cancel in an onbeforeunload
// confirm dialog and then presses the browser stop button
win . setTimeout ( function ( ) {
2010-07-02 01:48:07 +02:00
if ( d )
2012-05-16 02:18:46 +02:00
d . detachEvent ( 'onstop' , stop ) ;
} , 0 ) ;
}
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
f = { func : f , scope : s || this } ;
if ( ! t . unloads ) {
2010-07-02 01:48:07 +02:00
// Attach unload handler
if ( win . attachEvent ) {
win . attachEvent ( 'onunload' , unload ) ;
win . attachEvent ( 'onbeforeunload' , fakeUnload ) ;
} else if ( win . addEventListener )
win . addEventListener ( 'unload' , unload , false ) ;
// Setup initial unload handler array
t . unloads = [ f ] ;
} else
t . unloads . push ( f ) ;
return f ;
} ,
removeUnload : function ( f ) {
var u = this . unloads , r = null ;
tinymce . each ( u , function ( o , i ) {
if ( o && o . func == f ) {
u . splice ( i , 1 ) ;
r = f ;
return false ;
}
} ) ;
return r ;
} ,
explode : function ( s , d ) {
2012-05-16 02:18:46 +02:00
if ( ! s || tinymce . is ( s , 'array' ) ) {
return s ;
}
return tinymce . map ( s . split ( d || ',' ) , tinymce . trim ) ;
2010-07-02 01:48:07 +02:00
} ,
_addVer : function ( u ) {
var v ;
if ( ! this . query )
return u ;
v = ( u . indexOf ( '?' ) == - 1 ? '?' : '&' ) + this . query ;
if ( u . indexOf ( '#' ) == - 1 )
return u + v ;
return u . replace ( '#' , v + '#' ) ;
2012-03-21 04:47:31 +01:00
} ,
// Fix function for IE 9 where regexps isn't working correctly
// Todo: remove me once MS fixes the bug
_replace : function ( find , replace , str ) {
// On IE9 we have to fake $x replacement
if ( isRegExpBroken ) {
return str . replace ( find , function ( ) {
var val = replace , args = arguments , i ;
for ( i = 0 ; i < args . length - 2 ; i ++ ) {
2012-05-16 02:18:46 +02:00
if ( args [ i ] === undef ) {
2012-03-21 04:47:31 +01:00
val = val . replace ( new RegExp ( '\\$' + i , 'g' ) , '' ) ;
} else {
val = val . replace ( new RegExp ( '\\$' + i , 'g' ) , args [ i ] ) ;
}
}
return val ;
} ) ;
}
return str . replace ( find , replace ) ;
2010-07-02 01:48:07 +02:00
}
} ;
// Initialize the API
tinymce . _init ( ) ;
// Expose tinymce namespace to the global namespace (window)
win . tinymce = win . tinyMCE = tinymce ;
2012-03-21 04:47:31 +01:00
// Describe the different namespaces
} ) ( window ) ;
2010-07-02 01:48:07 +02:00
tinymce . create ( 'tinymce.util.Dispatcher' , {
scope : null ,
listeners : null ,
2012-05-16 02:18:46 +02:00
inDispatch : false ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
Dispatcher : function ( scope ) {
this . scope = scope || this ;
2010-07-02 01:48:07 +02:00
this . listeners = [ ] ;
} ,
2012-05-16 02:18:46 +02:00
add : function ( callback , scope ) {
this . listeners . push ( { cb : callback , scope : scope || this . scope } ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
return callback ;
2010-07-02 01:48:07 +02:00
} ,
2012-05-16 02:18:46 +02:00
addToTop : function ( callback , scope ) {
var self = this , listener = { cb : callback , scope : scope || self . scope } ;
// Create new listeners if addToTop is executed in a dispatch loop
if ( self . inDispatch ) {
self . listeners = [ listener ] . concat ( self . listeners ) ;
} else {
self . listeners . unshift ( listener ) ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
return callback ;
2010-07-02 01:48:07 +02:00
} ,
2012-05-16 02:18:46 +02:00
remove : function ( callback ) {
var listeners = this . listeners , output = null ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
tinymce . each ( listeners , function ( listener , i ) {
if ( callback == listener . cb ) {
output = listener ;
listeners . splice ( i , 1 ) ;
2010-07-02 01:48:07 +02:00
return false ;
}
} ) ;
2012-05-16 02:18:46 +02:00
return output ;
2010-07-02 01:48:07 +02:00
} ,
dispatch : function ( ) {
2012-05-16 02:18:46 +02:00
var self = this , returnValue , args = arguments , i , listeners = self . listeners , listener ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
self . inDispatch = true ;
2010-07-02 01:48:07 +02:00
// Needs to be a real loop since the listener count might change while looping
// And this is also more efficient
2012-05-16 02:18:46 +02:00
for ( i = 0 ; i < listeners . length ; i ++ ) {
listener = listeners [ i ] ;
returnValue = listener . cb . apply ( listener . scope , args . length > 0 ? args : [ listener . scope ] ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( returnValue === false )
2010-07-02 01:48:07 +02:00
break ;
}
2012-05-16 02:18:46 +02:00
self . inDispatch = false ;
return returnValue ;
2010-07-02 01:48:07 +02:00
}
} ) ;
( function ( ) {
var each = tinymce . each ;
tinymce . create ( 'tinymce.util.URI' , {
URI : function ( u , s ) {
2012-03-21 04:47:31 +01:00
var t = this , o , a , b , base _url ;
2010-07-02 01:48:07 +02:00
// Trim whitespace
u = tinymce . trim ( u ) ;
// Default settings
s = t . settings = s || { } ;
2012-03-21 04:47:31 +01:00
// Strange app protocol that isn't http/https or local anchor
// For example: mailto,skype,tel etc.
if ( /^([\w\-]+):([^\/]{2})/i . test ( u ) || /^\s*#/ . test ( u ) ) {
2010-07-02 01:48:07 +02:00
t . source = u ;
return ;
}
// Absolute path with no host, fake host and protocol
if ( u . indexOf ( '/' ) === 0 && u . indexOf ( '//' ) !== 0 )
u = ( s . base _uri ? s . base _uri . protocol || 'http' : 'http' ) + '://mce_host' + u ;
// Relative path http:// or protocol relative //path
2012-05-16 02:18:46 +02:00
if ( ! /^[\w\-]*:?\/\// . test ( u ) ) {
2012-03-21 04:47:31 +01:00
base _url = s . base _uri ? s . base _uri . path : new tinymce . util . URI ( location . href ) . directory ;
u = ( ( s . base _uri && s . base _uri . protocol ) || 'http' ) + '://mce_host' + t . toAbsPath ( base _url , u ) ;
}
2010-07-02 01:48:07 +02:00
// Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
u = u . replace ( /@@/g , '(mce_at)' ) ; // Zope 3 workaround, they use @@something
2012-03-21 04:47:31 +01:00
u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ . exec ( u ) ;
2010-07-02 01:48:07 +02:00
each ( [ "source" , "protocol" , "authority" , "userInfo" , "user" , "password" , "host" , "port" , "relative" , "path" , "directory" , "file" , "query" , "anchor" ] , function ( v , i ) {
var s = u [ i ] ;
// Zope 3 workaround, they use @@something
if ( s )
s = s . replace ( /\(mce_at\)/g , '@@' ) ;
t [ v ] = s ;
} ) ;
2012-05-16 02:18:46 +02:00
b = s . base _uri ;
if ( b ) {
2010-07-02 01:48:07 +02:00
if ( ! t . protocol )
t . protocol = b . protocol ;
if ( ! t . userInfo )
t . userInfo = b . userInfo ;
2012-05-16 02:18:46 +02:00
if ( ! t . port && t . host === 'mce_host' )
2010-07-02 01:48:07 +02:00
t . port = b . port ;
2012-05-16 02:18:46 +02:00
if ( ! t . host || t . host === 'mce_host' )
2010-07-02 01:48:07 +02:00
t . host = b . host ;
t . source = '' ;
}
//t.path = t.path || '/';
} ,
setPath : function ( p ) {
var t = this ;
p = /^(.*?)\/?(\w+)?$/ . exec ( p ) ;
// Update path parts
t . path = p [ 0 ] ;
t . directory = p [ 1 ] ;
t . file = p [ 2 ] ;
// Rebuild source
t . source = '' ;
t . getURI ( ) ;
} ,
toRelative : function ( u ) {
var t = this , o ;
if ( u === "./" )
return u ;
u = new tinymce . util . URI ( u , { base _uri : t } ) ;
// Not on same domain/port or protocol
if ( ( u . host != 'mce_host' && t . host != u . host && u . host ) || t . port != u . port || t . protocol != u . protocol )
return u . getURI ( ) ;
2012-05-16 02:18:46 +02:00
var tu = t . getURI ( ) , uu = u . getURI ( ) ;
// Allow usage of the base_uri when relative_urls = true
if ( tu == uu || ( tu . charAt ( tu . length - 1 ) == "/" && tu . substr ( 0 , tu . length - 1 ) == uu ) )
return tu ;
2010-07-02 01:48:07 +02:00
o = t . toRelPath ( t . path , u . path ) ;
// Add query
if ( u . query )
o += '?' + u . query ;
// Add anchor
if ( u . anchor )
o += '#' + u . anchor ;
return o ;
} ,
toAbsolute : function ( u , nh ) {
2012-05-16 02:18:46 +02:00
u = new tinymce . util . URI ( u , { base _uri : this } ) ;
2010-07-02 01:48:07 +02:00
return u . getURI ( this . host == u . host && this . protocol == u . protocol ? nh : 0 ) ;
} ,
toRelPath : function ( base , path ) {
var items , bp = 0 , out = '' , i , l ;
// Split the paths
base = base . substring ( 0 , base . lastIndexOf ( '/' ) ) ;
base = base . split ( '/' ) ;
items = path . split ( '/' ) ;
if ( base . length >= items . length ) {
for ( i = 0 , l = base . length ; i < l ; i ++ ) {
if ( i >= items . length || base [ i ] != items [ i ] ) {
bp = i + 1 ;
break ;
}
}
}
if ( base . length < items . length ) {
for ( i = 0 , l = items . length ; i < l ; i ++ ) {
if ( i >= base . length || base [ i ] != items [ i ] ) {
bp = i + 1 ;
break ;
}
}
}
2012-05-16 02:18:46 +02:00
if ( bp === 1 )
2010-07-02 01:48:07 +02:00
return path ;
for ( i = 0 , l = base . length - ( bp - 1 ) ; i < l ; i ++ )
out += "../" ;
for ( i = bp - 1 , l = items . length ; i < l ; i ++ ) {
if ( i != bp - 1 )
out += "/" + items [ i ] ;
else
out += items [ i ] ;
}
return out ;
} ,
toAbsPath : function ( base , path ) {
var i , nb = 0 , o = [ ] , tr , outPath ;
// Split paths
tr = /\/$/ . test ( path ) ? '/' : '' ;
base = base . split ( '/' ) ;
path = path . split ( '/' ) ;
// Remove empty chunks
each ( base , function ( k ) {
if ( k )
o . push ( k ) ;
} ) ;
base = o ;
// Merge relURLParts chunks
for ( i = path . length - 1 , o = [ ] ; i >= 0 ; i -- ) {
// Ignore empty or .
2012-05-16 02:18:46 +02:00
if ( path [ i ] . length === 0 || path [ i ] === "." )
2010-07-02 01:48:07 +02:00
continue ;
// Is parent
2012-05-16 02:18:46 +02:00
if ( path [ i ] === '..' ) {
2010-07-02 01:48:07 +02:00
nb ++ ;
continue ;
}
// Move up
if ( nb > 0 ) {
nb -- ;
continue ;
}
o . push ( path [ i ] ) ;
}
i = base . length - nb ;
// If /a/b/c or /
if ( i <= 0 )
outPath = o . reverse ( ) . join ( '/' ) ;
else
outPath = base . slice ( 0 , i ) . join ( '/' ) + '/' + o . reverse ( ) . join ( '/' ) ;
// Add front / if it's needed
if ( outPath . indexOf ( '/' ) !== 0 )
outPath = '/' + outPath ;
// Add traling / if it's needed
if ( tr && outPath . lastIndexOf ( '/' ) !== outPath . length - 1 )
outPath += tr ;
return outPath ;
} ,
getURI : function ( nh ) {
var s , t = this ;
// Rebuild source
if ( ! t . source || nh ) {
s = '' ;
if ( ! nh ) {
if ( t . protocol )
s += t . protocol + '://' ;
if ( t . userInfo )
s += t . userInfo + '@' ;
if ( t . host )
s += t . host ;
if ( t . port )
s += ':' + t . port ;
}
if ( t . path )
s += t . path ;
if ( t . query )
s += '?' + t . query ;
if ( t . anchor )
s += '#' + t . anchor ;
t . source = s ;
}
return t . source ;
}
} ) ;
} ) ( ) ;
( function ( ) {
var each = tinymce . each ;
tinymce . create ( 'static tinymce.util.Cookie' , {
getHash : function ( n ) {
var v = this . get ( n ) , h ;
if ( v ) {
each ( v . split ( '&' ) , function ( v ) {
v = v . split ( '=' ) ;
h = h || { } ;
h [ unescape ( v [ 0 ] ) ] = unescape ( v [ 1 ] ) ;
} ) ;
}
return h ;
} ,
setHash : function ( n , v , e , p , d , s ) {
var o = '' ;
each ( v , function ( v , k ) {
o += ( ! o ? '' : '&' ) + escape ( k ) + '=' + escape ( v ) ;
} ) ;
this . set ( n , o , e , p , d , s ) ;
} ,
get : function ( n ) {
var c = document . cookie , e , p = n + "=" , b ;
// Strict mode
if ( ! c )
return ;
b = c . indexOf ( "; " + p ) ;
if ( b == - 1 ) {
b = c . indexOf ( p ) ;
2012-05-16 02:18:46 +02:00
if ( b !== 0 )
2010-07-02 01:48:07 +02:00
return null ;
} else
b += 2 ;
e = c . indexOf ( ";" , b ) ;
if ( e == - 1 )
e = c . length ;
return unescape ( c . substring ( b + p . length , e ) ) ;
} ,
set : function ( n , v , e , p , d , s ) {
document . cookie = n + "=" + escape ( v ) +
( ( e ) ? "; expires=" + e . toGMTString ( ) : "" ) +
( ( p ) ? "; path=" + escape ( p ) : "" ) +
( ( d ) ? "; domain=" + d : "" ) +
( ( s ) ? "; secure" : "" ) ;
} ,
remove : function ( n , p ) {
var d = new Date ( ) ;
d . setTime ( d . getTime ( ) - 1000 ) ;
this . set ( n , '' , d , p , d ) ;
}
} ) ;
} ) ( ) ;
2012-03-21 04:47:31 +01:00
( function ( ) {
function serialize ( o , quote ) {
2012-05-16 02:18:46 +02:00
var i , v , t , name ;
2012-03-21 04:47:31 +01:00
quote = quote || '"' ;
2010-07-02 01:48:07 +02:00
if ( o == null )
return 'null' ;
t = typeof o ;
if ( t == 'string' ) {
v = '\bb\tt\nn\ff\rr\""\'\'\\\\' ;
2012-03-21 04:47:31 +01:00
return quote + o . replace ( /([\u0080-\uFFFF\x00-\x1f\"\'\\])/g , function ( a , b ) {
// Make sure single quotes never get encoded inside double quotes for JSON compatibility
if ( quote === '"' && a === "'" )
return a ;
2010-07-02 01:48:07 +02:00
i = v . indexOf ( b ) ;
if ( i + 1 )
return '\\' + v . charAt ( i + 1 ) ;
a = b . charCodeAt ( ) . toString ( 16 ) ;
return '\\u' + '0000' . substring ( a . length ) + a ;
2012-03-21 04:47:31 +01:00
} ) + quote ;
2010-07-02 01:48:07 +02:00
}
if ( t == 'object' ) {
if ( o . hasOwnProperty && o instanceof Array ) {
for ( i = 0 , v = '[' ; i < o . length ; i ++ )
2012-03-21 04:47:31 +01:00
v += ( i > 0 ? ',' : '' ) + serialize ( o [ i ] , quote ) ;
2010-07-02 01:48:07 +02:00
return v + ']' ;
}
v = '{' ;
2012-05-16 02:18:46 +02:00
for ( name in o ) {
if ( o . hasOwnProperty ( name ) ) {
v += typeof o [ name ] != 'function' ? ( v . length > 1 ? ',' + quote : quote ) + name + quote + ':' + serialize ( o [ name ] , quote ) : '' ;
2012-03-21 04:47:31 +01:00
}
}
2010-07-02 01:48:07 +02:00
return v + '}' ;
}
return '' + o ;
2012-03-21 04:47:31 +01:00
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinymce . util . JSON = {
serialize : serialize ,
parse : function ( s ) {
try {
return eval ( '(' + s + ')' ) ;
} catch ( ex ) {
// Ignore
}
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
} ;
} ) ( ) ;
2010-07-02 01:48:07 +02:00
tinymce . create ( 'static tinymce.util.XHR' , {
send : function ( o ) {
var x , t , w = window , c = 0 ;
2012-05-16 02:18:46 +02:00
function ready ( ) {
if ( ! o . async || x . readyState == 4 || c ++ > 10000 ) {
if ( o . success && c < 10000 && x . status == 200 )
o . success . call ( o . success _scope , '' + x . responseText , x , o ) ;
else if ( o . error )
o . error . call ( o . error _scope , c > 10000 ? 'TIMED_OUT' : 'GENERAL' , x , o ) ;
x = null ;
} else
w . setTimeout ( ready , 10 ) ;
} ;
2010-07-02 01:48:07 +02:00
// Default settings
o . scope = o . scope || this ;
o . success _scope = o . success _scope || o . scope ;
o . error _scope = o . error _scope || o . scope ;
o . async = o . async === false ? false : true ;
o . data = o . data || '' ;
function get ( s ) {
x = 0 ;
try {
x = new ActiveXObject ( s ) ;
} catch ( ex ) {
}
return x ;
} ;
x = w . XMLHttpRequest ? new XMLHttpRequest ( ) : get ( 'Microsoft.XMLHTTP' ) || get ( 'Msxml2.XMLHTTP' ) ;
if ( x ) {
if ( x . overrideMimeType )
x . overrideMimeType ( o . content _type ) ;
x . open ( o . type || ( o . data ? 'POST' : 'GET' ) , o . url , o . async ) ;
if ( o . content _type )
x . setRequestHeader ( 'Content-Type' , o . content _type ) ;
x . setRequestHeader ( 'X-Requested-With' , 'XMLHttpRequest' ) ;
x . send ( o . data ) ;
// Syncronous request
if ( ! o . async )
return ready ( ) ;
// Wait for response, onReadyStateChange can not be used since it leaks memory in IE
t = w . setTimeout ( ready , 10 ) ;
}
}
} ) ;
( function ( ) {
var extend = tinymce . extend , JSON = tinymce . util . JSON , XHR = tinymce . util . XHR ;
tinymce . create ( 'tinymce.util.JSONRequest' , {
JSONRequest : function ( s ) {
this . settings = extend ( {
} , s ) ;
this . count = 0 ;
} ,
send : function ( o ) {
var ecb = o . error , scb = o . success ;
o = extend ( this . settings , o ) ;
o . success = function ( c , x ) {
c = JSON . parse ( c ) ;
if ( typeof ( c ) == 'undefined' ) {
c = {
error : 'JSON Parse error.'
} ;
}
if ( c . error )
ecb . call ( o . error _scope || o . scope , c . error , x ) ;
else
scb . call ( o . success _scope || o . scope , c . result ) ;
} ;
o . error = function ( ty , x ) {
2012-03-21 04:47:31 +01:00
if ( ecb )
ecb . call ( o . error _scope || o . scope , ty , x ) ;
2010-07-02 01:48:07 +02:00
} ;
o . data = JSON . serialize ( {
id : o . id || 'c' + ( this . count ++ ) ,
method : o . method ,
params : o . params
} ) ;
// JSON content type for Ruby on rails. Bug: #1883287
o . content _type = 'application/json' ;
XHR . send ( o ) ;
} ,
'static' : {
sendRPC : function ( o ) {
return new tinymce . util . JSONRequest ( ) . send ( o ) ;
}
}
} ) ;
} ( ) ) ;
2012-03-21 04:47:31 +01:00
( function ( tinymce ) {
tinymce . VK = {
BACKSPACE : 8 ,
DELETE : 46 ,
DOWN : 40 ,
ENTER : 13 ,
LEFT : 37 ,
RIGHT : 39 ,
SPACEBAR : 32 ,
TAB : 9 ,
UP : 38 ,
modifierPressed : function ( e ) {
return e . shiftKey || e . ctrlKey || e . altKey ;
}
2012-05-16 02:18:46 +02:00
} ;
2012-03-21 04:47:31 +01:00
} ) ( tinymce ) ;
2012-05-16 02:18:46 +02:00
tinymce . util . Quirks = function ( editor ) {
var VK = tinymce . VK , BACKSPACE = VK . BACKSPACE , DELETE = VK . DELETE , dom = editor . dom , selection = editor . selection , settings = editor . settings ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
function setEditorCommandState ( cmd , state ) {
2012-03-21 04:47:31 +01:00
try {
editor . getDoc ( ) . execCommand ( cmd , false , state ) ;
} catch ( ex ) {
// Ignore
}
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
function cleanupStylesWhenDeleting ( ) {
function removeMergedFormatSpans ( isDelete ) {
var rng , blockElm , node , clonedSpan ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
rng = selection . getRng ( ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Find root block
blockElm = dom . getParent ( rng . startContainer , dom . isBlock ) ;
// On delete clone the root span of the next block element
if ( isDelete )
blockElm = dom . getNext ( blockElm , dom . isBlock ) ;
// Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace
if ( blockElm ) {
node = blockElm . firstChild ;
// Ignore empty text nodes
while ( node && node . nodeType == 3 && node . nodeValue . length === 0 )
node = node . nextSibling ;
if ( node && node . nodeName === 'SPAN' ) {
clonedSpan = node . cloneNode ( false ) ;
}
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Do the backspace/delete action
editor . getDoc ( ) . execCommand ( isDelete ? 'ForwardDelete' : 'Delete' , false , null ) ;
// Find all odd apple-style-spans
blockElm = dom . getParent ( rng . startContainer , dom . isBlock ) ;
tinymce . each ( dom . select ( 'span.Apple-style-span,font.Apple-style-span' , blockElm ) , function ( span ) {
var bm = selection . getBookmark ( ) ;
if ( clonedSpan ) {
dom . replace ( clonedSpan . cloneNode ( false ) , span , true ) ;
} else {
dom . remove ( span , true ) ;
}
// Restore the selection
selection . moveToBookmark ( bm ) ;
} ) ;
} ;
editor . onKeyDown . add ( function ( editor , e ) {
var isDelete ;
2012-03-21 04:47:31 +01:00
isDelete = e . keyCode == DELETE ;
2012-05-16 02:18:46 +02:00
if ( ! e . isDefaultPrevented ( ) && ( isDelete || e . keyCode == BACKSPACE ) && ! VK . modifierPressed ( e ) ) {
2012-03-21 04:47:31 +01:00
e . preventDefault ( ) ;
2012-05-16 02:18:46 +02:00
removeMergedFormatSpans ( isDelete ) ;
}
} ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
editor . addCommand ( 'Delete' , function ( ) { removeMergedFormatSpans ( ) ; } ) ;
} ;
function emptyEditorWhenDeleting ( ) {
function getEndPointNode ( rng , start ) {
var container , offset , prefix = start ? 'start' : 'end' ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
container = rng [ prefix + 'Container' ] ;
offset = rng [ prefix + 'Offset' ] ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Resolve indexed container
if ( container . nodeType == 1 && container . hasChildNodes ( ) ) {
container = container . childNodes [ Math . min ( start ? offset : ( offset > 0 ? offset - 1 : 0 ) , container . childNodes . length - 1 ) ]
}
2012-03-21 04:47:31 +01:00
2012-05-16 02:18:46 +02:00
return container ;
} ;
2012-03-21 04:47:31 +01:00
2012-05-16 02:18:46 +02:00
function isAtStartEndOfBody ( rng , start ) {
var container , offset , root , childNode , prefix = start ? 'start' : 'end' , isAfter ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
container = rng [ prefix + 'Container' ] ;
offset = rng [ prefix + 'Offset' ] ;
root = dom . getRoot ( ) ;
2012-03-21 04:47:31 +01:00
2012-05-16 02:18:46 +02:00
// Resolve indexed container
if ( container . nodeType == 1 ) {
isAfter = offset >= container . childNodes . length ;
container = getEndPointNode ( rng , start ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( container . nodeType == 3 ) {
offset = start && ! isAfter ? 0 : container . nodeValue . length ;
}
}
2012-03-21 04:47:31 +01:00
2012-05-16 02:18:46 +02:00
// Check if start/end is in the middle of text
if ( container . nodeType == 3 && ( ( start && offset > 0 ) || ( ! start && offset < container . nodeValue . length ) ) ) {
return false ;
2010-07-02 01:48:07 +02:00
}
2012-05-16 02:18:46 +02:00
// Walk up the DOM tree to see if the endpoint is at the beginning/end of body
while ( container !== root ) {
childNode = container . parentNode [ start ? 'firstChild' : 'lastChild' ] ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// If first/last element is a BR then jump to it's sibling in case: <p>x<br></p>
if ( childNode . nodeName == "BR" ) {
childNode = childNode [ start ? 'nextSibling' : 'previousSibling' ] || childNode ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// If the childNode isn't the container node then break in case <p><span>A</span>[X]</p>
if ( childNode !== container ) {
return false ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
container = container . parentNode ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
return true ;
} ;
editor . onKeyDown . addToTop ( function ( editor , e ) {
var rng , keyCode = e . keyCode ;
if ( ! e . isDefaultPrevented ( ) && ( keyCode == DELETE || keyCode == BACKSPACE ) ) {
rng = selection . getRng ( true ) ;
if ( isAtStartEndOfBody ( rng , true ) && isAtStartEndOfBody ( rng , false ) &&
( rng . collapsed || dom . findCommonAncestor ( getEndPointNode ( rng , true ) , getEndPointNode ( rng ) ) === dom . getRoot ( ) ) ) {
editor . setContent ( '' ) ;
editor . nodeChanged ( ) ;
2012-03-21 04:47:31 +01:00
e . preventDefault ( ) ;
}
}
} ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
function inputMethodFocus ( ) {
if ( ! editor . settings . content _editable ) {
// Case 1 IME doesn't initialize if you focus the document
dom . bind ( editor . getDoc ( ) , 'focusin' , function ( e ) {
selection . setRng ( selection . getRng ( ) ) ;
} ) ;
// Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event
dom . bind ( editor . getDoc ( ) , 'mousedown' , function ( e ) {
if ( e . target == editor . getDoc ( ) . documentElement ) {
editor . getWin ( ) . focus ( ) ;
selection . setRng ( selection . getRng ( ) ) ;
}
} ) ;
}
2012-03-21 04:47:31 +01:00
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
function removeHrOnBackspace ( ) {
editor . onKeyDown . add ( function ( editor , e ) {
if ( ! e . isDefaultPrevented ( ) && e . keyCode === BACKSPACE ) {
if ( selection . isCollapsed ( ) && selection . getRng ( true ) . startOffset === 0 ) {
var node = selection . getNode ( ) ;
2012-03-21 04:47:31 +01:00
var previousSibling = node . previousSibling ;
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
if ( previousSibling && previousSibling . nodeName && previousSibling . nodeName . toLowerCase ( ) === "hr" ) {
2012-05-16 02:18:46 +02:00
dom . remove ( previousSibling ) ;
2012-03-21 04:47:31 +01:00
tinymce . dom . Event . cancel ( e ) ;
}
}
}
} )
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
function focusBody ( ) {
2012-03-21 04:47:31 +01:00
// Fix for a focus bug in FF 3.x where the body element
// wouldn't get proper focus if the user clicked on the HTML element
if ( ! Range . prototype . getClientRects ) { // Detect getClientRects got introduced in FF 4
2012-05-16 02:18:46 +02:00
editor . onMouseDown . add ( function ( editor , e ) {
2012-03-21 04:47:31 +01:00
if ( e . target . nodeName === "HTML" ) {
2012-05-16 02:18:46 +02:00
var body = editor . getBody ( ) ;
2012-03-21 04:47:31 +01:00
// Blur the body it's focused but not correctly focused
body . blur ( ) ;
// Refocus the body after a little while
setTimeout ( function ( ) {
body . focus ( ) ;
} , 0 ) ;
}
} ) ;
}
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
function selectControlElements ( ) {
editor . onClick . add ( function ( editor , e ) {
2012-03-21 04:47:31 +01:00
e = e . target ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
// WebKit can't even do simple things like selecting an image
// Needs tobe the setBaseAndExtend or it will fail to select floated images
2012-05-16 02:18:46 +02:00
if ( /^(IMG|HR)$/ . test ( e . nodeName ) ) {
selection . getSel ( ) . setBaseAndExtent ( e , 0 , e , 1 ) ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( e . nodeName == 'A' && dom . hasClass ( e , 'mceItemAnchor' ) ) {
selection . select ( e ) ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
editor . nodeChanged ( ) ;
2012-03-21 04:47:31 +01:00
} ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
function removeStylesWhenDeletingAccrossBlockElements ( ) {
2012-03-21 04:47:31 +01:00
function getAttributeApplyFunction ( ) {
var template = dom . getAttribs ( selection . getStart ( ) . cloneNode ( false ) ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return function ( ) {
var target = selection . getStart ( ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( target !== editor . getBody ( ) ) {
2012-03-21 04:47:31 +01:00
dom . setAttrib ( target , "style" , null ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
tinymce . each ( template , function ( attr ) {
target . setAttributeNode ( attr . cloneNode ( true ) ) ;
} ) ;
2012-03-21 04:47:31 +01:00
}
} ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function isSelectionAcrossElements ( ) {
return ! selection . isCollapsed ( ) && selection . getStart ( ) != selection . getEnd ( ) ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
function blockEvent ( editor , e ) {
2012-03-21 04:47:31 +01:00
e . preventDefault ( ) ;
return false ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
editor . onKeyPress . add ( function ( editor , e ) {
2012-03-21 04:47:31 +01:00
var applyAttributes ;
if ( ( e . keyCode == 8 || e . keyCode == 46 ) && isSelectionAcrossElements ( ) ) {
applyAttributes = getAttributeApplyFunction ( ) ;
2012-05-16 02:18:46 +02:00
editor . getDoc ( ) . execCommand ( 'delete' , false , null ) ;
2012-03-21 04:47:31 +01:00
applyAttributes ( ) ;
e . preventDefault ( ) ;
return false ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
} ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
dom . bind ( editor . getDoc ( ) , 'cut' , function ( e ) {
2012-03-21 04:47:31 +01:00
var applyAttributes ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( isSelectionAcrossElements ( ) ) {
applyAttributes = getAttributeApplyFunction ( ) ;
2012-05-16 02:18:46 +02:00
editor . onKeyUp . addToTop ( blockEvent ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
setTimeout ( function ( ) {
applyAttributes ( ) ;
2012-05-16 02:18:46 +02:00
editor . onKeyUp . remove ( blockEvent ) ;
2012-03-21 04:47:31 +01:00
} , 0 ) ;
}
} ) ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
function selectionChangeNodeChanged ( ) {
2012-03-21 04:47:31 +01:00
var lastRng , selectionTimer ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
dom . bind ( editor . getDoc ( ) , 'selectionchange' , function ( ) {
2012-03-21 04:47:31 +01:00
if ( selectionTimer ) {
clearTimeout ( selectionTimer ) ;
selectionTimer = 0 ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
selectionTimer = window . setTimeout ( function ( ) {
2012-05-16 02:18:46 +02:00
var rng = selection . getRng ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Compare the ranges to see if it was a real change or not
if ( ! lastRng || ! tinymce . dom . RangeUtils . compareRanges ( rng , lastRng ) ) {
2012-05-16 02:18:46 +02:00
editor . nodeChanged ( ) ;
2012-03-21 04:47:31 +01:00
lastRng = rng ;
}
} , 50 ) ;
} ) ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
function ensureBodyHasRoleApplication ( ) {
2012-03-21 04:47:31 +01:00
document . body . setAttribute ( "role" , "application" ) ;
}
2012-05-16 02:18:46 +02:00
function disableBackspaceIntoATable ( ) {
editor . onKeyDown . add ( function ( editor , e ) {
if ( ! e . isDefaultPrevented ( ) && e . keyCode === BACKSPACE ) {
if ( selection . isCollapsed ( ) && selection . getRng ( true ) . startOffset === 0 ) {
var previousSibling = selection . getNode ( ) . previousSibling ;
2012-03-21 04:47:31 +01:00
if ( previousSibling && previousSibling . nodeName && previousSibling . nodeName . toLowerCase ( ) === "table" ) {
return tinymce . dom . Event . cancel ( e ) ;
}
}
}
} )
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
function addNewLinesBeforeBrInPre ( ) {
2012-03-21 04:47:31 +01:00
var documentMode = editor . getDoc ( ) . documentMode ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// IE8+ rendering mode does the right thing with BR in PRE
if ( documentMode && documentMode > 7 ) {
return ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Enable display: none in area and add a specific class that hides all BR elements in PRE to
// avoid the caret from getting stuck at the BR elements while pressing the right arrow key
2012-05-16 02:18:46 +02:00
setEditorCommandState ( 'RespectVisibilityInDesign' , true ) ;
dom . addClass ( editor . getBody ( ) , 'mceHideBrInPre' ) ;
2012-03-21 04:47:31 +01:00
// Adds a \n before all BR elements in PRE to get them visual
editor . parser . addNodeFilter ( 'pre' , function ( nodes , name ) {
var i = nodes . length , brNodes , j , brElm , sibling ;
while ( i -- ) {
brNodes = nodes [ i ] . getAll ( 'br' ) ;
j = brNodes . length ;
while ( j -- ) {
brElm = brNodes [ j ] ;
// Add \n before BR in PRE elements on older IE:s so the new lines get rendered
sibling = brElm . prev ;
if ( sibling && sibling . type === 3 && sibling . value . charAt ( sibling . value - 1 ) != '\n' ) {
sibling . value += '\n' ;
} else {
brElm . parent . insert ( new tinymce . html . Node ( '#text' , 3 ) , brElm , true ) . value = '\n' ;
}
}
}
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible
editor . serializer . addNodeFilter ( 'pre' , function ( nodes , name ) {
var i = nodes . length , brNodes , j , brElm , sibling ;
while ( i -- ) {
brNodes = nodes [ i ] . getAll ( 'br' ) ;
j = brNodes . length ;
while ( j -- ) {
brElm = brNodes [ j ] ;
sibling = brElm . prev ;
if ( sibling && sibling . type == 3 ) {
sibling . value = sibling . value . replace ( /\r?\n$/ , '' ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
}
}
} ) ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
function removePreSerializedStylesWhenSelectingControls ( ) {
dom . bind ( editor . getBody ( ) , 'mouseup' , function ( e ) {
var value , node = selection . getNode ( ) ;
2012-03-21 04:47:31 +01:00
2012-05-16 02:18:46 +02:00
// Moved styles to attributes on IMG eements
if ( node . nodeName == 'IMG' ) {
// Convert style width to width attribute
if ( value = dom . getStyle ( node , 'width' ) ) {
dom . setAttrib ( node , 'width' , value . replace ( /[^0-9%]+/g , '' ) ) ;
dom . setStyle ( node , 'width' , '' ) ;
}
2012-03-21 04:47:31 +01:00
2012-05-16 02:18:46 +02:00
// Convert style height to height attribute
if ( value = dom . getStyle ( node , 'height' ) ) {
dom . setAttrib ( node , 'height' , value . replace ( /[^0-9%]+/g , '' ) ) ;
dom . setStyle ( node , 'height' , '' ) ;
2010-07-02 01:48:07 +02:00
}
}
2012-05-16 02:18:46 +02:00
} ) ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
function keepInlineElementOnDeleteBackspace ( ) {
editor . onKeyDown . add ( function ( editor , e ) {
var isDelete , rng , container , offset , brElm , sibling , collapsed ;
isDelete = e . keyCode == DELETE ;
if ( ! e . isDefaultPrevented ( ) && ( isDelete || e . keyCode == BACKSPACE ) && ! VK . modifierPressed ( e ) ) {
rng = selection . getRng ( ) ;
container = rng . startContainer ;
offset = rng . startOffset ;
collapsed = rng . collapsed ;
// Override delete if the start container is a text node and is at the beginning of text or
// just before/after the last character to be deleted in collapsed mode
if ( container . nodeType == 3 && container . nodeValue . length > 0 && ( ( offset === 0 && ! collapsed ) || ( collapsed && offset === ( isDelete ? 0 : 1 ) ) ) ) {
nonEmptyElements = editor . schema . getNonEmptyElements ( ) ;
// Prevent default logic since it's broken
e . preventDefault ( ) ;
// Insert a BR before the text node this will prevent the containing element from being deleted/converted
brElm = dom . create ( 'br' , { id : '__tmp' } ) ;
container . parentNode . insertBefore ( brElm , container ) ;
// Do the browser delete
editor . getDoc ( ) . execCommand ( isDelete ? 'ForwardDelete' : 'Delete' , false , null ) ;
// Check if the previous sibling is empty after deleting for example: <p><b></b>|</p>
container = selection . getRng ( ) . startContainer ;
sibling = container . previousSibling ;
if ( sibling && sibling . nodeType == 1 && ! dom . isBlock ( sibling ) && dom . isEmpty ( sibling ) && ! nonEmptyElements [ sibling . nodeName . toLowerCase ( ) ] ) {
dom . remove ( sibling ) ;
}
// Remove the temp element we inserted
dom . remove ( '__tmp' ) ;
}
2012-03-21 04:47:31 +01:00
}
2012-05-16 02:18:46 +02:00
} ) ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
function removeBlockQuoteOnBackSpace ( ) {
// Add block quote deletion handler
editor . onKeyDown . add ( function ( editor , e ) {
var rng , container , offset , root , parent ;
if ( e . isDefaultPrevented ( ) || e . keyCode != VK . BACKSPACE ) {
return ;
}
rng = selection . getRng ( ) ;
container = rng . startContainer ;
offset = rng . startOffset ;
root = dom . getRoot ( ) ;
parent = container ;
if ( ! rng . collapsed || offset !== 0 ) {
return ;
}
while ( parent && parent . parentNode && parent . parentNode . firstChild == parent && parent . parentNode != root ) {
parent = parent . parentNode ;
}
// Is the cursor at the beginning of a blockquote?
if ( parent . tagName === 'BLOCKQUOTE' ) {
// Remove the blockquote
editor . formatter . toggle ( 'blockquote' , null , parent ) ;
// Move the caret to the beginning of container
rng . setStart ( container , 0 ) ;
rng . setEnd ( container , 0 ) ;
selection . setRng ( rng ) ;
selection . collapse ( false ) ;
}
} ) ;
} ;
function setGeckoEditingOptions ( ) {
function setOpts ( ) {
editor . _refreshContentEditable ( ) ;
setEditorCommandState ( "StyleWithCSS" , false ) ;
setEditorCommandState ( "enableInlineTableEditing" , false ) ;
if ( ! settings . object _resizing ) {
setEditorCommandState ( "enableObjectResizing" , false ) ;
2012-03-21 04:47:31 +01:00
}
2012-05-16 02:18:46 +02:00
} ;
if ( ! settings . readonly ) {
editor . onBeforeExecCommand . add ( setOpts ) ;
editor . onMouseDown . add ( setOpts ) ;
2012-03-21 04:47:31 +01:00
}
2012-05-16 02:18:46 +02:00
} ;
function addBrAfterLastLinks ( ) {
function fixLinks ( editor , o ) {
tinymce . each ( dom . select ( 'a' ) , function ( node ) {
var parentNode = node . parentNode , root = dom . getRoot ( ) ;
if ( parentNode . lastChild === node ) {
while ( parentNode && ! dom . isBlock ( parentNode ) ) {
if ( parentNode . parentNode . lastChild !== parentNode || parentNode === root ) {
return ;
}
parentNode = parentNode . parentNode ;
}
dom . add ( parentNode , 'br' , { 'data-mce-bogus' : 1 } ) ;
}
} ) ;
} ;
editor . onExecCommand . add ( function ( editor , cmd ) {
if ( cmd === 'CreateLink' ) {
fixLinks ( editor ) ;
}
} ) ;
editor . onSetContent . add ( selection . onSetContent . add ( fixLinks ) ) ;
} ;
function removeGhostSelection ( ) {
function repaint ( sender , args ) {
if ( ! sender || ! args . initial ) {
editor . execCommand ( 'mceRepaint' ) ;
}
} ;
editor . onUndo . add ( repaint ) ;
editor . onRedo . add ( repaint ) ;
editor . onSetContent . add ( repaint ) ;
} ;
function deleteImageOnBackSpace ( ) {
editor . onKeyDown . add ( function ( editor , e ) {
if ( ! e . isDefaultPrevented ( ) && e . keyCode == 8 && selection . getNode ( ) . nodeName == 'IMG' ) {
e . preventDefault ( ) ;
editor . undoManager . beforeChange ( ) ;
dom . remove ( selection . getNode ( ) ) ;
editor . undoManager . add ( ) ;
}
} ) ;
} ;
// All browsers
disableBackspaceIntoATable ( ) ;
removeBlockQuoteOnBackSpace ( ) ;
emptyEditorWhenDeleting ( ) ;
// WebKit
if ( tinymce . isWebKit ) {
keepInlineElementOnDeleteBackspace ( ) ;
cleanupStylesWhenDeleting ( ) ;
inputMethodFocus ( ) ;
selectControlElements ( ) ;
// iOS
if ( tinymce . isIDevice ) {
selectionChangeNodeChanged ( ) ;
}
}
// IE
if ( tinymce . isIE ) {
removeHrOnBackspace ( ) ;
ensureBodyHasRoleApplication ( ) ;
addNewLinesBeforeBrInPre ( ) ;
removePreSerializedStylesWhenSelectingControls ( ) ;
deleteImageOnBackSpace ( ) ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Gecko
if ( tinymce . isGecko ) {
removeHrOnBackspace ( ) ;
focusBody ( ) ;
removeStylesWhenDeletingAccrossBlockElements ( ) ;
setGeckoEditingOptions ( ) ;
addBrAfterLastLinks ( ) ;
removeGhostSelection ( ) ;
}
} ;
2012-03-21 04:47:31 +01:00
( function ( tinymce ) {
var namedEntities , baseEntities , reverseEntities ,
attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g ,
textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g ,
rawCharsRegExp = /[<>&\"\']/g ,
entityRegExp = /&(#x|#)?([\w]+);/g ,
asciiMap = {
128 : "\u20AC" , 130 : "\u201A" , 131 : "\u0192" , 132 : "\u201E" , 133 : "\u2026" , 134 : "\u2020" ,
135 : "\u2021" , 136 : "\u02C6" , 137 : "\u2030" , 138 : "\u0160" , 139 : "\u2039" , 140 : "\u0152" ,
142 : "\u017D" , 145 : "\u2018" , 146 : "\u2019" , 147 : "\u201C" , 148 : "\u201D" , 149 : "\u2022" ,
150 : "\u2013" , 151 : "\u2014" , 152 : "\u02DC" , 153 : "\u2122" , 154 : "\u0161" , 155 : "\u203A" ,
156 : "\u0153" , 158 : "\u017E" , 159 : "\u0178"
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Raw entities
baseEntities = {
'\"' : '"' , // Needs to be escaped since the YUI compressor would otherwise break the code
"'" : ''' ,
'<' : '<' ,
'>' : '>' ,
'&' : '&'
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Reverse lookup table for raw entities
reverseEntities = {
'<' : '<' ,
'>' : '>' ,
'&' : '&' ,
'"' : '"' ,
''' : "'"
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Decodes text by using the browser
function nativeDecode ( text ) {
var elm ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
elm = document . createElement ( "div" ) ;
elm . innerHTML = text ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return elm . textContent || elm . innerText || text ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Build a two way lookup table for the entities
function buildEntitiesLookup ( items , radix ) {
var i , chr , entity , lookup = { } ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( items ) {
items = items . split ( ',' ) ;
radix = radix || 10 ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Build entities lookup table
for ( i = 0 ; i < items . length ; i += 2 ) {
chr = String . fromCharCode ( parseInt ( items [ i ] , radix ) ) ;
// Only add non base entities
if ( ! baseEntities [ chr ] ) {
entity = '&' + items [ i + 1 ] + ';' ;
lookup [ chr ] = entity ;
lookup [ entity ] = chr ;
}
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
return lookup ;
}
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Unpack entities lookup where the numbers are in radix 32 to reduce the size
namedEntities = buildEntitiesLookup (
'50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
'5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
'5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
'5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
'68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
'6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
'6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
'75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
'7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
'7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
'81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
'8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
'8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
'8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
'8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
'80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
2012-05-16 02:18:46 +02:00
'811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro' , 32 ) ;
2012-03-21 04:47:31 +01:00
tinymce . html = tinymce . html || { } ;
tinymce . html . Entities = {
encodeRaw : function ( text , attr ) {
return text . replace ( attr ? attrsCharsRegExp : textCharsRegExp , function ( chr ) {
return baseEntities [ chr ] || chr ;
} ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
encodeAllRaw : function ( text ) {
return ( '' + text ) . replace ( rawCharsRegExp , function ( chr ) {
return baseEntities [ chr ] || chr ;
} ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
encodeNumeric : function ( text , attr ) {
return text . replace ( attr ? attrsCharsRegExp : textCharsRegExp , function ( chr ) {
// Multi byte sequence convert it to a single entity
if ( chr . length > 1 )
return '&#' + ( ( ( chr . charCodeAt ( 0 ) - 0xD800 ) * 0x400 ) + ( chr . charCodeAt ( 1 ) - 0xDC00 ) + 0x10000 ) + ';' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return baseEntities [ chr ] || '&#' + chr . charCodeAt ( 0 ) + ';' ;
} ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
encodeNamed : function ( text , attr , entities ) {
entities = entities || namedEntities ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return text . replace ( attr ? attrsCharsRegExp : textCharsRegExp , function ( chr ) {
return baseEntities [ chr ] || entities [ chr ] || chr ;
2010-07-02 01:48:07 +02:00
} ) ;
} ,
2012-03-21 04:47:31 +01:00
getEncodeFunc : function ( name , entities ) {
var Entities = tinymce . html . Entities ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
entities = buildEntitiesLookup ( entities ) || namedEntities ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function encodeNamedAndNumeric ( text , attr ) {
return text . replace ( attr ? attrsCharsRegExp : textCharsRegExp , function ( chr ) {
return baseEntities [ chr ] || entities [ chr ] || '&#' + chr . charCodeAt ( 0 ) + ';' || chr ;
2010-07-02 01:48:07 +02:00
} ) ;
2012-03-21 04:47:31 +01:00
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function encodeCustomNamed ( text , attr ) {
return Entities . encodeNamed ( text , attr , entities ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Replace + with , to be compatible with previous TinyMCE versions
name = tinymce . makeMap ( name . replace ( /\+/g , ',' ) ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Named and numeric encoder
if ( name . named && name . numeric )
return encodeNamedAndNumeric ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Named encoder
if ( name . named ) {
// Custom names
if ( entities )
return encodeCustomNamed ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return Entities . encodeNamed ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Numeric
if ( name . numeric )
return Entities . encodeNumeric ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Raw encoder
return Entities . encodeRaw ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
decode : function ( text ) {
return text . replace ( entityRegExp , function ( all , numeric , value ) {
if ( numeric ) {
value = parseInt ( value , numeric . length === 2 ? 16 : 10 ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Support upper UTF
if ( value > 0xFFFF ) {
value -= 0x10000 ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return String . fromCharCode ( 0xD800 + ( value >> 10 ) , 0xDC00 + ( value & 0x3FF ) ) ;
} else
return asciiMap [ value ] || String . fromCharCode ( value ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
return reverseEntities [ all ] || namedEntities [ all ] || nativeDecode ( all ) ;
2010-07-02 01:48:07 +02:00
} ) ;
2012-03-21 04:47:31 +01:00
}
} ;
} ) ( tinymce ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinymce . html . Styles = function ( settings , schema ) {
var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi ,
urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi ,
styleRegExp = /\s*([^:]+):\s*([^;]+);?/g ,
trimRightRegExp = /\s+$/ ,
urlColorRegExp = /rgb/ ,
undef , i , encodingLookup = { } , encodingItems ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
settings = settings || { } ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
encodingItems = '\\" \\\' \\; \\: ; : \uFEFF' . split ( ' ' ) ;
for ( i = 0 ; i < encodingItems . length ; i ++ ) {
encodingLookup [ encodingItems [ i ] ] = '\uFEFF' + i ;
encodingLookup [ '\uFEFF' + i ] = encodingItems [ i ] ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function toHex ( match , r , g , b ) {
function hex ( val ) {
val = parseInt ( val ) . toString ( 16 ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return val . length > 1 ? val : '0' + val ; // 0 -> 00
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return '#' + hex ( r ) + hex ( g ) + hex ( b ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return {
toHex : function ( color ) {
return color . replace ( rgbRegExp , toHex ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
parse : function ( css ) {
var styles = { } , matches , name , value , isEncoded , urlConverter = settings . url _converter , urlConverterScope = settings . url _converter _scope || this ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function compress ( prefix , suffix ) {
var top , right , bottom , left ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Get values and check it it needs compressing
top = styles [ prefix + '-top' + suffix ] ;
if ( ! top )
return ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
right = styles [ prefix + '-right' + suffix ] ;
if ( top != right )
return ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
bottom = styles [ prefix + '-bottom' + suffix ] ;
if ( right != bottom )
return ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
left = styles [ prefix + '-left' + suffix ] ;
if ( bottom != left )
return ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Compress
styles [ prefix + suffix ] = left ;
delete styles [ prefix + '-top' + suffix ] ;
delete styles [ prefix + '-right' + suffix ] ;
delete styles [ prefix + '-bottom' + suffix ] ;
delete styles [ prefix + '-left' + suffix ] ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function canCompress ( key ) {
var value = styles [ key ] , i ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! value || value . indexOf ( ' ' ) < 0 )
return ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
value = value . split ( ' ' ) ;
i = value . length ;
while ( i -- ) {
if ( value [ i ] !== value [ 0 ] )
return false ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
styles [ key ] = value [ 0 ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return true ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function compress2 ( target , a , b , c ) {
if ( ! canCompress ( a ) )
return ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! canCompress ( b ) )
return ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! canCompress ( c ) )
return ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Compress
styles [ target ] = styles [ a ] + ' ' + styles [ b ] + ' ' + styles [ c ] ;
delete styles [ a ] ;
delete styles [ b ] ;
delete styles [ c ] ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Encodes the specified string by replacing all \" \' ; : with _<num>
function encode ( str ) {
isEncoded = true ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return encodingLookup [ str ] ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Decodes the specified string by replacing all _<num> with it's original value \" \' etc
// It will also decode the \" \' if keep_slashes is set to fale or omitted
function decode ( str , keep _slashes ) {
if ( isEncoded ) {
str = str . replace ( /\uFEFF[0-9]/g , function ( str ) {
return encodingLookup [ str ] ;
} ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! keep _slashes )
str = str . replace ( /\\([\'\";:])/g , "$1" ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return str ;
2012-05-16 02:18:46 +02:00
} ;
function processUrl ( match , url , url2 , url3 , str , str2 ) {
str = str || str2 ;
if ( str ) {
str = decode ( str ) ;
// Force strings into single quote format
return "'" + str . replace ( /\'/g , "\\'" ) + "'" ;
}
url = decode ( url || url2 || url3 ) ;
// Convert the URL to relative/absolute depending on config
if ( urlConverter )
url = urlConverter . call ( urlConverterScope , url , 'style' ) ;
// Output new URL format
return "url('" + url . replace ( /\'/g , "\\'" ) + "')" ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( css ) {
// Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
css = css . replace ( /\\[\"\';:\uFEFF]/g , encode ) . replace ( /\"[^\"]+\"|\'[^\']+\'/g , function ( str ) {
return str . replace ( /[;:]/g , encode ) ;
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Parse styles
while ( matches = styleRegExp . exec ( css ) ) {
name = matches [ 1 ] . replace ( trimRightRegExp , '' ) . toLowerCase ( ) ;
value = matches [ 2 ] . replace ( trimRightRegExp , '' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( name && value . length > 0 ) {
// Opera will produce 700 instead of bold in their style values
if ( name === 'font-weight' && value === '700' )
value = 'bold' ;
else if ( name === 'color' || name === 'background-color' ) // Lowercase colors like RED
value = value . toLowerCase ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Convert RGB colors to HEX
value = value . replace ( rgbRegExp , toHex ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Convert URLs and force them into url('value') format
2012-05-16 02:18:46 +02:00
value = value . replace ( urlOrStrRegExp , processUrl ) ;
2012-03-21 04:47:31 +01:00
styles [ name ] = isEncoded ? decode ( value , true ) : value ;
}
styleRegExp . lastIndex = matches . index + matches [ 0 ] . length ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
// Compress the styles to reduce it's size for example IE will expand styles
compress ( "border" , "" ) ;
compress ( "border" , "-width" ) ;
compress ( "border" , "-color" ) ;
compress ( "border" , "-style" ) ;
compress ( "padding" , "" ) ;
compress ( "margin" , "" ) ;
compress2 ( 'border' , 'border-width' , 'border-style' , 'border-color' ) ;
// Remove pointless border, IE produces these
if ( styles . border === 'medium none' )
delete styles . border ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
return styles ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
serialize : function ( styles , element _name ) {
var css = '' , name , value ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function serializeStyles ( name ) {
var styleList , i , l , value ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
styleList = schema . styles [ name ] ;
if ( styleList ) {
for ( i = 0 , l = styleList . length ; i < l ; i ++ ) {
name = styleList [ i ] ;
value = styles [ name ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( value !== undef && value . length > 0 )
css += ( css . length > 0 ? ' ' : '' ) + name + ': ' + value + ';' ;
}
}
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Serialize styles according to schema
if ( element _name && schema && schema . styles ) {
// Serialize global styles and element specific styles
serializeStyles ( '*' ) ;
serializeStyles ( element _name ) ;
} else {
// Output the styles in the order they are inside the object
for ( name in styles ) {
value = styles [ name ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( value !== undef && value . length > 0 )
css += ( css . length > 0 ? ' ' : '' ) + name + ': ' + value + ';' ;
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return css ;
}
} ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
( function ( tinymce ) {
var mapCache = { } , makeMap = tinymce . makeMap , each = tinymce . each ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function split ( str , delim ) {
return str . split ( delim || ',' ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function unpack ( lookup , data ) {
var key , elements = { } ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function replace ( value ) {
return value . replace ( /[A-Z]+/g , function ( key ) {
return replace ( lookup [ key ] ) ;
} ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Unpack lookup
for ( key in lookup ) {
if ( lookup . hasOwnProperty ( key ) )
lookup [ key ] = replace ( lookup [ key ] ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Unpack and parse data into object map
replace ( data ) . replace ( /#/g , '#text' ) . replace ( /(\w+)\[([^\]]+)\]\[([^\]]*)\]/g , function ( str , name , attributes , children ) {
attributes = split ( attributes , '|' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
elements [ name ] = {
attributes : makeMap ( attributes ) ,
attributesOrder : attributes ,
children : makeMap ( children , '|' , { '#comment' : { } } )
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return elements ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function getHTML5 ( ) {
var html5 = mapCache . html5 ;
if ( ! html5 ) {
html5 = mapCache . html5 = unpack ( {
A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title' ,
B : '#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|meta|meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video' ,
C : '#|a|abbr|area|address|article|aside|audio|b|bdo|blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|dl|em|embed|fieldset|figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|nav|noscript|ol|object|output|p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video'
} , 'html[A|manifest][body|head]' +
'head[A][base|command|link|meta|noscript|script|style|title]' +
'title[A][#]' +
'base[A|href|target][]' +
'link[A|href|rel|media|type|sizes][]' +
'meta[A|http-equiv|name|content|charset][]' +
'style[A|type|media|scoped][#]' +
'script[A|charset|type|src|defer|async][#]' +
'noscript[A][C]' +
'body[A][C]' +
'section[A][C]' +
'nav[A][C]' +
'article[A][C]' +
'aside[A][C]' +
'h1[A][B]' +
'h2[A][B]' +
'h3[A][B]' +
'h4[A][B]' +
'h5[A][B]' +
'h6[A][B]' +
'hgroup[A][h1|h2|h3|h4|h5|h6]' +
'header[A][C]' +
'footer[A][C]' +
'address[A][C]' +
'p[A][B]' +
'br[A][]' +
'pre[A][B]' +
'dialog[A][dd|dt]' +
'blockquote[A|cite][C]' +
'ol[A|start|reversed][li]' +
'ul[A][li]' +
'li[A|value][C]' +
'dl[A][dd|dt]' +
'dt[A][B]' +
'dd[A][C]' +
'a[A|href|target|ping|rel|media|type][C]' +
'em[A][B]' +
'strong[A][B]' +
'small[A][B]' +
'cite[A][B]' +
'q[A|cite][B]' +
'dfn[A][B]' +
'abbr[A][B]' +
'code[A][B]' +
'var[A][B]' +
'samp[A][B]' +
'kbd[A][B]' +
'sub[A][B]' +
'sup[A][B]' +
'i[A][B]' +
'b[A][B]' +
'mark[A][B]' +
'progress[A|value|max][B]' +
'meter[A|value|min|max|low|high|optimum][B]' +
'time[A|datetime][B]' +
'ruby[A][B|rt|rp]' +
'rt[A][B]' +
'rp[A][B]' +
'bdo[A][B]' +
'span[A][B]' +
'ins[A|cite|datetime][B]' +
'del[A|cite|datetime][B]' +
2012-05-16 02:18:46 +02:00
'figure[A][C|legend|figcaption]' +
'figcaption[A][C]' +
2012-03-21 04:47:31 +01:00
'img[A|alt|src|height|width|usemap|ismap][]' +
'iframe[A|name|src|height|width|sandbox|seamless][]' +
'embed[A|src|height|width|type][]' +
'object[A|data|type|height|width|usemap|name|form|classid][param]' +
'param[A|name|value][]' +
'details[A|open][C|legend]' +
'command[A|type|label|icon|disabled|checked|radiogroup][]' +
'menu[A|type|label][C|li]' +
'legend[A][C|B]' +
'div[A][C]' +
'source[A|src|type|media][]' +
'audio[A|src|autobuffer|autoplay|loop|controls][source]' +
'video[A|src|autobuffer|autoplay|loop|controls|width|height|poster][source]' +
'hr[A][]' +
'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' +
'fieldset[A|disabled|form|name][C|legend]' +
'label[A|form|for][B]' +
'input[A|type|accept|alt|autocomplete|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value][]' +
'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' +
'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' +
'datalist[A][B|option]' +
'optgroup[A|disabled|label][option]' +
'option[A|disabled|selected|label|value][]' +
'textarea[A|autofocus|disabled|form|maxlength|name|placeholder|readonly|required|rows|cols|wrap][]' +
'keygen[A|autofocus|challenge|disabled|form|keytype|name][]' +
'output[A|for|form|name][B]' +
'canvas[A|width|height][]' +
'map[A|name][B|C]' +
'area[A|shape|coords|href|alt|target|media|rel|ping|type][]' +
'mathml[A][]' +
'svg[A][]' +
'table[A|summary][caption|colgroup|thead|tfoot|tbody|tr]' +
'caption[A][C]' +
'colgroup[A|span][col]' +
'col[A|span][]' +
'thead[A][tr]' +
'tfoot[A][tr]' +
'tbody[A][tr]' +
'tr[A][th|td]' +
'th[A|headers|rowspan|colspan|scope][B]' +
'td[A|headers|rowspan|colspan][C]'
) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return html5 ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function getHTML4 ( ) {
var html4 = mapCache . html4 ;
if ( ! html4 ) {
// This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size
html4 = mapCache . html4 = unpack ( {
Z : 'H|K|N|O|P' ,
Y : 'X|form|R|Q' ,
ZG : 'E|span|width|align|char|charoff|valign' ,
X : 'p|T|div|U|W|isindex|fieldset|table' ,
ZF : 'E|align|char|charoff|valign' ,
W : 'pre|hr|blockquote|address|center|noframes' ,
ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height' ,
ZD : '[E][S]' ,
U : 'ul|ol|dl|menu|dir' ,
ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q' ,
T : 'h1|h2|h3|h4|h5|h6' ,
ZB : 'X|S|Q' ,
S : 'R|P' ,
ZA : 'a|G|J|M|O|P' ,
R : 'a|H|K|N|O' ,
Q : 'noscript|P' ,
P : 'ins|del|script' ,
O : 'input|select|textarea|label|button' ,
N : 'M|L' ,
M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym' ,
L : 'sub|sup' ,
K : 'J|I' ,
J : 'tt|i|b|u|s|strike' ,
I : 'big|small|font|basefont' ,
H : 'G|F' ,
G : 'br|span|bdo' ,
F : 'object|applet|img|map|iframe' ,
E : 'A|B|C' ,
D : 'accesskey|tabindex|onfocus|onblur' ,
C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup' ,
B : 'lang|xml:lang|dir' ,
A : 'id|class|style|title'
} , 'script[id|charset|type|language|src|defer|xml:space][]' +
'style[B|id|type|media|title|xml:space][]' +
'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' +
'param[id|name|value|valuetype|type][]' +
'p[E|align][#|S]' +
'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' +
'br[A|clear][]' +
'span[E][#|S]' +
'bdo[A|C|B][#|S]' +
'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' +
'h1[E|align][#|S]' +
'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' +
'map[B|C|A|name][X|form|Q|area]' +
'h2[E|align][#|S]' +
'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' +
'h3[E|align][#|S]' +
'tt[E][#|S]' +
'i[E][#|S]' +
'b[E][#|S]' +
'u[E][#|S]' +
's[E][#|S]' +
'strike[E][#|S]' +
'big[E][#|S]' +
'small[E][#|S]' +
'font[A|B|size|color|face][#|S]' +
'basefont[id|size|color|face][]' +
'em[E][#|S]' +
'strong[E][#|S]' +
'dfn[E][#|S]' +
'code[E][#|S]' +
'q[E|cite][#|S]' +
'samp[E][#|S]' +
'kbd[E][#|S]' +
'var[E][#|S]' +
'cite[E][#|S]' +
'abbr[E][#|S]' +
'acronym[E][#|S]' +
'sub[E][#|S]' +
'sup[E][#|S]' +
'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' +
'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' +
'optgroup[E|disabled|label][option]' +
'option[E|selected|disabled|label|value][]' +
'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' +
'label[E|for|accesskey|onfocus|onblur][#|S]' +
'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' +
'h4[E|align][#|S]' +
'ins[E|cite|datetime][#|Y]' +
'h5[E|align][#|S]' +
'del[E|cite|datetime][#|Y]' +
'h6[E|align][#|S]' +
'div[E|align][#|Y]' +
'ul[E|type|compact][li]' +
'li[E|type|value][#|Y]' +
'ol[E|type|compact|start][li]' +
'dl[E|compact][dt|dd]' +
'dt[E][#|S]' +
'dd[E][#|Y]' +
'menu[E|compact][li]' +
'dir[E|compact][li]' +
'pre[E|width|xml:space][#|ZA]' +
'hr[E|align|noshade|size|width][]' +
'blockquote[E|cite][#|Y]' +
'address[E][#|S|p]' +
'center[E][#|Y]' +
'noframes[E][#|Y]' +
'isindex[A|B|prompt][]' +
'fieldset[E][#|legend|Y]' +
'legend[E|accesskey|align][#|S]' +
'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' +
'caption[E|align][#|S]' +
'col[ZG][]' +
'colgroup[ZG][col]' +
'thead[ZF][tr]' +
'tr[ZF|bgcolor][th|td]' +
'th[E|ZE][#|Y]' +
'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' +
'noscript[E][#|Y]' +
'td[E|ZE][#|Y]' +
'tfoot[ZF][tr]' +
'tbody[ZF][tr]' +
'area[E|D|shape|coords|href|nohref|alt|target][]' +
'base[id|href|target][]' +
'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'
) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return html4 ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinymce . html . Schema = function ( settings ) {
var self = this , elements = { } , children = { } , patternElements = [ ] , validStyles , schemaItems ;
var whiteSpaceElementsMap , selfClosingElementsMap , shortEndedElementsMap , boolAttrMap , blockElementsMap , nonEmptyElementsMap , customElementsMap = { } ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Creates an lookup table map object for the specified option or the default value
function createLookupTable ( option , default _value , extend ) {
var value = settings [ option ] ;
if ( ! value ) {
// Get cached default map or make it if needed
value = mapCache [ option ] ;
if ( ! value ) {
value = makeMap ( default _value , ' ' , makeMap ( default _value . toUpperCase ( ) , ' ' ) ) ;
value = tinymce . extend ( value , extend ) ;
mapCache [ option ] = value ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
} else {
// Create custom map
value = makeMap ( value , ',' , makeMap ( value . toUpperCase ( ) , ' ' ) ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
return value ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
settings = settings || { } ;
schemaItems = settings . schema == "html5" ? getHTML5 ( ) : getHTML4 ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Allow all elements and attributes if verify_html is set to false
if ( settings . verify _html === false )
settings . valid _elements = '*[*]' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Build styles list
if ( settings . valid _styles ) {
validStyles = { } ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Convert styles into a rule list
each ( settings . valid _styles , function ( value , key ) {
validStyles [ key ] = tinymce . explode ( value ) ;
} ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Setup map objects
whiteSpaceElementsMap = createLookupTable ( 'whitespace_elements' , 'pre script style textarea' ) ;
selfClosingElementsMap = createLookupTable ( 'self_closing_elements' , 'colgroup dd dt li options p td tfoot th thead tr' ) ;
shortEndedElementsMap = createLookupTable ( 'short_ended_elements' , 'area base basefont br col frame hr img input isindex link meta param embed source' ) ;
boolAttrMap = createLookupTable ( 'boolean_attributes' , 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls' ) ;
nonEmptyElementsMap = createLookupTable ( 'non_empty_elements' , 'td th iframe video audio object' , shortEndedElementsMap ) ;
blockElementsMap = createLookupTable ( 'block_elements' , 'h1 h2 h3 h4 h5 h6 hr p div address pre form table tbody thead tfoot ' +
'th tr td li ol ul caption blockquote center dl dt dd dir fieldset ' +
2012-05-16 02:18:46 +02:00
'noscript menu isindex samp header footer article section hgroup aside nav figure' ) ;
2012-03-21 04:47:31 +01:00
// Converts a wildcard expression string to a regexp for example *a will become /.*a/.
function patternToRegExp ( str ) {
return new RegExp ( '^' + str . replace ( /([?+*])/g , '.$1' ) + '$' ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Parses the specified valid_elements string and adds to the current rules
// This function is a bit hard to read since it's heavily optimized for speed
function addValidElements ( valid _elements ) {
var ei , el , ai , al , yl , matches , element , attr , attrData , elementName , attrName , attrType , attributes , attributesOrder ,
prefix , outputName , globalAttributes , globalAttributesOrder , transElement , key , childKey , value ,
2012-05-16 02:18:46 +02:00
elementRuleRegExp = /^([#+\-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/ ,
2012-03-21 04:47:31 +01:00
attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/ ,
hasPatternsRegExp = /[*?+]/ ;
if ( valid _elements ) {
// Split valid elements into an array with rules
valid _elements = split ( valid _elements ) ;
if ( elements [ '@' ] ) {
globalAttributes = elements [ '@' ] . attributes ;
globalAttributesOrder = elements [ '@' ] . attributesOrder ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Loop all rules
for ( ei = 0 , el = valid _elements . length ; ei < el ; ei ++ ) {
// Parse element rule
matches = elementRuleRegExp . exec ( valid _elements [ ei ] ) ;
if ( matches ) {
// Setup local names for matches
prefix = matches [ 1 ] ;
elementName = matches [ 2 ] ;
outputName = matches [ 3 ] ;
attrData = matches [ 4 ] ;
// Create new attributes and attributesOrder
attributes = { } ;
attributesOrder = [ ] ;
// Create the new element
element = {
attributes : attributes ,
attributesOrder : attributesOrder
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Padd empty elements prefix
if ( prefix === '#' )
element . paddEmpty = true ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Remove empty elements prefix
if ( prefix === '-' )
element . removeEmpty = true ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Copy attributes from global rule into current rule
if ( globalAttributes ) {
for ( key in globalAttributes )
attributes [ key ] = globalAttributes [ key ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
attributesOrder . push . apply ( attributesOrder , globalAttributesOrder ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Attributes defined
if ( attrData ) {
attrData = split ( attrData , '|' ) ;
for ( ai = 0 , al = attrData . length ; ai < al ; ai ++ ) {
matches = attrRuleRegExp . exec ( attrData [ ai ] ) ;
if ( matches ) {
attr = { } ;
attrType = matches [ 1 ] ;
attrName = matches [ 2 ] . replace ( /::/g , ':' ) ;
prefix = matches [ 3 ] ;
value = matches [ 4 ] ;
// Required
if ( attrType === '!' ) {
element . attributesRequired = element . attributesRequired || [ ] ;
element . attributesRequired . push ( attrName ) ;
attr . required = true ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Denied from global
if ( attrType === '-' ) {
delete attributes [ attrName ] ;
attributesOrder . splice ( tinymce . inArray ( attributesOrder , attrName ) , 1 ) ;
continue ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Default value
if ( prefix ) {
// Default value
if ( prefix === '=' ) {
element . attributesDefault = element . attributesDefault || [ ] ;
element . attributesDefault . push ( { name : attrName , value : value } ) ;
attr . defaultValue = value ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Forced value
if ( prefix === ':' ) {
element . attributesForced = element . attributesForced || [ ] ;
element . attributesForced . push ( { name : attrName , value : value } ) ;
attr . forcedValue = value ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Required values
if ( prefix === '<' )
attr . validValues = makeMap ( value , '?' ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Check for attribute patterns
if ( hasPatternsRegExp . test ( attrName ) ) {
element . attributePatterns = element . attributePatterns || [ ] ;
attr . pattern = patternToRegExp ( attrName ) ;
element . attributePatterns . push ( attr ) ;
} else {
// Add attribute to order list if it doesn't already exist
if ( ! attributes [ attrName ] )
attributesOrder . push ( attrName ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
attributes [ attrName ] = attr ;
}
}
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Global rule, store away these for later usage
if ( ! globalAttributes && elementName == '@' ) {
globalAttributes = attributes ;
globalAttributesOrder = attributesOrder ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Handle substitute elements such as b/strong
if ( outputName ) {
element . outputName = elementName ;
elements [ outputName ] = element ;
}
// Add pattern or exact element
if ( hasPatternsRegExp . test ( elementName ) ) {
element . pattern = patternToRegExp ( elementName ) ;
patternElements . push ( element ) ;
} else
elements [ elementName ] = element ;
}
}
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function setValidElements ( valid _elements ) {
elements = { } ;
patternElements = [ ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
addValidElements ( valid _elements ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
each ( schemaItems , function ( element , name ) {
children [ name ] = element . children ;
} ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Adds custom non HTML elements to the schema
function addCustomElements ( custom _elements ) {
var customElementRegExp = /^(~)?(.+)$/ ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( custom _elements ) {
each ( split ( custom _elements ) , function ( rule ) {
var matches = customElementRegExp . exec ( rule ) ,
inline = matches [ 1 ] === '~' ,
cloneName = inline ? 'span' : 'div' ,
name = matches [ 2 ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
children [ name ] = children [ cloneName ] ;
customElementsMap [ name ] = cloneName ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// If it's not marked as inline then add it to valid block elements
if ( ! inline )
blockElementsMap [ name ] = { } ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Add custom elements at span/div positions
each ( children , function ( element , child ) {
if ( element [ cloneName ] )
element [ name ] = element [ cloneName ] ;
} ) ;
2010-07-02 01:48:07 +02:00
} ) ;
2012-03-21 04:47:31 +01:00
}
} ;
// Adds valid children to the schema object
function addValidChildren ( valid _children ) {
var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/ ;
if ( valid _children ) {
each ( split ( valid _children ) , function ( rule ) {
var matches = childRuleRegExp . exec ( rule ) , parent , prefix ;
if ( matches ) {
prefix = matches [ 1 ] ;
// Add/remove items from default
if ( prefix )
parent = children [ matches [ 2 ] ] ;
else
parent = children [ matches [ 2 ] ] = { '#comment' : { } } ;
parent = children [ matches [ 2 ] ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
each ( split ( matches [ 3 ] , '|' ) , function ( child ) {
if ( prefix === '-' )
delete parent [ child ] ;
else
parent [ child ] = { } ;
} ) ;
}
2010-07-02 01:48:07 +02:00
} ) ;
2012-03-21 04:47:31 +01:00
}
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function getElementRule ( name ) {
var element = elements [ name ] , i ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Exact match found
if ( element )
return element ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// No exact match then try the patterns
i = patternElements . length ;
while ( i -- ) {
element = patternElements [ i ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( element . pattern . test ( name ) )
return element ;
}
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! settings . valid _elements ) {
// No valid elements defined then clone the elements from the schema spec
each ( schemaItems , function ( element , name ) {
elements [ name ] = {
attributes : element . attributes ,
attributesOrder : element . attributesOrder
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
children [ name ] = element . children ;
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Switch these on HTML4
if ( settings . schema != "html5" ) {
each ( split ( 'strong/b,em/i' ) , function ( item ) {
item = split ( item , '/' ) ;
elements [ item [ 1 ] ] . outputName = item [ 0 ] ;
} ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Add default alt attribute for images
elements . img . attributesDefault = [ { name : 'alt' , value : '' } ] ;
// Remove these if they are empty by default
each ( split ( 'ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr,strong,em,b,i' ) , function ( name ) {
if ( elements [ name ] ) {
elements [ name ] . removeEmpty = true ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Padd these by default
each ( split ( 'p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption' ) , function ( name ) {
elements [ name ] . paddEmpty = true ;
2010-07-02 01:48:07 +02:00
} ) ;
2012-03-21 04:47:31 +01:00
} else
setValidElements ( settings . valid _elements ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
addCustomElements ( settings . custom _elements ) ;
addValidChildren ( settings . valid _children ) ;
addValidElements ( settings . extended _valid _elements ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Todo: Remove this when we fix list handling to be valid
addValidChildren ( '+ol[ul|ol],+ul[ul|ol]' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Delete invalid elements
if ( settings . invalid _elements ) {
tinymce . each ( tinymce . explode ( settings . invalid _elements ) , function ( item ) {
if ( elements [ item ] )
delete elements [ item ] ;
} ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// If the user didn't allow span only allow internal spans
if ( ! getElementRule ( 'span' ) )
addValidElements ( 'span[!data-mce-type|*]' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . children = children ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . styles = validStyles ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . getBoolAttrs = function ( ) {
return boolAttrMap ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . getBlockElements = function ( ) {
return blockElementsMap ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . getShortEndedElements = function ( ) {
return shortEndedElementsMap ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . getSelfClosingElements = function ( ) {
return selfClosingElementsMap ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . getNonEmptyElements = function ( ) {
return nonEmptyElementsMap ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . getWhiteSpaceElements = function ( ) {
return whiteSpaceElementsMap ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . isValidChild = function ( name , child ) {
var parent = children [ name ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return ! ! ( parent && parent [ child ] ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . getElementRule = getElementRule ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . getCustomElements = function ( ) {
return customElementsMap ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . addValidElements = addValidElements ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . setValidElements = setValidElements ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . addCustomElements = addCustomElements ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . addValidChildren = addValidChildren ;
} ;
} ) ( tinymce ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
( function ( tinymce ) {
tinymce . html . SaxParser = function ( settings , schema ) {
var self = this , noop = function ( ) { } ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
settings = settings || { } ;
self . schema = schema = schema || new tinymce . html . Schema ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( settings . fix _self _closing !== false )
settings . fix _self _closing = true ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Add handler functions from settings and setup default handlers
tinymce . each ( 'comment cdata text start end pi doctype' . split ( ' ' ) , function ( name ) {
if ( name )
self [ name ] = settings [ name ] || noop ;
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . parse = function ( html ) {
var self = this , matches , index = 0 , value , endRegExp , stack = [ ] , attrList , i , text , name , isInternalElement , removeInternalElements ,
shortEndedElements , fillAttrsMap , isShortEnded , validate , elementRule , isValidElement , attr , attribsValue , invalidPrefixRegExp ,
validAttributesMap , validAttributePatterns , attributesRequired , attributesDefault , attributesForced , selfClosing ,
tokenRegExp , attrRegExp , specialElements , attrValue , idCount = 0 , decode = tinymce . html . Entities . decode , fixSelfClosing , isIE ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function processEndTag ( name ) {
var pos , i ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Find position of parent of the same type
pos = stack . length ;
while ( pos -- ) {
if ( stack [ pos ] . name === name )
break ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Found parent
if ( pos >= 0 ) {
// Close all the open elements
for ( i = stack . length - 1 ; i >= pos ; i -- ) {
name = stack [ i ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( name . valid )
self . end ( name . name ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Remove the open elements from the stack
stack . length = pos ;
}
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
function parseAttribute ( match , name , value , val2 , val3 ) {
var attrRule , i ;
name = name . toLowerCase ( ) ;
value = name in fillAttrsMap ? name : decode ( value || val2 || val3 || '' ) ; // Handle boolean attribute than value attribute
// Validate name and value
if ( validate && ! isInternalElement && name . indexOf ( 'data-' ) !== 0 ) {
attrRule = validAttributesMap [ name ] ;
// Find rule by pattern matching
if ( ! attrRule && validAttributePatterns ) {
i = validAttributePatterns . length ;
while ( i -- ) {
attrRule = validAttributePatterns [ i ] ;
if ( attrRule . pattern . test ( name ) )
break ;
}
// No rule matched
if ( i === - 1 )
attrRule = null ;
}
// No attribute rule found
if ( ! attrRule )
return ;
// Validate value
if ( attrRule . validValues && ! ( value in attrRule . validValues ) )
return ;
}
// Add attribute to list and map
attrList . map [ name ] = value ;
attrList . push ( {
name : name ,
value : value
} ) ;
} ;
2012-03-21 04:47:31 +01:00
// Precompile RegExps and map objects
tokenRegExp = new RegExp ( '<(?:' +
'(?:!--([\\w\\W]*?)-->)|' + // Comment
'(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
'(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
'(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
'(?:\\/([^>]+)>)|' + // End element
2012-05-16 02:18:46 +02:00
'(?:([A-Za-z0-9\\-\\:]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
2012-03-21 04:47:31 +01:00
')' , 'g' ) ;
attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g ;
specialElements = {
'script' : /<\/script[^>]*>/gi ,
'style' : /<\/style[^>]*>/gi ,
'noscript' : /<\/noscript[^>]*>/gi
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Setup lookup tables for empty elements and boolean attributes
shortEndedElements = schema . getShortEndedElements ( ) ;
selfClosing = schema . getSelfClosingElements ( ) ;
fillAttrsMap = schema . getBoolAttrs ( ) ;
validate = settings . validate ;
removeInternalElements = settings . remove _internals ;
fixSelfClosing = settings . fix _self _closing ;
isIE = tinymce . isIE ;
invalidPrefixRegExp = /^:/ ;
while ( matches = tokenRegExp . exec ( html ) ) {
// Text
if ( index < matches . index )
self . text ( decode ( html . substr ( index , matches . index - index ) ) ) ;
if ( value = matches [ 6 ] ) { // End element
value = value . toLowerCase ( ) ;
// IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
if ( isIE && invalidPrefixRegExp . test ( value ) )
value = value . substr ( 1 ) ;
processEndTag ( value ) ;
} else if ( value = matches [ 7 ] ) { // Start element
value = value . toLowerCase ( ) ;
// IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
if ( isIE && invalidPrefixRegExp . test ( value ) )
value = value . substr ( 1 ) ;
isShortEnded = value in shortEndedElements ;
// Is self closing tag for example an <li> after an open <li>
if ( fixSelfClosing && selfClosing [ value ] && stack . length > 0 && stack [ stack . length - 1 ] . name === value )
processEndTag ( value ) ;
// Validate element
if ( ! validate || ( elementRule = schema . getElementRule ( value ) ) ) {
isValidElement = true ;
// Grab attributes map and patters when validation is enabled
if ( validate ) {
validAttributesMap = elementRule . attributes ;
validAttributePatterns = elementRule . attributePatterns ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Parse attributes
if ( attribsValue = matches [ 8 ] ) {
isInternalElement = attribsValue . indexOf ( 'data-mce-type' ) !== - 1 ; // Check if the element is an internal element
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// If the element has internal attributes then remove it if we are told to do so
if ( isInternalElement && removeInternalElements )
isValidElement = false ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
attrList = [ ] ;
attrList . map = { } ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
attribsValue . replace ( attrRegExp , parseAttribute ) ;
} else {
attrList = [ ] ;
attrList . map = { } ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Process attributes if validation is enabled
if ( validate && ! isInternalElement ) {
attributesRequired = elementRule . attributesRequired ;
attributesDefault = elementRule . attributesDefault ;
attributesForced = elementRule . attributesForced ;
2012-03-21 04:47:31 +01:00
// Handle forced attributes
if ( attributesForced ) {
i = attributesForced . length ;
while ( i -- ) {
attr = attributesForced [ i ] ;
name = attr . name ;
attrValue = attr . value ;
if ( attrValue === '{$uid}' )
attrValue = 'mce_' + idCount ++ ;
attrList . map [ name ] = attrValue ;
attrList . push ( { name : name , value : attrValue } ) ;
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Handle default attributes
if ( attributesDefault ) {
i = attributesDefault . length ;
while ( i -- ) {
attr = attributesDefault [ i ] ;
name = attr . name ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! ( name in attrList . map ) ) {
attrValue = attr . value ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( attrValue === '{$uid}' )
attrValue = 'mce_' + idCount ++ ;
attrList . map [ name ] = attrValue ;
attrList . push ( { name : name , value : attrValue } ) ;
}
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Handle required attributes
if ( attributesRequired ) {
i = attributesRequired . length ;
while ( i -- ) {
if ( attributesRequired [ i ] in attrList . map )
break ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// None of the required attributes where found
if ( i === - 1 )
isValidElement = false ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Invalidate element if it's marked as bogus
if ( attrList . map [ 'data-mce-bogus' ] )
isValidElement = false ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( isValidElement )
self . start ( value , attrList , isShortEnded ) ;
} else
isValidElement = false ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Treat script, noscript and style a bit different since they may include code that looks like elements
if ( endRegExp = specialElements [ value ] ) {
endRegExp . lastIndex = index = matches . index + matches [ 0 ] . length ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( matches = endRegExp . exec ( html ) ) {
if ( isValidElement )
text = html . substr ( index , matches . index - index ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
index = matches . index + matches [ 0 ] . length ;
} else {
text = html . substr ( index ) ;
index = html . length ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
if ( isValidElement && text . length > 0 )
self . text ( text , true ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( isValidElement )
self . end ( value ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tokenRegExp . lastIndex = index ;
continue ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Push value on to stack
if ( ! isShortEnded ) {
if ( ! attribsValue || attribsValue . indexOf ( '/' ) != attribsValue . length - 1 )
stack . push ( { name : value , valid : isValidElement } ) ;
else if ( isValidElement )
self . end ( value ) ;
}
} else if ( value = matches [ 1 ] ) { // Comment
self . comment ( value ) ;
} else if ( value = matches [ 2 ] ) { // CDATA
self . cdata ( value ) ;
} else if ( value = matches [ 3 ] ) { // DOCTYPE
self . doctype ( value ) ;
} else if ( value = matches [ 4 ] ) { // PI
self . pi ( value , matches [ 5 ] ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
index = matches . index + matches [ 0 ] . length ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Text
if ( index < html . length )
self . text ( decode ( html . substr ( index ) ) ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Close any open elements
for ( i = stack . length - 1 ; i >= 0 ; i -- ) {
value = stack [ i ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( value . valid )
self . end ( value . name ) ;
}
} ;
}
} ) ( tinymce ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
( function ( tinymce ) {
var whiteSpaceRegExp = /^[ \t\r\n]*$/ , typeLookup = {
'#text' : 3 ,
'#comment' : 8 ,
'#cdata' : 4 ,
'#pi' : 7 ,
'#doctype' : 10 ,
'#document-fragment' : 11
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Walks the tree left/right
function walk ( node , root _node , prev ) {
var sibling , parent , startName = prev ? 'lastChild' : 'firstChild' , siblingName = prev ? 'prev' : 'next' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Walk into nodes if it has a start
if ( node [ startName ] )
return node [ startName ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Return the sibling if it has one
if ( node !== root _node ) {
sibling = node [ siblingName ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( sibling )
return sibling ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Walk up the parents to look for siblings
for ( parent = node . parent ; parent && parent !== root _node ; parent = parent . parent ) {
sibling = parent [ siblingName ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( sibling )
return sibling ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
}
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function Node ( name , type ) {
this . name = name ;
this . type = type ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( type === 1 ) {
this . attributes = [ ] ;
this . attributes . map = { } ;
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinymce . extend ( Node . prototype , {
replace : function ( node ) {
var self = this ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( node . parent )
node . remove ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . insert ( node , self ) ;
self . remove ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return self ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
attr : function ( name , value ) {
var self = this , attrs , i , undef ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( typeof name !== "string" ) {
for ( i in name )
self . attr ( i , name [ i ] ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return self ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( attrs = self . attributes ) {
if ( value !== undef ) {
// Remove attribute
if ( value === null ) {
if ( name in attrs . map ) {
delete attrs . map [ name ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
i = attrs . length ;
while ( i -- ) {
if ( attrs [ i ] . name === name ) {
attrs = attrs . splice ( i , 1 ) ;
return self ;
}
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return self ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Set attribute
if ( name in attrs . map ) {
// Set attribute
i = attrs . length ;
while ( i -- ) {
if ( attrs [ i ] . name === name ) {
attrs [ i ] . value = value ;
break ;
}
2010-07-02 01:48:07 +02:00
}
} else
2012-03-21 04:47:31 +01:00
attrs . push ( { name : name , value : value } ) ;
attrs . map [ name ] = value ;
return self ;
} else {
return attrs . map [ name ] ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
clone : function ( ) {
var self = this , clone = new Node ( self . name , self . type ) , i , l , selfAttrs , selfAttr , cloneAttrs ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Clone element attributes
if ( selfAttrs = self . attributes ) {
cloneAttrs = [ ] ;
cloneAttrs . map = { } ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
for ( i = 0 , l = selfAttrs . length ; i < l ; i ++ ) {
selfAttr = selfAttrs [ i ] ;
// Clone everything except id
if ( selfAttr . name !== 'id' ) {
cloneAttrs [ cloneAttrs . length ] = { name : selfAttr . name , value : selfAttr . value } ;
cloneAttrs . map [ selfAttr . name ] = selfAttr . value ;
}
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
clone . attributes = cloneAttrs ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
clone . value = self . value ;
clone . shortEnded = self . shortEnded ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return clone ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
wrap : function ( wrapper ) {
var self = this ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . parent . insert ( wrapper , self ) ;
wrapper . append ( self ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return self ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
unwrap : function ( ) {
var self = this , node , next ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
for ( node = self . firstChild ; node ; ) {
next = node . next ;
self . insert ( node , self , true ) ;
node = next ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . remove ( ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
remove : function ( ) {
var self = this , parent = self . parent , next = self . next , prev = self . prev ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( parent ) {
if ( parent . firstChild === self ) {
parent . firstChild = next ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( next )
next . prev = null ;
} else {
prev . next = next ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
if ( parent . lastChild === self ) {
parent . lastChild = prev ;
if ( prev )
prev . next = null ;
} else {
next . prev = prev ;
}
self . parent = self . next = self . prev = null ;
}
return self ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
append : function ( node ) {
var self = this , last ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( node . parent )
node . remove ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
last = self . lastChild ;
if ( last ) {
last . next = node ;
node . prev = last ;
self . lastChild = node ;
} else
self . lastChild = self . firstChild = node ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
node . parent = self ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return node ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
insert : function ( node , ref _node , before ) {
var parent ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( node . parent )
node . remove ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
parent = ref _node . parent || this ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( before ) {
if ( ref _node === parent . firstChild )
parent . firstChild = node ;
else
ref _node . prev . next = node ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
node . prev = ref _node . prev ;
node . next = ref _node ;
ref _node . prev = node ;
} else {
if ( ref _node === parent . lastChild )
parent . lastChild = node ;
else
ref _node . next . prev = node ;
node . next = ref _node . next ;
node . prev = ref _node ;
ref _node . next = node ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
node . parent = parent ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return node ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
getAll : function ( name ) {
var self = this , node , collection = [ ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
for ( node = self . firstChild ; node ; node = walk ( node , self ) ) {
if ( node . name === name )
collection . push ( node ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return collection ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
empty : function ( ) {
var self = this , nodes , i , node ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Remove all children
if ( self . firstChild ) {
nodes = [ ] ;
// Collect the children
for ( node = self . firstChild ; node ; node = walk ( node , self ) )
nodes . push ( node ) ;
// Remove the children
i = nodes . length ;
while ( i -- ) {
node = nodes [ i ] ;
node . parent = node . firstChild = node . lastChild = node . next = node . prev = null ;
}
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
self . firstChild = self . lastChild = null ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return self ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
isEmpty : function ( elements ) {
var self = this , node = self . firstChild , i , name ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( node ) {
do {
if ( node . type === 1 ) {
// Ignore bogus elements
if ( node . attributes . map [ 'data-mce-bogus' ] )
continue ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Keep empty elements like <img />
if ( elements [ node . name ] )
return false ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Keep elements with data attributes or name attribute like <a name="1"></a>
i = node . attributes . length ;
while ( i -- ) {
name = node . attributes [ i ] . name ;
if ( name === "name" || name . indexOf ( 'data-' ) === 0 )
return false ;
}
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
// Keep comments
if ( node . type === 8 )
return false ;
// Keep non whitespace text nodes
if ( ( node . type === 3 && ! whiteSpaceRegExp . test ( node . value ) ) )
return false ;
} while ( node = walk ( node , self ) ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
return true ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
walk : function ( prev ) {
return walk ( this , null , prev ) ;
}
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinymce . extend ( Node , {
create : function ( name , attrs ) {
var node , attrName ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Create node
node = new Node ( name , typeLookup [ name ] || 1 ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Add attributes if needed
if ( attrs ) {
for ( attrName in attrs )
node . attr ( attrName , attrs [ attrName ] ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return node ;
}
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinymce . html . Node = Node ;
} ) ( tinymce ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
( function ( tinymce ) {
var Node = tinymce . html . Node ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinymce . html . DomParser = function ( settings , schema ) {
var self = this , nodeFilters = { } , attributeFilters = [ ] , matchedNodes = { } , matchedAttributes = { } ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
settings = settings || { } ;
settings . validate = "validate" in settings ? settings . validate : true ;
settings . root _name = settings . root _name || 'body' ;
self . schema = schema = schema || new tinymce . html . Schema ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function fixInvalidChildren ( nodes ) {
var ni , node , parent , parents , newParent , currentNode , tempNode , childNode , i ,
childClone , nonEmptyElements , nonSplitableElements , sibling , nextNode ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
nonSplitableElements = tinymce . makeMap ( 'tr,td,th,tbody,thead,tfoot,table' ) ;
nonEmptyElements = schema . getNonEmptyElements ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
for ( ni = 0 ; ni < nodes . length ; ni ++ ) {
node = nodes [ ni ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Already removed
if ( ! node . parent )
continue ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Get list of all parent nodes until we find a valid parent to stick the child into
parents = [ node ] ;
for ( parent = node . parent ; parent && ! schema . isValidChild ( parent . name , node . name ) && ! nonSplitableElements [ parent . name ] ; parent = parent . parent )
parents . push ( parent ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Found a suitable parent
if ( parent && parents . length > 1 ) {
// Reverse the array since it makes looping easier
parents . reverse ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Clone the related parent and insert that after the moved node
newParent = currentNode = self . filterNode ( parents [ 0 ] . clone ( ) ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Start cloning and moving children on the left side of the target node
for ( i = 0 ; i < parents . length - 1 ; i ++ ) {
if ( schema . isValidChild ( currentNode . name , parents [ i ] . name ) ) {
tempNode = self . filterNode ( parents [ i ] . clone ( ) ) ;
currentNode . append ( tempNode ) ;
} else
tempNode = currentNode ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
for ( childNode = parents [ i ] . firstChild ; childNode && childNode != parents [ i + 1 ] ; ) {
nextNode = childNode . next ;
tempNode . append ( childNode ) ;
childNode = nextNode ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
currentNode = tempNode ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! newParent . isEmpty ( nonEmptyElements ) ) {
parent . insert ( newParent , parents [ 0 ] , true ) ;
parent . insert ( node , newParent ) ;
} else {
parent . insert ( node , parents [ 0 ] , true ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
parent = parents [ 0 ] ;
if ( parent . isEmpty ( nonEmptyElements ) || parent . firstChild === parent . lastChild && parent . firstChild . name === 'br' ) {
parent . empty ( ) . remove ( ) ;
}
} else if ( node . parent ) {
// If it's an LI try to find a UL/OL for it or wrap it
if ( node . name === 'li' ) {
sibling = node . prev ;
if ( sibling && ( sibling . name === 'ul' || sibling . name === 'ul' ) ) {
sibling . append ( node ) ;
continue ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
sibling = node . next ;
if ( sibling && ( sibling . name === 'ul' || sibling . name === 'ul' ) ) {
sibling . insert ( node , sibling . firstChild , true ) ;
continue ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
node . wrap ( self . filterNode ( new Node ( 'ul' , 1 ) ) ) ;
continue ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Try wrapping the element in a DIV
if ( schema . isValidChild ( node . parent . name , 'div' ) && schema . isValidChild ( 'div' , node . name ) ) {
node . wrap ( self . filterNode ( new Node ( 'div' , 1 ) ) ) ;
} else {
// We failed wrapping it, then remove or unwrap it
if ( node . name === 'style' || node . name === 'script' )
node . empty ( ) . remove ( ) ;
else
node . unwrap ( ) ;
}
}
}
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . filterNode = function ( node ) {
var i , name , list ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Run element filters
if ( name in nodeFilters ) {
list = matchedNodes [ name ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( list )
list . push ( node ) ;
else
matchedNodes [ name ] = [ node ] ;
}
// Run attribute filters
i = attributeFilters . length ;
while ( i -- ) {
name = attributeFilters [ i ] . name ;
if ( name in node . attributes . map ) {
list = matchedAttributes [ name ] ;
if ( list )
list . push ( node ) ;
else
matchedAttributes [ name ] = [ node ] ;
2010-07-02 01:48:07 +02:00
}
}
2012-03-21 04:47:31 +01:00
return node ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . addNodeFilter = function ( name , callback ) {
tinymce . each ( tinymce . explode ( name ) , function ( name ) {
var list = nodeFilters [ name ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! list )
nodeFilters [ name ] = list = [ ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
list . push ( callback ) ;
} ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . addAttributeFilter = function ( name , callback ) {
tinymce . each ( tinymce . explode ( name ) , function ( name ) {
var i ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
for ( i = 0 ; i < attributeFilters . length ; i ++ ) {
if ( attributeFilters [ i ] . name === name ) {
attributeFilters [ i ] . callbacks . push ( callback ) ;
2010-07-02 01:48:07 +02:00
return ;
2012-03-21 04:47:31 +01:00
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
attributeFilters . push ( { name : name , callbacks : [ callback ] } ) ;
} ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . parse = function ( html , args ) {
var parser , rootNode , node , nodes , i , l , fi , fl , list , name , validate ,
blockElements , startWhiteSpaceRegExp , invalidChildren = [ ] , isInWhiteSpacePreservedElement ,
2012-05-16 02:18:46 +02:00
endWhiteSpaceRegExp , allWhiteSpaceRegExp , isAllWhiteSpaceRegExp , whiteSpaceElements , children , nonEmptyElements , rootBlockName ;
2012-03-21 04:47:31 +01:00
args = args || { } ;
matchedNodes = { } ;
matchedAttributes = { } ;
blockElements = tinymce . extend ( tinymce . makeMap ( 'script,style,head,html,body,title,meta,param' ) , schema . getBlockElements ( ) ) ;
nonEmptyElements = schema . getNonEmptyElements ( ) ;
children = schema . children ;
validate = settings . validate ;
rootBlockName = "forced_root_block" in args ? args . forced _root _block : settings . forced _root _block ;
whiteSpaceElements = schema . getWhiteSpaceElements ( ) ;
startWhiteSpaceRegExp = /^[ \t\r\n]+/ ;
endWhiteSpaceRegExp = /[ \t\r\n]+$/ ;
allWhiteSpaceRegExp = /[ \t\r\n]+/g ;
2012-05-16 02:18:46 +02:00
isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/ ;
2012-03-21 04:47:31 +01:00
function addRootBlocks ( ) {
var node = rootNode . firstChild , next , rootBlockNode ;
while ( node ) {
next = node . next ;
if ( node . type == 3 || ( node . type == 1 && node . name !== 'p' && ! blockElements [ node . name ] && ! node . attr ( 'data-mce-type' ) ) ) {
if ( ! rootBlockNode ) {
// Create a new root block element
rootBlockNode = createNode ( rootBlockName , 1 ) ;
rootNode . insert ( rootBlockNode , node ) ;
rootBlockNode . append ( node ) ;
} else
rootBlockNode . append ( node ) ;
} else {
rootBlockNode = null ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
node = next ;
} ;
} ;
function createNode ( name , type ) {
var node = new Node ( name , type ) , list ;
if ( name in nodeFilters ) {
list = matchedNodes [ name ] ;
if ( list )
list . push ( node ) ;
else
matchedNodes [ name ] = [ node ] ;
2010-07-02 01:48:07 +02:00
}
return node ;
} ;
2012-03-21 04:47:31 +01:00
function removeWhitespaceBefore ( node ) {
var textNode , textVal , sibling ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
for ( textNode = node . prev ; textNode && textNode . type === 3 ; ) {
textVal = textNode . value . replace ( endWhiteSpaceRegExp , '' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( textVal . length > 0 ) {
textNode . value = textVal ;
textNode = textNode . prev ;
} else {
sibling = textNode . prev ;
textNode . remove ( ) ;
textNode = sibling ;
}
}
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
parser = new tinymce . html . SaxParser ( {
validate : validate ,
fix _self _closing : ! validate , // Let the DOM parser handle <li> in <li> or <p> in <p> for better results
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
cdata : function ( text ) {
node . append ( createNode ( '#cdata' , 4 ) ) . value = text ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
text : function ( text , raw ) {
var textNode ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Trim all redundant whitespace on non white space elements
if ( ! isInWhiteSpacePreservedElement ) {
text = text . replace ( allWhiteSpaceRegExp , ' ' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( node . lastChild && blockElements [ node . lastChild . name ] )
text = text . replace ( startWhiteSpaceRegExp , '' ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Do we need to create the node
if ( text . length !== 0 ) {
textNode = createNode ( '#text' , 3 ) ;
textNode . raw = ! ! raw ;
node . append ( textNode ) . value = text ;
}
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
comment : function ( text ) {
node . append ( createNode ( '#comment' , 8 ) ) . value = text ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
pi : function ( name , text ) {
node . append ( createNode ( name , 7 ) ) . value = text ;
removeWhitespaceBefore ( node ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
doctype : function ( text ) {
var newNode ;
newNode = node . append ( createNode ( '#doctype' , 10 ) ) ;
newNode . value = text ;
removeWhitespaceBefore ( node ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
start : function ( name , attrs , empty ) {
var newNode , attrFiltersLen , elementRule , textNode , attrName , text , sibling , parent ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
elementRule = validate ? schema . getElementRule ( name ) : { } ;
if ( elementRule ) {
newNode = createNode ( elementRule . outputName || name , 1 ) ;
newNode . attributes = attrs ;
newNode . shortEnded = empty ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
node . append ( newNode ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Check if node is valid child of the parent node is the child is
// unknown we don't collect it since it's probably a custom element
parent = children [ node . name ] ;
if ( parent && children [ newNode . name ] && ! parent [ newNode . name ] )
invalidChildren . push ( newNode ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
attrFiltersLen = attributeFilters . length ;
while ( attrFiltersLen -- ) {
attrName = attributeFilters [ attrFiltersLen ] . name ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( attrName in attrs . map ) {
list = matchedAttributes [ attrName ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( list )
list . push ( newNode ) ;
else
matchedAttributes [ attrName ] = [ newNode ] ;
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Trim whitespace before block
if ( blockElements [ name ] )
removeWhitespaceBefore ( newNode ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Change current node if the element wasn't empty i.e not <br /> or <img />
if ( ! empty )
node = newNode ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Check if we are inside a whitespace preserved element
if ( ! isInWhiteSpacePreservedElement && whiteSpaceElements [ name ] ) {
isInWhiteSpacePreservedElement = true ;
}
}
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
end : function ( name ) {
var textNode , elementRule , text , sibling , tempNode ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
elementRule = validate ? schema . getElementRule ( name ) : { } ;
if ( elementRule ) {
if ( blockElements [ name ] ) {
if ( ! isInWhiteSpacePreservedElement ) {
2012-05-16 02:18:46 +02:00
// Trim whitespace of the first node in a block
textNode = node . firstChild ;
if ( textNode && textNode . type === 3 ) {
2012-03-21 04:47:31 +01:00
text = textNode . value . replace ( startWhiteSpaceRegExp , '' ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Any characters left after trim or should we remove it
2012-03-21 04:47:31 +01:00
if ( text . length > 0 ) {
textNode . value = text ;
textNode = textNode . next ;
} else {
sibling = textNode . next ;
textNode . remove ( ) ;
textNode = sibling ;
}
2012-05-16 02:18:46 +02:00
// Remove any pure whitespace siblings
while ( textNode && textNode . type === 3 ) {
text = textNode . value ;
sibling = textNode . next ;
if ( text . length === 0 || isAllWhiteSpaceRegExp . test ( text ) ) {
textNode . remove ( ) ;
textNode = sibling ;
}
textNode = sibling ;
}
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Trim whitespace of the last node in a block
textNode = node . lastChild ;
if ( textNode && textNode . type === 3 ) {
2012-03-21 04:47:31 +01:00
text = textNode . value . replace ( endWhiteSpaceRegExp , '' ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Any characters left after trim or should we remove it
2012-03-21 04:47:31 +01:00
if ( text . length > 0 ) {
textNode . value = text ;
textNode = textNode . prev ;
} else {
sibling = textNode . prev ;
textNode . remove ( ) ;
textNode = sibling ;
}
2012-05-16 02:18:46 +02:00
// Remove any pure whitespace siblings
while ( textNode && textNode . type === 3 ) {
text = textNode . value ;
sibling = textNode . prev ;
if ( text . length === 0 || isAllWhiteSpaceRegExp . test ( text ) ) {
textNode . remove ( ) ;
textNode = sibling ;
}
textNode = sibling ;
}
2012-03-21 04:47:31 +01:00
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Trim start white space
textNode = node . prev ;
if ( textNode && textNode . type === 3 ) {
text = textNode . value . replace ( startWhiteSpaceRegExp , '' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( text . length > 0 )
textNode . value = text ;
else
textNode . remove ( ) ;
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Check if we exited a whitespace preserved element
if ( isInWhiteSpacePreservedElement && whiteSpaceElements [ name ] ) {
isInWhiteSpacePreservedElement = false ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Handle empty nodes
if ( elementRule . removeEmpty || elementRule . paddEmpty ) {
if ( node . isEmpty ( nonEmptyElements ) ) {
if ( elementRule . paddEmpty )
node . empty ( ) . append ( new Node ( '#text' , '3' ) ) . value = '\u00a0' ;
else {
// Leave nodes that have a name like <a name="name">
if ( ! node . attributes . map . name ) {
tempNode = node . parent ;
node . empty ( ) . remove ( ) ;
node = tempNode ;
return ;
}
}
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
node = node . parent ;
}
}
} , schema ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
rootNode = node = new Node ( args . context || settings . root _name , 11 ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
parser . parse ( html ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Fix invalid children or report invalid children in a contextual parsing
if ( validate && invalidChildren . length ) {
if ( ! args . context )
fixInvalidChildren ( invalidChildren ) ;
else
args . invalid = true ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
// Wrap nodes in the root into block elements if the root is body
if ( rootBlockName && rootNode . name == 'body' )
addRootBlocks ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Run filters only when the contents is valid
if ( ! args . invalid ) {
// Run node filters
for ( name in matchedNodes ) {
list = nodeFilters [ name ] ;
nodes = matchedNodes [ name ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Remove already removed children
fi = nodes . length ;
while ( fi -- ) {
if ( ! nodes [ fi ] . parent )
nodes . splice ( fi , 1 ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
for ( i = 0 , l = list . length ; i < l ; i ++ )
list [ i ] ( nodes , name , args ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Run attribute filters
for ( i = 0 , l = attributeFilters . length ; i < l ; i ++ ) {
list = attributeFilters [ i ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( list . name in matchedAttributes ) {
nodes = matchedAttributes [ list . name ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Remove already removed children
fi = nodes . length ;
while ( fi -- ) {
if ( ! nodes [ fi ] . parent )
nodes . splice ( fi , 1 ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
for ( fi = 0 , fl = list . callbacks . length ; fi < fl ; fi ++ )
list . callbacks [ fi ] ( nodes , list . name , args ) ;
}
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return rootNode ;
2010-07-02 01:48:07 +02:00
} ;
2012-03-21 04:47:31 +01:00
// Remove <br> at end of block elements Gecko and WebKit injects BR elements to
// make it possible to place the caret inside empty blocks. This logic tries to remove
// these elements and keep br elements that where intended to be there intact
if ( settings . remove _trailing _brs ) {
self . addNodeFilter ( 'br' , function ( nodes , name ) {
2012-05-16 02:18:46 +02:00
var i , l = nodes . length , node , blockElements = tinymce . extend ( { } , schema . getBlockElements ( ) ) ,
nonEmptyElements = schema . getNonEmptyElements ( ) , parent , lastParent , prev , prevName ;
2012-03-21 04:47:31 +01:00
// Remove brs from body element as well
blockElements . body = 1 ;
// Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
for ( i = 0 ; i < l ; i ++ ) {
node = nodes [ i ] ;
parent = node . parent ;
if ( blockElements [ node . parent . name ] && node === parent . lastChild ) {
2012-05-16 02:18:46 +02:00
// Loop all nodes to the left of the current node and check for other BR elements
2012-03-21 04:47:31 +01:00
// excluding bookmarks since they are invisible
prev = node . prev ;
while ( prev ) {
prevName = prev . name ;
// Ignore bookmarks
if ( prevName !== "span" || prev . attr ( 'data-mce-type' ) !== 'bookmark' ) {
// Found a non BR element
if ( prevName !== "br" )
break ;
// Found another br it's a <br><br> structure then don't remove anything
if ( prevName === 'br' ) {
node = null ;
break ;
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
prev = prev . prev ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( node ) {
node . remove ( ) ;
// Is the parent to be considered empty after we removed the BR
if ( parent . isEmpty ( nonEmptyElements ) ) {
elementRule = schema . getElementRule ( parent . name ) ;
// Remove or padd the element depending on schema rule
if ( elementRule ) {
if ( elementRule . removeEmpty )
parent . remove ( ) ;
else if ( elementRule . paddEmpty )
parent . empty ( ) . append ( new tinymce . html . Node ( '#text' , 3 ) ) . value = '\u00a0' ;
}
}
}
2012-05-16 02:18:46 +02:00
} else {
// Replaces BR elements inside inline elements like <p><b><i><br></i></b></p> so they become <p><b><i> </i></b></p>
lastParent = node ;
while ( parent . firstChild === lastParent && parent . lastChild === lastParent ) {
lastParent = parent ;
if ( blockElements [ parent . name ] ) {
break ;
}
parent = parent . parent ;
}
if ( lastParent === parent ) {
textNode = new tinymce . html . Node ( '#text' , 3 ) ;
textNode . value = '\u00a0' ;
node . replace ( textNode ) ;
}
}
}
} ) ;
}
// Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
if ( ! settings . allow _html _in _named _anchor ) {
self . addAttributeFilter ( 'name' , function ( nodes , name ) {
var i = nodes . length , sibling , prevSibling , parent , node ;
while ( i -- ) {
node = nodes [ i ] ;
if ( node . name === 'a' && node . firstChild ) {
parent = node . parent ;
// Move children after current node
sibling = node . lastChild ;
do {
prevSibling = sibling . prev ;
parent . insert ( sibling , node ) ;
sibling = prevSibling ;
} while ( sibling ) ;
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
} ) ;
}
}
} ) ( tinymce ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinymce . html . Writer = function ( settings ) {
var html = [ ] , indent , indentBefore , indentAfter , encode , htmlOutput ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
settings = settings || { } ;
indent = settings . indent ;
indentBefore = tinymce . makeMap ( settings . indent _before || '' ) ;
indentAfter = tinymce . makeMap ( settings . indent _after || '' ) ;
encode = tinymce . html . Entities . getEncodeFunc ( settings . entity _encoding || 'raw' , settings . entities ) ;
htmlOutput = settings . element _format == "html" ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return {
start : function ( name , attrs , empty ) {
var i , l , attr , value ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( indent && indentBefore [ name ] && html . length > 0 ) {
value = html [ html . length - 1 ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( value . length > 0 && value !== '\n' )
html . push ( '\n' ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
html . push ( '<' , name ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( attrs ) {
for ( i = 0 , l = attrs . length ; i < l ; i ++ ) {
attr = attrs [ i ] ;
html . push ( ' ' , attr . name , '="' , encode ( attr . value , true ) , '"' ) ;
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! empty || htmlOutput )
html [ html . length ] = '>' ;
else
html [ html . length ] = ' />' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( empty && indent && indentAfter [ name ] && html . length > 0 ) {
value = html [ html . length - 1 ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( value . length > 0 && value !== '\n' )
html . push ( '\n' ) ;
}
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
end : function ( name ) {
var value ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
/ * i f ( i n d e n t & & i n d e n t B e f o r e [ n a m e ] & & h t m l . l e n g t h > 0 ) {
value = html [ html . length - 1 ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( value . length > 0 && value !== '\n' )
html . push ( '\n' ) ;
} * /
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
html . push ( '</' , name , '>' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( indent && indentAfter [ name ] && html . length > 0 ) {
value = html [ html . length - 1 ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( value . length > 0 && value !== '\n' )
html . push ( '\n' ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
text : function ( text , raw ) {
if ( text . length > 0 )
html [ html . length ] = raw ? text : encode ( text ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
cdata : function ( text ) {
html . push ( '<![CDATA[' , text , ']]>' ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
comment : function ( text ) {
html . push ( '<!--' , text , '-->' ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
pi : function ( name , text ) {
if ( text )
html . push ( '<?' , name , ' ' , text , '?>' ) ;
else
html . push ( '<?' , name , '?>' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( indent )
html . push ( '\n' ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
doctype : function ( text ) {
html . push ( '<!DOCTYPE' , text , '>' , indent ? '\n' : '' ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
reset : function ( ) {
html . length = 0 ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
getContent : function ( ) {
return html . join ( '' ) . replace ( /\n$/ , '' ) ;
}
} ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
( function ( tinymce ) {
tinymce . html . Serializer = function ( settings , schema ) {
var self = this , writer = new tinymce . html . Writer ( settings ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
settings = settings || { } ;
settings . validate = "validate" in settings ? settings . validate : true ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . schema = schema = schema || new tinymce . html . Schema ( ) ;
self . writer = writer ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . serialize = function ( node ) {
var handlers , validate ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
validate = settings . validate ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
handlers = {
// #text
3 : function ( node , raw ) {
writer . text ( node . value , node . raw ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// #comment
8 : function ( node ) {
writer . comment ( node . value ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Processing instruction
7 : function ( node ) {
writer . pi ( node . name , node . value ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Doctype
10 : function ( node ) {
writer . doctype ( node . value ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// CDATA
4 : function ( node ) {
writer . cdata ( node . value ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Document fragment
2012-03-21 04:47:31 +01:00
11 : function ( node ) {
if ( ( node = node . firstChild ) ) {
do {
walk ( node ) ;
} while ( node = node . next ) ;
}
}
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
writer . reset ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function walk ( node ) {
var handler = handlers [ node . type ] , name , isEmpty , attrs , attrName , attrValue , sortedAttrs , i , l , elementRule ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! handler ) {
name = node . name ;
isEmpty = node . shortEnded ;
attrs = node . attributes ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Sort attributes
if ( validate && attrs && attrs . length > 1 ) {
sortedAttrs = [ ] ;
sortedAttrs . map = { } ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
elementRule = schema . getElementRule ( node . name ) ;
for ( i = 0 , l = elementRule . attributesOrder . length ; i < l ; i ++ ) {
attrName = elementRule . attributesOrder [ i ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( attrName in attrs . map ) {
attrValue = attrs . map [ attrName ] ;
sortedAttrs . map [ attrName ] = attrValue ;
sortedAttrs . push ( { name : attrName , value : attrValue } ) ;
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
for ( i = 0 , l = attrs . length ; i < l ; i ++ ) {
attrName = attrs [ i ] . name ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! ( attrName in sortedAttrs . map ) ) {
attrValue = attrs . map [ attrName ] ;
sortedAttrs . map [ attrName ] = attrValue ;
sortedAttrs . push ( { name : attrName , value : attrValue } ) ;
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
attrs = sortedAttrs ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
writer . start ( node . name , attrs , isEmpty ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! isEmpty ) {
if ( ( node = node . firstChild ) ) {
do {
walk ( node ) ;
} while ( node = node . next ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
writer . end ( name ) ;
}
} else
handler ( node ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
// Serialize element and treat all non elements as fragments
if ( node . type == 1 && ! settings . inner )
walk ( node ) ;
else
handlers [ 11 ] ( node ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return writer . getContent ( ) ;
2010-07-02 01:48:07 +02:00
} ;
2012-03-21 04:47:31 +01:00
}
} ) ( tinymce ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// JSLint defined globals
/*global tinymce:false, window:false */
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinymce . dom = { } ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
( function ( namespace , expando ) {
var w3cEventModel = ! ! document . addEventListener ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function addEvent ( target , name , callback , capture ) {
if ( target . addEventListener ) {
target . addEventListener ( name , callback , capture || false ) ;
} else if ( target . attachEvent ) {
target . attachEvent ( 'on' + name , callback ) ;
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function removeEvent ( target , name , callback , capture ) {
if ( target . removeEventListener ) {
target . removeEventListener ( name , callback , capture || false ) ;
} else if ( target . detachEvent ) {
target . detachEvent ( 'on' + name , callback ) ;
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function fix ( original _event , data ) {
var name , event = data || { } ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Dummy function that gets replaced on the delegation state functions
function returnFalse ( ) {
return false ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Dummy function that gets replaced on the delegation state functions
function returnTrue ( ) {
return true ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Copy all properties from the original event
for ( name in original _event ) {
// layerX/layerY is deprecated in Chrome and produces a warning
if ( name !== "layerX" && name !== "layerY" ) {
event [ name ] = original _event [ name ] ;
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Normalize target IE uses srcElement
if ( ! event . target ) {
event . target = event . srcElement || document ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Add preventDefault method
event . preventDefault = function ( ) {
event . isDefaultPrevented = returnTrue ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Execute preventDefault on the original event object
if ( original _event ) {
if ( original _event . preventDefault ) {
original _event . preventDefault ( ) ;
} else {
original _event . returnValue = false ; // IE
}
2010-07-02 01:48:07 +02:00
}
} ;
2012-03-21 04:47:31 +01:00
// Add stopPropagation
event . stopPropagation = function ( ) {
event . isPropagationStopped = returnTrue ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Execute stopPropagation on the original event object
if ( original _event ) {
if ( original _event . stopPropagation ) {
original _event . stopPropagation ( ) ;
} else {
original _event . cancelBubble = true ; // IE
}
}
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Add stopImmediatePropagation
event . stopImmediatePropagation = function ( ) {
event . isImmediatePropagationStopped = returnTrue ;
event . stopPropagation ( ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Add event delegation states
if ( ! event . isDefaultPrevented ) {
event . isDefaultPrevented = returnFalse ;
event . isPropagationStopped = returnFalse ;
event . isImmediatePropagationStopped = returnFalse ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return event ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function bindOnReady ( win , callback , event _utils ) {
var doc = win . document , event = { type : 'ready' } ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Gets called when the DOM is ready
function readyHandler ( ) {
if ( ! event _utils . domLoaded ) {
event _utils . domLoaded = true ;
callback ( event ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Use W3C method
if ( w3cEventModel ) {
addEvent ( win , 'DOMContentLoaded' , readyHandler ) ;
} else {
// Use IE method
addEvent ( doc , "readystatechange" , function ( ) {
if ( doc . readyState === "complete" ) {
removeEvent ( doc , "readystatechange" , arguments . callee ) ;
readyHandler ( ) ;
}
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Wait until we can scroll, when we can the DOM is initialized
if ( doc . documentElement . doScroll && win === win . top ) {
( function ( ) {
try {
// If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
// http://javascript.nwbox.com/IEContentLoaded/
doc . documentElement . doScroll ( "left" ) ;
} catch ( ex ) {
setTimeout ( arguments . callee , 0 ) ;
return ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
readyHandler ( ) ;
} ) ( ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Fallback if any of the above methods should fail for some odd reason
addEvent ( win , 'load' , readyHandler ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function EventUtils ( proxy ) {
var self = this , events = { } , count , isFocusBlurBound , hasFocusIn , hasMouseEnterLeave , mouseEnterLeave ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
hasMouseEnterLeave = "onmouseenter" in document . documentElement ;
hasFocusIn = "onfocusin" in document . documentElement ;
mouseEnterLeave = { mouseenter : 'mouseover' , mouseleave : 'mouseout' } ;
count = 1 ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// State if the DOMContentLoaded was executed or not
self . domLoaded = false ;
self . events = events ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function executeHandlers ( evt , id ) {
var callbackList , i , l , callback ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
callbackList = events [ id ] [ evt . type ] ;
if ( callbackList ) {
for ( i = 0 , l = callbackList . length ; i < l ; i ++ ) {
callback = callbackList [ i ] ;
// Check if callback exists might be removed if a unbind is called inside the callback
if ( callback && callback . func . call ( callback . scope , evt ) === false ) {
evt . preventDefault ( ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Should we stop propagation to immediate listeners
if ( evt . isImmediatePropagationStopped ( ) ) {
return ;
}
}
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . bind = function ( target , names , callback , scope ) {
var id , callbackList , i , name , fakeName , nativeHandler , capture , win = window ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Native event handler function patches the event and executes the callbacks for the expando
function defaultNativeHandler ( evt ) {
executeHandlers ( fix ( evt || win . event ) , id ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
// Don't bind to text nodes or comments
if ( ! target || target . nodeType === 3 || target . nodeType === 8 ) {
return ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
// Create or get events id for the target
if ( ! target [ expando ] ) {
id = count ++ ;
target [ expando ] = id ;
events [ id ] = { } ;
} else {
id = target [ expando ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! events [ id ] ) {
events [ id ] = { } ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Setup the specified scope or use the target as a default
scope = scope || target ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Split names and bind each event, enables you to bind multiple events with one call
names = names . split ( ' ' ) ;
i = names . length ;
while ( i -- ) {
name = names [ i ] ;
nativeHandler = defaultNativeHandler ;
fakeName = capture = false ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Use ready instead of DOMContentLoaded
if ( name === "DOMContentLoaded" ) {
name = "ready" ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// DOM is already ready
if ( ( self . domLoaded || target . readyState == 'complete' ) && name === "ready" ) {
self . domLoaded = true ;
callback . call ( scope , fix ( { type : name } ) ) ;
continue ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Handle mouseenter/mouseleaver
if ( ! hasMouseEnterLeave ) {
fakeName = mouseEnterLeave [ name ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( fakeName ) {
nativeHandler = function ( evt ) {
var current , related ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
current = evt . currentTarget ;
related = evt . relatedTarget ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Check if related is inside the current target if it's not then the event should be ignored since it's a mouseover/mouseout inside the element
if ( related && current . contains ) {
// Use contains for performance
related = current . contains ( related ) ;
} else {
while ( related && related !== current ) {
related = related . parentNode ;
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Fire fake event
if ( ! related ) {
evt = fix ( evt || win . event ) ;
evt . type = evt . type === 'mouseout' ? 'mouseleave' : 'mouseenter' ;
evt . target = current ;
executeHandlers ( evt , id ) ;
}
} ;
}
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
// Fake bubbeling of focusin/focusout
if ( ! hasFocusIn && ( name === "focusin" || name === "focusout" ) ) {
capture = true ;
fakeName = name === "focusin" ? "focus" : "blur" ;
nativeHandler = function ( evt ) {
evt = fix ( evt || win . event ) ;
evt . type = evt . type === 'focus' ? 'focusin' : 'focusout' ;
executeHandlers ( evt , id ) ;
} ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Setup callback list and bind native event
callbackList = events [ id ] [ name ] ;
if ( ! callbackList ) {
events [ id ] [ name ] = callbackList = [ { func : callback , scope : scope } ] ;
callbackList . fakeName = fakeName ;
callbackList . capture = capture ;
// Add the nativeHandler to the callback list so that we can later unbind it
callbackList . nativeHandler = nativeHandler ;
if ( ! w3cEventModel ) {
callbackList . proxyHandler = proxy ( id ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Check if the target has native events support
if ( name === "ready" ) {
bindOnReady ( target , nativeHandler , self ) ;
} else {
addEvent ( target , fakeName || name , w3cEventModel ? nativeHandler : callbackList . proxyHandler , capture ) ;
}
} else {
// If it already has an native handler then just push the callback
callbackList . push ( { func : callback , scope : scope } ) ;
}
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
target = callbackList = 0 ; // Clean memory for IE
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return callback ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . unbind = function ( target , names , callback ) {
var id , callbackList , i , ci , name , eventMap ;
// Don't bind to text nodes or comments
if ( ! target || target . nodeType === 3 || target . nodeType === 8 ) {
return self ;
}
// Unbind event or events if the target has the expando
id = target [ expando ] ;
if ( id ) {
eventMap = events [ id ] ;
// Specific callback
if ( names ) {
names = names . split ( ' ' ) ;
i = names . length ;
while ( i -- ) {
name = names [ i ] ;
callbackList = eventMap [ name ] ;
// Unbind the event if it exists in the map
if ( callbackList ) {
// Remove specified callback
if ( callback ) {
ci = callbackList . length ;
while ( ci -- ) {
if ( callbackList [ ci ] . func === callback ) {
callbackList . splice ( ci , 1 ) ;
}
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Remove all callbacks if there isn't a specified callback or there is no callbacks left
if ( ! callback || callbackList . length === 0 ) {
delete eventMap [ name ] ;
removeEvent ( target , callbackList . fakeName || name , w3cEventModel ? callbackList . nativeHandler : callbackList . proxyHandler , callbackList . capture ) ;
}
}
}
2010-07-02 01:48:07 +02:00
} else {
2012-03-21 04:47:31 +01:00
// All events for a specific element
for ( name in eventMap ) {
callbackList = eventMap [ name ] ;
removeEvent ( target , callbackList . fakeName || name , w3cEventModel ? callbackList . nativeHandler : callbackList . proxyHandler , callbackList . capture ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
eventMap = { } ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Check if object is empty, if it isn't then we won't remove the expando map
for ( name in eventMap ) {
return self ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Delete event object
delete events [ id ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Remove expando from target
try {
// IE will fail here since it can't delete properties from window
delete target [ expando ] ;
} catch ( ex ) {
// IE will set it to null
target [ expando ] = null ;
}
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
return self ;
2010-07-02 01:48:07 +02:00
} ;
2012-03-21 04:47:31 +01:00
self . fire = function ( target , name , args ) {
var id , event ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Don't bind to text nodes or comments
if ( ! target || target . nodeType === 3 || target . nodeType === 8 ) {
return self ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Build event object by patching the args
event = fix ( null , args ) ;
event . type = name ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
do {
// Found an expando that means there is listeners to execute
id = target [ expando ] ;
if ( id ) {
executeHandlers ( event , id ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Walk up the DOM
target = target . parentNode || target . ownerDocument || target . defaultView || target . parentWindow ;
} while ( target && ! event . isPropagationStopped ( ) ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return self ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . clean = function ( target ) {
var i , children , unbind = self . unbind ;
// Don't bind to text nodes or comments
if ( ! target || target . nodeType === 3 || target . nodeType === 8 ) {
return self ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
// Unbind any element on the specificed target
if ( target [ expando ] ) {
unbind ( target ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children
if ( ! target . getElementsByTagName ) {
target = target . document ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Remove events from each child element
if ( target && target . getElementsByTagName ) {
unbind ( target ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
children = target . getElementsByTagName ( '*' ) ;
i = children . length ;
while ( i -- ) {
target = children [ i ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( target [ expando ] ) {
unbind ( target ) ;
}
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return self ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . callNativeHandler = function ( id , evt ) {
if ( events ) {
events [ id ] [ evt . type ] . nativeHandler ( evt ) ;
}
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . destory = function ( ) {
events = { } ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Legacy function calls
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . add = function ( target , events , func , scope ) {
// Old API supported direct ID assignment
if ( typeof ( target ) === "string" ) {
target = document . getElementById ( target ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Old API supported multiple targets
if ( target && target instanceof Array ) {
var i = target ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
while ( i -- ) {
self . add ( target [ i ] , events , func , scope ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
return ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Old API called ready init
if ( events === "init" ) {
events = "ready" ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return self . bind ( target , events instanceof Array ? events . join ( ' ' ) : events , func , scope ) ;
2010-07-02 01:48:07 +02:00
} ;
2012-05-16 02:18:46 +02:00
self . remove = function ( target , events , func , scope ) {
if ( ! target ) {
return self ;
}
2012-03-21 04:47:31 +01:00
// Old API supported direct ID assignment
if ( typeof ( target ) === "string" ) {
target = document . getElementById ( target ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Old API supported multiple targets
if ( target instanceof Array ) {
2012-05-16 02:18:46 +02:00
var i = target . length ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
while ( i -- ) {
self . remove ( target [ i ] , events , func , scope ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return self ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
return self . unbind ( target , events instanceof Array ? events . join ( ' ' ) : events , func ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . clear = function ( target ) {
// Old API supported direct ID assignment
if ( typeof ( target ) === "string" ) {
target = document . getElementById ( target ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
return self . clean ( target ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . cancel = function ( e ) {
if ( e ) {
self . prevent ( e ) ;
self . stop ( e ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
return false ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . prevent = function ( e ) {
e . preventDefault ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return false ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . stop = function ( e ) {
e . stopPropagation ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return false ;
} ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
namespace . EventUtils = EventUtils ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
namespace . Event = new EventUtils ( function ( id ) {
return function ( evt ) {
tinymce . dom . Event . callNativeHandler ( id , evt ) ;
} ;
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Bind ready event when tinymce script is loaded
namespace . Event . bind ( window , 'ready' , function ( ) { } ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
namespace = 0 ;
} ) ( tinymce . dom , 'data-mce-expando' ) ; // Namespace and expando
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
tinymce . dom . TreeWalker = function ( start _node , root _node ) {
var node = start _node ;
function findSibling ( node , start _name , sibling _name , shallow ) {
var sibling , parent ;
if ( node ) {
// Walk into nodes if it has a start
if ( ! shallow && node [ start _name ] )
return node [ start _name ] ;
// Return the sibling if it has one
if ( node != root _node ) {
sibling = node [ sibling _name ] ;
if ( sibling )
return sibling ;
// Walk up the parents to look for siblings
for ( parent = node . parentNode ; parent && parent != root _node ; parent = parent . parentNode ) {
sibling = parent [ sibling _name ] ;
if ( sibling )
return sibling ;
}
}
}
} ;
this . current = function ( ) {
return node ;
} ;
this . next = function ( shallow ) {
return ( node = findSibling ( node , 'firstChild' , 'nextSibling' , shallow ) ) ;
} ;
this . prev = function ( shallow ) {
return ( node = findSibling ( node , 'lastChild' , 'previousSibling' , shallow ) ) ;
} ;
} ;
2012-03-21 04:47:31 +01:00
( function ( tinymce ) {
// Shorten names
var each = tinymce . each ,
is = tinymce . is ,
isWebKit = tinymce . isWebKit ,
isIE = tinymce . isIE ,
Entities = tinymce . html . Entities ,
simpleSelectorRe = /^([a-z0-9],?)+$/i ,
whiteSpaceRegExp = /^[ \t\r\n]*$/ ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinymce . create ( 'tinymce.dom.DOMUtils' , {
doc : null ,
root : null ,
files : null ,
pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/ ,
props : {
"for" : "htmlFor" ,
"class" : "className" ,
className : "className" ,
checked : "checked" ,
disabled : "disabled" ,
maxlength : "maxLength" ,
readonly : "readOnly" ,
selected : "selected" ,
value : "value" ,
id : "id" ,
name : "name" ,
type : "type"
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
DOMUtils : function ( d , s ) {
var t = this , globalStyle , name , blockElementsMap ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . doc = d ;
t . win = window ;
t . files = { } ;
t . cssFlicker = false ;
t . counter = 0 ;
t . stdMode = ! tinymce . isIE || d . documentMode >= 8 ;
t . boxModel = ! tinymce . isIE || d . compatMode == "CSS1Compat" || t . stdMode ;
t . hasOuterHTML = "outerHTML" in d . createElement ( "a" ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . settings = s = tinymce . extend ( {
keep _values : false ,
hex _colors : 1
} , s ) ;
t . schema = s . schema ;
t . styles = new tinymce . html . Styles ( {
url _converter : s . url _converter ,
url _converter _scope : s . url _converter _scope
} , s . schema ) ;
// Fix IE6SP2 flicker and check it failed for pre SP2
if ( tinymce . isIE6 ) {
try {
d . execCommand ( 'BackgroundImageCache' , false , true ) ;
} catch ( e ) {
t . cssFlicker = true ;
}
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
t . fixDoc ( d ) ;
t . events = s . ownEvents ? new tinymce . dom . EventUtils ( s . proxy ) : tinymce . dom . Event ;
tinymce . addUnload ( t . destroy , t ) ;
blockElementsMap = s . schema ? s . schema . getBlockElements ( ) : { } ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . isBlock = function ( node ) {
// This function is called in module pattern style since it might be executed with the wrong this scope
var type = node . nodeType ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// If it's a node then check the type and use the nodeName
if ( type )
return ! ! ( type === 1 && blockElementsMap [ node . nodeName ] ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return ! ! blockElementsMap [ node ] ;
} ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
fixDoc : function ( doc ) {
var settings = this . settings , name ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( isIE && settings . schema ) {
// Add missing HTML 4/5 elements to IE
( 'abbr article aside audio canvas ' +
'details figcaption figure footer ' +
'header hgroup mark menu meter nav ' +
'output progress section summary ' +
'time video' ) . replace ( /\w+/g , function ( name ) {
doc . createElement ( name ) ;
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Create all custom elements
for ( name in settings . schema . getCustomElements ( ) ) {
doc . createElement ( name ) ;
}
}
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
clone : function ( node , deep ) {
var self = this , clone , doc ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// TODO: Add feature detection here in the future
if ( ! isIE || node . nodeType !== 1 || deep ) {
return node . cloneNode ( deep ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
doc = self . doc ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Make a HTML5 safe shallow copy
if ( ! deep ) {
clone = doc . createElement ( node . nodeName ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Copy attribs
each ( self . getAttribs ( node ) , function ( attr ) {
self . setAttrib ( clone , attr . nodeName , self . getAttrib ( node , attr . nodeName ) ) ;
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return clone ;
}
/ *
// Setup HTML5 patched document fragment
if ( ! self . frag ) {
self . frag = doc . createDocumentFragment ( ) ;
self . fixDoc ( self . frag ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Make a deep copy by adding it to the document fragment then removing it this removed the :section
clone = doc . createElement ( 'div' ) ;
self . frag . appendChild ( clone ) ;
clone . innerHTML = node . outerHTML ;
self . frag . removeChild ( clone ) ;
* /
return clone . firstChild ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
getRoot : function ( ) {
var t = this , s = t . settings ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return ( s && t . get ( s . root _element ) ) || t . doc . body ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
getViewPort : function ( w ) {
var d , b ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
w = ! w ? this . win : w ;
d = w . document ;
b = this . boxModel ? d . documentElement : d . body ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Returns viewport size excluding scrollbars
return {
x : w . pageXOffset || b . scrollLeft ,
y : w . pageYOffset || b . scrollTop ,
w : w . innerWidth || b . clientWidth ,
h : w . innerHeight || b . clientHeight
} ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
getRect : function ( e ) {
var p , t = this , sr ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
e = t . get ( e ) ;
p = t . getPos ( e ) ;
sr = t . getSize ( e ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return {
x : p . x ,
y : p . y ,
w : sr . w ,
h : sr . h
} ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
getSize : function ( e ) {
var t = this , w , h ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
e = t . get ( e ) ;
w = t . getStyle ( e , 'width' ) ;
h = t . getStyle ( e , 'height' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Non pixel value, then force offset/clientWidth
if ( w . indexOf ( 'px' ) === - 1 )
w = 0 ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Non pixel value, then force offset/clientWidth
if ( h . indexOf ( 'px' ) === - 1 )
h = 0 ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return {
2012-05-16 02:18:46 +02:00
w : parseInt ( w , 10 ) || e . offsetWidth || e . clientWidth ,
h : parseInt ( h , 10 ) || e . offsetHeight || e . clientHeight
2012-03-21 04:47:31 +01:00
} ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
getParent : function ( n , f , r ) {
return this . getParents ( n , f , r , false ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
getParents : function ( n , f , r , c ) {
var t = this , na , se = t . settings , o = [ ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
n = t . get ( n ) ;
c = c === undefined ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( se . strict _root )
r = r || t . getRoot ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Wrap node name as func
if ( is ( f , 'string' ) ) {
na = f ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( f === '*' ) {
f = function ( n ) { return n . nodeType == 1 ; } ;
} else {
f = function ( n ) {
return t . is ( n , na ) ;
} ;
2010-07-02 01:48:07 +02:00
}
}
2012-03-21 04:47:31 +01:00
while ( n ) {
if ( n == r || ! n . nodeType || n . nodeType === 9 )
break ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! f || f ( n ) ) {
if ( c )
o . push ( n ) ;
else
return n ;
}
n = n . parentNode ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
return c ? o : null ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
get : function ( e ) {
var n ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( e && this . doc && typeof ( e ) == 'string' ) {
n = e ;
e = this . doc . getElementById ( e ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
if ( e && e . id !== n )
return this . doc . getElementsByName ( n ) [ 1 ] ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
return e ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
getNext : function ( node , selector ) {
return this . _findSib ( node , selector , 'nextSibling' ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
getPrev : function ( node , selector ) {
return this . _findSib ( node , selector , 'previousSibling' ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
select : function ( pa , s ) {
var t = this ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return tinymce . dom . Sizzle ( pa , t . get ( s ) || t . get ( t . settings . root _element ) || t . doc , [ ] ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
is : function ( n , selector ) {
var i ;
// If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
if ( n . length === undefined ) {
// Simple all selector
if ( selector === '*' )
return n . nodeType == 1 ;
// Simple selector just elements
if ( simpleSelectorRe . test ( selector ) ) {
selector = selector . toLowerCase ( ) . split ( /,/ ) ;
n = n . nodeName . toLowerCase ( ) ;
for ( i = selector . length - 1 ; i >= 0 ; i -- ) {
if ( selector [ i ] == n )
return true ;
}
return false ;
2010-07-02 01:48:07 +02:00
}
}
2012-03-21 04:47:31 +01:00
return tinymce . dom . Sizzle . matches ( selector , n . nodeType ? [ n ] : n ) . length > 0 ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
add : function ( p , n , a , h , c ) {
var t = this ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return this . run ( p , function ( p ) {
var e , k ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
e = is ( n , 'string' ) ? t . doc . createElement ( n ) : n ;
t . setAttribs ( e , a ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( h ) {
if ( h . nodeType )
e . appendChild ( h ) ;
else
t . setHTML ( e , h ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
return ! c ? p . appendChild ( e ) : e ;
} ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
create : function ( n , a , h ) {
return this . add ( this . doc . createElement ( n ) , n , a , h , 1 ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
createHTML : function ( n , a , h ) {
var o = '' , t = this , k ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
o += '<' + n ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
for ( k in a ) {
if ( a . hasOwnProperty ( k ) )
o += ' ' + k + '="' + t . encode ( a [ k ] ) + '"' ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
if ( typeof ( h ) != "undefined" )
return o + '>' + h + '</' + n + '>' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return o + ' />' ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
remove : function ( node , keep _children ) {
return this . run ( node , function ( node ) {
var child , parent = node . parentNode ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! parent )
return null ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( keep _children ) {
while ( child = node . firstChild ) {
// IE 8 will crash if you don't remove completely empty text nodes
if ( ! tinymce . isIE || child . nodeType !== 3 || child . nodeValue )
parent . insertBefore ( child , node ) ;
else
node . removeChild ( child ) ;
2010-07-02 01:48:07 +02:00
}
}
2012-03-21 04:47:31 +01:00
return parent . removeChild ( node ) ;
} ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
setStyle : function ( n , na , v ) {
var t = this ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return t . run ( n , function ( e ) {
var s , i ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
s = e . style ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Camelcase it, if needed
na = na . replace ( /-(\D)/g , function ( a , b ) {
return b . toUpperCase ( ) ;
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Default px suffix on these
if ( t . pixelStyles . test ( na ) && ( tinymce . is ( v , 'number' ) || /^[\-0-9\.]+$/ . test ( v ) ) )
v += 'px' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
switch ( na ) {
case 'opacity' :
// IE specific opacity
if ( isIE ) {
s . filter = v === '' ? '' : "alpha(opacity=" + ( v * 100 ) + ")" ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! n . currentStyle || ! n . currentStyle . hasLayout )
s . display = 'inline-block' ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Fix for older browsers
s [ na ] = s [ '-moz-opacity' ] = s [ '-khtml-opacity' ] = v || '' ;
break ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
case 'float' :
isIE ? s . styleFloat = v : s . cssFloat = v ;
break ;
default :
s [ na ] = v || '' ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
// Force update of the style data
if ( t . settings . update _styles )
t . setAttrib ( e , 'data-mce-style' ) ;
} ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
getStyle : function ( n , na , c ) {
n = this . get ( n ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! n )
return ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Gecko
if ( this . doc . defaultView && c ) {
// Remove camelcase
na = na . replace ( /[A-Z]/g , function ( a ) {
return '-' + a ;
} ) ;
try {
return this . doc . defaultView . getComputedStyle ( n , null ) . getPropertyValue ( na ) ;
} catch ( ex ) {
// Old safari might fail
return null ;
2010-07-02 01:48:07 +02:00
}
}
2012-03-21 04:47:31 +01:00
// Camelcase it, if needed
na = na . replace ( /-(\D)/g , function ( a , b ) {
return b . toUpperCase ( ) ;
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( na == 'float' )
na = isIE ? 'styleFloat' : 'cssFloat' ;
// IE & Opera
if ( n . currentStyle && c )
return n . currentStyle [ na ] ;
return n . style ? n . style [ na ] : undefined ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
setStyles : function ( e , o ) {
var t = this , s = t . settings , ol ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
ol = s . update _styles ;
s . update _styles = 0 ;
each ( o , function ( v , n ) {
t . setStyle ( e , n , v ) ;
} ) ;
// Update style info
s . update _styles = ol ;
if ( s . update _styles )
t . setAttrib ( e , s . cssText ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
removeAllAttribs : function ( e ) {
return this . run ( e , function ( e ) {
var i , attrs = e . attributes ;
for ( i = attrs . length - 1 ; i >= 0 ; i -- ) {
e . removeAttributeNode ( attrs . item ( i ) ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
} ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
setAttrib : function ( e , n , v ) {
var t = this ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Whats the point
if ( ! e || ! n )
return ;
// Strict XML mode
if ( t . settings . strict )
n = n . toLowerCase ( ) ;
return this . run ( e , function ( e ) {
var s = t . settings ;
var originalValue = e . getAttribute ( n ) ;
if ( v !== null ) {
switch ( n ) {
case "style" :
if ( ! is ( v , 'string' ) ) {
each ( v , function ( v , n ) {
t . setStyle ( e , n , v ) ;
} ) ;
return ;
}
// No mce_style for elements with these since they might get resized by the user
if ( s . keep _values ) {
if ( v && ! t . _isRes ( v ) )
e . setAttribute ( 'data-mce-style' , v , 2 ) ;
else
e . removeAttribute ( 'data-mce-style' , 2 ) ;
}
e . style . cssText = v ;
break ;
case "class" :
e . className = v || '' ; // Fix IE null bug
break ;
case "src" :
case "href" :
if ( s . keep _values ) {
if ( s . url _converter )
v = s . url _converter . call ( s . url _converter _scope || t , v , n , e ) ;
t . setAttrib ( e , 'data-mce-' + n , v , 2 ) ;
}
break ;
case "shape" :
e . setAttribute ( 'data-mce-style' , v ) ;
break ;
2010-07-02 01:48:07 +02:00
}
}
2012-03-21 04:47:31 +01:00
if ( is ( v ) && v !== null && v . length !== 0 )
e . setAttribute ( n , '' + v , 2 ) ;
else
e . removeAttribute ( n , 2 ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// fire onChangeAttrib event for attributes that have changed
if ( tinyMCE . activeEditor && originalValue != v ) {
var ed = tinyMCE . activeEditor ;
ed . onSetAttrib . dispatch ( ed , e , n , v ) ;
}
} ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
setAttribs : function ( e , o ) {
var t = this ;
return this . run ( e , function ( e ) {
each ( o , function ( v , n ) {
t . setAttrib ( e , n , v ) ;
} ) ;
} ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
getAttrib : function ( e , n , dv ) {
var v , t = this , undef ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
e = t . get ( e ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! e || e . nodeType !== 1 )
return dv === undef ? false : dv ;
if ( ! is ( dv ) )
dv = '' ;
// Try the mce variant for these
if ( /^(src|href|style|coords|shape)$/ . test ( n ) ) {
v = e . getAttribute ( "data-mce-" + n ) ;
if ( v )
return v ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
if ( isIE && t . props [ n ] ) {
v = e [ t . props [ n ] ] ;
v = v && v . nodeValue ? v . nodeValue : v ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
if ( ! v )
v = e . getAttribute ( n , 2 ) ;
// Check boolean attribs
if ( /^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/ . test ( n ) ) {
if ( e [ t . props [ n ] ] === true && v === '' )
return n ;
return v ? n : '' ;
}
// Inner input elements will override attributes on form elements
if ( e . nodeName === "FORM" && e . getAttributeNode ( n ) )
return e . getAttributeNode ( n ) . nodeValue ;
if ( n === 'style' ) {
v = v || e . style . cssText ;
if ( v ) {
v = t . serializeStyle ( t . parseStyle ( v ) , e . nodeName ) ;
if ( t . settings . keep _values && ! t . _isRes ( v ) )
e . setAttribute ( 'data-mce-style' , v ) ;
2010-07-02 01:48:07 +02:00
}
}
2012-03-21 04:47:31 +01:00
// Remove Apple and WebKit stuff
if ( isWebKit && n === "class" && v )
v = v . replace ( /(apple|webkit)\-[a-z\-]+/gi , '' ) ;
// Handle IE issues
if ( isIE ) {
switch ( n ) {
case 'rowspan' :
case 'colspan' :
// IE returns 1 as default value
if ( v === 1 )
v = '' ;
break ;
case 'size' :
// IE returns +0 as default value for size
if ( v === '+0' || v === 20 || v === 0 )
v = '' ;
break ;
case 'width' :
case 'height' :
case 'vspace' :
case 'checked' :
case 'disabled' :
case 'readonly' :
if ( v === 0 )
v = '' ;
break ;
case 'hspace' :
// IE returns -1 as default value
if ( v === - 1 )
v = '' ;
break ;
case 'maxlength' :
case 'tabindex' :
// IE returns default value
if ( v === 32768 || v === 2147483647 || v === '32768' )
v = '' ;
break ;
case 'multiple' :
case 'compact' :
case 'noshade' :
case 'nowrap' :
if ( v === 65535 )
return n ;
return dv ;
case 'shape' :
v = v . toLowerCase ( ) ;
break ;
default :
// IE has odd anonymous function for event attributes
if ( n . indexOf ( 'on' ) === 0 && v )
v = tinymce . _replace ( /^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/ , '$1' , '' + v ) ;
}
}
return ( v !== undef && v !== null && v !== '' ) ? '' + v : dv ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
getPos : function ( n , ro ) {
var t = this , x = 0 , y = 0 , e , d = t . doc , r ;
n = t . get ( n ) ;
ro = ro || d . body ;
if ( n ) {
// Use getBoundingClientRect if it exists since it's faster than looping offset nodes
if ( n . getBoundingClientRect ) {
n = n . getBoundingClientRect ( ) ;
e = t . boxModel ? d . documentElement : d . body ;
// Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
// Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
x = n . left + ( d . documentElement . scrollLeft || d . body . scrollLeft ) - e . clientTop ;
y = n . top + ( d . documentElement . scrollTop || d . body . scrollTop ) - e . clientLeft ;
return { x : x , y : y } ;
}
r = n ;
while ( r && r != ro && r . nodeType ) {
x += r . offsetLeft || 0 ;
y += r . offsetTop || 0 ;
r = r . offsetParent ;
}
r = n . parentNode ;
while ( r && r != ro && r . nodeType ) {
x -= r . scrollLeft || 0 ;
y -= r . scrollTop || 0 ;
r = r . parentNode ;
}
}
return { x : x , y : y } ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
parseStyle : function ( st ) {
return this . styles . parse ( st ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
serializeStyle : function ( o , name ) {
return this . styles . serialize ( o , name ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
loadCSS : function ( u ) {
var t = this , d = t . doc , head ;
if ( ! u )
u = '' ;
2012-05-16 02:18:46 +02:00
head = d . getElementsByTagName ( 'head' ) [ 0 ] ;
2012-03-21 04:47:31 +01:00
each ( u . split ( ',' ) , function ( u ) {
var link ;
if ( t . files [ u ] )
return ;
t . files [ u ] = true ;
link = t . create ( 'link' , { rel : 'stylesheet' , href : tinymce . _addVer ( u ) } ) ;
// IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
// This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
// It's ugly but it seems to work fine.
if ( isIE && d . documentMode && d . recalc ) {
link . onload = function ( ) {
if ( d . recalc )
d . recalc ( ) ;
link . onload = null ;
} ;
}
head . appendChild ( link ) ;
} ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
addClass : function ( e , c ) {
return this . run ( e , function ( e ) {
var o ;
if ( ! c )
return 0 ;
if ( this . hasClass ( e , c ) )
return e . className ;
o = this . removeClass ( e , c ) ;
return e . className = ( o != '' ? ( o + ' ' ) : '' ) + c ;
} ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
removeClass : function ( e , c ) {
var t = this , re ;
return t . run ( e , function ( e ) {
var v ;
if ( t . hasClass ( e , c ) ) {
if ( ! re )
re = new RegExp ( "(^|\\s+)" + c + "(\\s+|$)" , "g" ) ;
v = e . className . replace ( re , ' ' ) ;
v = tinymce . trim ( v != ' ' ? v : '' ) ;
e . className = v ;
// Empty class attr
if ( ! v ) {
e . removeAttribute ( 'class' ) ;
e . removeAttribute ( 'className' ) ;
}
return v ;
}
return e . className ;
} ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
hasClass : function ( n , c ) {
n = this . get ( n ) ;
if ( ! n || ! c )
return false ;
return ( ' ' + n . className + ' ' ) . indexOf ( ' ' + c + ' ' ) !== - 1 ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
show : function ( e ) {
return this . setStyle ( e , 'display' , 'block' ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
hide : function ( e ) {
return this . setStyle ( e , 'display' , 'none' ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
isHidden : function ( e ) {
e = this . get ( e ) ;
return ! e || e . style . display == 'none' || this . getStyle ( e , 'display' ) == 'none' ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
uniqueId : function ( p ) {
return ( ! p ? 'mce_' : p ) + ( this . counter ++ ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
setHTML : function ( element , html ) {
var self = this ;
return self . run ( element , function ( element ) {
if ( isIE ) {
// Remove all child nodes, IE keeps empty text nodes in DOM
while ( element . firstChild )
element . removeChild ( element . firstChild ) ;
try {
// IE will remove comments from the beginning
// unless you padd the contents with something
element . innerHTML = '<br />' + html ;
element . removeChild ( element . firstChild ) ;
} catch ( ex ) {
// IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p
// This seems to fix this problem
// Create new div with HTML contents and a BR infront to keep comments
element = self . create ( 'div' ) ;
element . innerHTML = '<br />' + html ;
// Add all children from div to target
each ( element . childNodes , function ( node , i ) {
// Skip br element
if ( i )
element . appendChild ( node ) ;
} ) ;
}
} else
element . innerHTML = html ;
return html ;
} ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
getOuterHTML : function ( elm ) {
var doc , self = this ;
elm = self . get ( elm ) ;
if ( ! elm )
return null ;
if ( elm . nodeType === 1 && self . hasOuterHTML )
return elm . outerHTML ;
doc = ( elm . ownerDocument || self . doc ) . createElement ( "body" ) ;
doc . appendChild ( elm . cloneNode ( true ) ) ;
return doc . innerHTML ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
setOuterHTML : function ( e , h , d ) {
var t = this ;
function setHTML ( e , h , d ) {
var n , tp ;
tp = d . createElement ( "body" ) ;
tp . innerHTML = h ;
n = tp . lastChild ;
while ( n ) {
t . insertAfter ( n . cloneNode ( true ) , e ) ;
n = n . previousSibling ;
}
t . remove ( e ) ;
} ;
return this . run ( e , function ( e ) {
e = t . get ( e ) ;
// Only set HTML on elements
if ( e . nodeType == 1 ) {
d = d || e . ownerDocument || t . doc ;
if ( isIE ) {
try {
// Try outerHTML for IE it sometimes produces an unknown runtime error
if ( isIE && e . nodeType == 1 )
e . outerHTML = h ;
else
setHTML ( e , h , d ) ;
} catch ( ex ) {
// Fix for unknown runtime error
setHTML ( e , h , d ) ;
}
} else
setHTML ( e , h , d ) ;
}
} ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
decode : Entities . decode ,
encode : Entities . encodeAllRaw ,
insertAfter : function ( node , reference _node ) {
reference _node = this . get ( reference _node ) ;
return this . run ( node , function ( node ) {
var parent , nextSibling ;
parent = reference _node . parentNode ;
nextSibling = reference _node . nextSibling ;
if ( nextSibling )
parent . insertBefore ( node , nextSibling ) ;
else
parent . appendChild ( node ) ;
return node ;
} ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
replace : function ( n , o , k ) {
var t = this ;
if ( is ( o , 'array' ) )
n = n . cloneNode ( true ) ;
return t . run ( o , function ( o ) {
if ( k ) {
each ( tinymce . grep ( o . childNodes ) , function ( c ) {
n . appendChild ( c ) ;
} ) ;
}
return o . parentNode . replaceChild ( n , o ) ;
} ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
rename : function ( elm , name ) {
var t = this , newElm ;
if ( elm . nodeName != name . toUpperCase ( ) ) {
// Rename block element
newElm = t . create ( name ) ;
// Copy attribs to new block
each ( t . getAttribs ( elm ) , function ( attr _node ) {
t . setAttrib ( newElm , attr _node . nodeName , t . getAttrib ( elm , attr _node . nodeName ) ) ;
} ) ;
// Replace block
t . replace ( newElm , elm , 1 ) ;
}
return newElm || elm ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
findCommonAncestor : function ( a , b ) {
var ps = a , pe ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
while ( ps ) {
pe = b ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
while ( pe && ps != pe )
pe = pe . parentNode ;
if ( ps == pe )
break ;
ps = ps . parentNode ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
if ( ! ps && a . ownerDocument )
return a . ownerDocument . documentElement ;
return ps ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
toHex : function ( s ) {
var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i . exec ( s ) ;
function hex ( s ) {
2012-05-16 02:18:46 +02:00
s = parseInt ( s , 10 ) . toString ( 16 ) ;
2012-03-21 04:47:31 +01:00
return s . length > 1 ? s : '0' + s ; // 0 -> 00
} ;
if ( c ) {
s = '#' + hex ( c [ 1 ] ) + hex ( c [ 2 ] ) + hex ( c [ 3 ] ) ;
return s ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
return s ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
getClasses : function ( ) {
var t = this , cl = [ ] , i , lo = { } , f = t . settings . class _filter , ov ;
if ( t . classes )
return t . classes ;
function addClasses ( s ) {
// IE style imports
each ( s . imports , function ( r ) {
addClasses ( r ) ;
} ) ;
each ( s . cssRules || s . rules , function ( r ) {
// Real type or fake it on IE
switch ( r . type || 1 ) {
// Rule
case 1 :
if ( r . selectorText ) {
each ( r . selectorText . split ( ',' ) , function ( v ) {
v = v . replace ( /^\s*|\s*$|^\s\./g , "" ) ;
// Is internal or it doesn't contain a class
if ( /\.mce/ . test ( v ) || ! /\.[\w\-]+$/ . test ( v ) )
return ;
// Remove everything but class name
ov = v ;
v = tinymce . _replace ( /.*\.([a-z0-9_\-]+).*/i , '$1' , v ) ;
// Filter classes
if ( f && ! ( v = f ( v , ov ) ) )
return ;
if ( ! lo [ v ] ) {
cl . push ( { 'class' : v } ) ;
lo [ v ] = 1 ;
}
} ) ;
}
break ;
// Import
case 3 :
addClasses ( r . styleSheet ) ;
break ;
}
} ) ;
} ;
try {
each ( t . doc . styleSheets , addClasses ) ;
} catch ( ex ) {
// Ignore
}
if ( cl . length > 0 )
t . classes = cl ;
return cl ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
run : function ( e , f , s ) {
var t = this , o ;
if ( t . doc && typeof ( e ) === 'string' )
e = t . get ( e ) ;
if ( ! e )
return false ;
s = s || this ;
if ( ! e . nodeType && ( e . length || e . length === 0 ) ) {
o = [ ] ;
each ( e , function ( e , i ) {
if ( e ) {
if ( typeof ( e ) == 'string' )
e = t . doc . getElementById ( e ) ;
o . push ( f . call ( s , e , i ) ) ;
}
} ) ;
return o ;
}
return f . call ( s , e ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
getAttribs : function ( n ) {
var o ;
n = this . get ( n ) ;
if ( ! n )
return [ ] ;
if ( isIE ) {
o = [ ] ;
// Object will throw exception in IE
if ( n . nodeName == 'OBJECT' )
return n . attributes ;
// IE doesn't keep the selected attribute if you clone option elements
if ( n . nodeName === 'OPTION' && this . getAttrib ( n , 'selected' ) )
o . push ( { specified : 1 , nodeName : 'selected' } ) ;
// It's crazy that this is faster in IE but it's because it returns all attributes all the time
n . cloneNode ( false ) . outerHTML . replace ( /<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi , '' ) . replace ( /[\w:\-]+/gi , function ( a ) {
o . push ( { specified : 1 , nodeName : a } ) ;
} ) ;
return o ;
}
return n . attributes ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
isEmpty : function ( node , elements ) {
var self = this , i , attributes , type , walker , name , brCount = 0 ;
node = node . firstChild ;
if ( node ) {
walker = new tinymce . dom . TreeWalker ( node , node . parentNode ) ;
elements = elements || self . schema ? self . schema . getNonEmptyElements ( ) : null ;
do {
type = node . nodeType ;
if ( type === 1 ) {
// Ignore bogus elements
if ( node . getAttribute ( 'data-mce-bogus' ) )
continue ;
// Keep empty elements like <img />
name = node . nodeName . toLowerCase ( ) ;
if ( elements && elements [ name ] ) {
// Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p>
if ( name === 'br' ) {
brCount ++ ;
continue ;
}
return false ;
}
// Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
attributes = self . getAttribs ( node ) ;
i = node . attributes . length ;
while ( i -- ) {
name = node . attributes [ i ] . nodeName ;
if ( name === "name" || name === 'data-mce-bookmark' )
return false ;
}
}
// Keep comment nodes
if ( type == 8 )
return false ;
// Keep non whitespace text nodes
if ( ( type === 3 && ! whiteSpaceRegExp . test ( node . nodeValue ) ) )
return false ;
} while ( node = walker . next ( ) ) ;
}
return brCount <= 1 ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
destroy : function ( s ) {
var t = this ;
t . win = t . doc = t . root = t . events = t . frag = null ;
// Manual destroy then remove unload handler
if ( ! s )
tinymce . removeUnload ( t . destroy ) ;
} ,
createRng : function ( ) {
var d = this . doc ;
return d . createRange ? d . createRange ( ) : new tinymce . dom . Range ( this ) ;
} ,
nodeIndex : function ( node , normalized ) {
var idx = 0 , lastNodeType , lastNode , nodeType ;
if ( node ) {
for ( lastNodeType = node . nodeType , node = node . previousSibling , lastNode = node ; node ; node = node . previousSibling ) {
nodeType = node . nodeType ;
// Normalize text nodes
if ( normalized && nodeType == 3 ) {
if ( nodeType == lastNodeType || ! node . nodeValue . length )
continue ;
}
idx ++ ;
lastNodeType = nodeType ;
}
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
return idx ;
} ,
split : function ( pe , e , re ) {
var t = this , r = t . createRng ( ) , bef , aft , pa ;
// W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
// but we don't want that in our code since it serves no purpose for the end user
// For example if this is chopped:
// <p>text 1<span><b>CHOP</b></span>text 2</p>
// would produce:
// <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
// this function will then trim of empty edges and produce:
// <p>text 1</p><b>CHOP</b><p>text 2</p>
function trim ( node ) {
var i , children = node . childNodes , type = node . nodeType ;
function surroundedBySpans ( node ) {
var previousIsSpan = node . previousSibling && node . previousSibling . nodeName == 'SPAN' ;
var nextIsSpan = node . nextSibling && node . nextSibling . nodeName == 'SPAN' ;
return previousIsSpan && nextIsSpan ;
}
if ( type == 1 && node . getAttribute ( 'data-mce-type' ) == 'bookmark' )
return ;
for ( i = children . length - 1 ; i >= 0 ; i -- )
trim ( children [ i ] ) ;
if ( type != 9 ) {
// Keep non whitespace text nodes
if ( type == 3 && node . nodeValue . length > 0 ) {
// If parent element isn't a block or there isn't any useful contents for example "<p> </p>"
// Also keep text nodes with only spaces if surrounded by spans.
// eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b
var trimmedLength = tinymce . trim ( node . nodeValue ) . length ;
2012-05-16 02:18:46 +02:00
if ( ! t . isBlock ( node . parentNode ) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans ( node ) )
2012-03-21 04:47:31 +01:00
return ;
} else if ( type == 1 ) {
// If the only child is a bookmark then move it up
children = node . childNodes ;
if ( children . length == 1 && children [ 0 ] && children [ 0 ] . nodeType == 1 && children [ 0 ] . getAttribute ( 'data-mce-type' ) == 'bookmark' )
node . parentNode . insertBefore ( children [ 0 ] , node ) ;
// Keep non empty elements or img, hr etc
if ( children . length || /^(br|hr|input|img)$/i . test ( node . nodeName ) )
return ;
}
t . remove ( node ) ;
}
return node ;
} ;
if ( pe && e ) {
// Get before chunk
r . setStart ( pe . parentNode , t . nodeIndex ( pe ) ) ;
r . setEnd ( e . parentNode , t . nodeIndex ( e ) ) ;
bef = r . extractContents ( ) ;
// Get after chunk
r = t . createRng ( ) ;
r . setStart ( e . parentNode , t . nodeIndex ( e ) + 1 ) ;
r . setEnd ( pe . parentNode , t . nodeIndex ( pe ) + 1 ) ;
aft = r . extractContents ( ) ;
// Insert before chunk
pa = pe . parentNode ;
pa . insertBefore ( trim ( bef ) , pe ) ;
// Insert middle chunk
if ( re )
pa . replaceChild ( re , e ) ;
else
pa . insertBefore ( e , pe ) ;
// Insert after chunk
pa . insertBefore ( trim ( aft ) , pe ) ;
t . remove ( pe ) ;
return re || e ;
}
} ,
bind : function ( target , name , func , scope ) {
return this . events . add ( target , name , func , scope || this ) ;
} ,
unbind : function ( target , name , func ) {
return this . events . remove ( target , name , func ) ;
} ,
fire : function ( target , name , evt ) {
return this . events . fire ( target , name , evt ) ;
} ,
2012-05-16 02:18:46 +02:00
// Returns the content editable state of a node
getContentEditable : function ( node ) {
var contentEditable ;
// Check type
if ( node . nodeType != 1 ) {
return null ;
}
// Check for fake content editable
contentEditable = node . getAttribute ( "data-mce-contenteditable" ) ;
if ( contentEditable && contentEditable !== "inherit" ) {
return contentEditable ;
}
// Check for real content editable
return node . contentEditable !== "inherit" ? node . contentEditable : null ;
} ,
2012-03-21 04:47:31 +01:00
_findSib : function ( node , selector , name ) {
var t = this , f = selector ;
if ( node ) {
// If expression make a function of it using is
if ( is ( f , 'string' ) ) {
f = function ( node ) {
return t . is ( node , selector ) ;
} ;
}
// Loop all siblings
for ( node = node [ name ] ; node ; node = node [ name ] ) {
if ( f ( node ) )
return node ;
}
}
return null ;
} ,
_isRes : function ( c ) {
// Is live resizble element
return /^(top|left|bottom|right|width|height)/i . test ( c ) || /;\s*(top|left|bottom|right|width|height)/i . test ( c ) ;
}
/ *
walk : function ( n , f , s ) {
var d = this . doc , w ;
if ( d . createTreeWalker ) {
w = d . createTreeWalker ( n , NodeFilter . SHOW _TEXT , null , false ) ;
while ( ( n = w . nextNode ( ) ) != null )
f . call ( s || this , n ) ;
} else
tinymce . walk ( n , f , 'childNodes' , s ) ;
}
* /
/ *
toRGB : function ( s ) {
var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/ . exec ( s ) ;
if ( c ) {
// #FFF -> #FFFFFF
if ( ! is ( c [ 3 ] ) )
c [ 3 ] = c [ 2 ] = c [ 1 ] ;
return "rgb(" + parseInt ( c [ 1 ] , 16 ) + "," + parseInt ( c [ 2 ] , 16 ) + "," + parseInt ( c [ 3 ] , 16 ) + ")" ;
}
return s ;
}
* /
} ) ;
tinymce . DOM = new tinymce . dom . DOMUtils ( document , { process _html : 0 } ) ;
} ) ( tinymce ) ;
( function ( ns ) {
// Range constructor
function Range ( dom ) {
var t = this ,
doc = dom . doc ,
EXTRACT = 0 ,
CLONE = 1 ,
DELETE = 2 ,
TRUE = true ,
FALSE = false ,
START _OFFSET = 'startOffset' ,
START _CONTAINER = 'startContainer' ,
END _CONTAINER = 'endContainer' ,
END _OFFSET = 'endOffset' ,
extend = tinymce . extend ,
nodeIndex = dom . nodeIndex ;
extend ( t , {
// Inital states
startContainer : doc ,
startOffset : 0 ,
endContainer : doc ,
endOffset : 0 ,
collapsed : TRUE ,
commonAncestorContainer : doc ,
// Range constants
START _TO _START : 0 ,
START _TO _END : 1 ,
END _TO _END : 2 ,
END _TO _START : 3 ,
// Public methods
setStart : setStart ,
setEnd : setEnd ,
setStartBefore : setStartBefore ,
setStartAfter : setStartAfter ,
setEndBefore : setEndBefore ,
setEndAfter : setEndAfter ,
collapse : collapse ,
selectNode : selectNode ,
selectNodeContents : selectNodeContents ,
compareBoundaryPoints : compareBoundaryPoints ,
deleteContents : deleteContents ,
extractContents : extractContents ,
cloneContents : cloneContents ,
insertNode : insertNode ,
surroundContents : surroundContents ,
cloneRange : cloneRange
} ) ;
function createDocumentFragment ( ) {
return doc . createDocumentFragment ( ) ;
} ;
function setStart ( n , o ) {
_setEndPoint ( TRUE , n , o ) ;
} ;
function setEnd ( n , o ) {
_setEndPoint ( FALSE , n , o ) ;
} ;
function setStartBefore ( n ) {
setStart ( n . parentNode , nodeIndex ( n ) ) ;
} ;
function setStartAfter ( n ) {
setStart ( n . parentNode , nodeIndex ( n ) + 1 ) ;
} ;
function setEndBefore ( n ) {
setEnd ( n . parentNode , nodeIndex ( n ) ) ;
} ;
function setEndAfter ( n ) {
setEnd ( n . parentNode , nodeIndex ( n ) + 1 ) ;
} ;
function collapse ( ts ) {
if ( ts ) {
t [ END _CONTAINER ] = t [ START _CONTAINER ] ;
t [ END _OFFSET ] = t [ START _OFFSET ] ;
} else {
t [ START _CONTAINER ] = t [ END _CONTAINER ] ;
t [ START _OFFSET ] = t [ END _OFFSET ] ;
}
t . collapsed = TRUE ;
} ;
function selectNode ( n ) {
setStartBefore ( n ) ;
setEndAfter ( n ) ;
} ;
function selectNodeContents ( n ) {
setStart ( n , 0 ) ;
setEnd ( n , n . nodeType === 1 ? n . childNodes . length : n . nodeValue . length ) ;
} ;
function compareBoundaryPoints ( h , r ) {
var sc = t [ START _CONTAINER ] , so = t [ START _OFFSET ] , ec = t [ END _CONTAINER ] , eo = t [ END _OFFSET ] ,
rsc = r . startContainer , rso = r . startOffset , rec = r . endContainer , reo = r . endOffset ;
// Check START_TO_START
if ( h === 0 )
return _compareBoundaryPoints ( sc , so , rsc , rso ) ;
// Check START_TO_END
if ( h === 1 )
return _compareBoundaryPoints ( ec , eo , rsc , rso ) ;
// Check END_TO_END
if ( h === 2 )
return _compareBoundaryPoints ( ec , eo , rec , reo ) ;
// Check END_TO_START
if ( h === 3 )
return _compareBoundaryPoints ( sc , so , rec , reo ) ;
} ;
function deleteContents ( ) {
_traverse ( DELETE ) ;
} ;
function extractContents ( ) {
return _traverse ( EXTRACT ) ;
} ;
function cloneContents ( ) {
return _traverse ( CLONE ) ;
} ;
function insertNode ( n ) {
var startContainer = this [ START _CONTAINER ] ,
startOffset = this [ START _OFFSET ] , nn , o ;
// Node is TEXT_NODE or CDATA
if ( ( startContainer . nodeType === 3 || startContainer . nodeType === 4 ) && startContainer . nodeValue ) {
if ( ! startOffset ) {
// At the start of text
startContainer . parentNode . insertBefore ( n , startContainer ) ;
} else if ( startOffset >= startContainer . nodeValue . length ) {
// At the end of text
dom . insertAfter ( n , startContainer ) ;
} else {
// Middle, need to split
nn = startContainer . splitText ( startOffset ) ;
startContainer . parentNode . insertBefore ( n , nn ) ;
}
} else {
// Insert element node
if ( startContainer . childNodes . length > 0 )
o = startContainer . childNodes [ startOffset ] ;
if ( o )
startContainer . insertBefore ( n , o ) ;
else
startContainer . appendChild ( n ) ;
}
} ;
function surroundContents ( n ) {
var f = t . extractContents ( ) ;
t . insertNode ( n ) ;
n . appendChild ( f ) ;
t . selectNode ( n ) ;
} ;
function cloneRange ( ) {
return extend ( new Range ( dom ) , {
startContainer : t [ START _CONTAINER ] ,
startOffset : t [ START _OFFSET ] ,
endContainer : t [ END _CONTAINER ] ,
endOffset : t [ END _OFFSET ] ,
collapsed : t . collapsed ,
commonAncestorContainer : t . commonAncestorContainer
} ) ;
} ;
// Private methods
function _getSelectedNode ( container , offset ) {
var child ;
if ( container . nodeType == 3 /* TEXT_NODE */ )
return container ;
if ( offset < 0 )
return container ;
child = container . firstChild ;
while ( child && offset > 0 ) {
-- offset ;
child = child . nextSibling ;
}
if ( child )
return child ;
return container ;
} ;
function _isCollapsed ( ) {
return ( t [ START _CONTAINER ] == t [ END _CONTAINER ] && t [ START _OFFSET ] == t [ END _OFFSET ] ) ;
} ;
function _compareBoundaryPoints ( containerA , offsetA , containerB , offsetB ) {
var c , offsetC , n , cmnRoot , childA , childB ;
// In the first case the boundary-points have the same container. A is before B
// if its offset is less than the offset of B, A is equal to B if its offset is
// equal to the offset of B, and A is after B if its offset is greater than the
// offset of B.
if ( containerA == containerB ) {
if ( offsetA == offsetB )
return 0 ; // equal
if ( offsetA < offsetB )
return - 1 ; // before
return 1 ; // after
}
// In the second case a child node C of the container of A is an ancestor
// container of B. In this case, A is before B if the offset of A is less than or
// equal to the index of the child node C and A is after B otherwise.
c = containerB ;
while ( c && c . parentNode != containerA )
c = c . parentNode ;
if ( c ) {
offsetC = 0 ;
n = containerA . firstChild ;
while ( n != c && offsetC < offsetA ) {
offsetC ++ ;
n = n . nextSibling ;
}
if ( offsetA <= offsetC )
return - 1 ; // before
return 1 ; // after
}
// In the third case a child node C of the container of B is an ancestor container
// of A. In this case, A is before B if the index of the child node C is less than
// the offset of B and A is after B otherwise.
c = containerA ;
while ( c && c . parentNode != containerB ) {
c = c . parentNode ;
}
if ( c ) {
offsetC = 0 ;
n = containerB . firstChild ;
while ( n != c && offsetC < offsetB ) {
offsetC ++ ;
n = n . nextSibling ;
}
if ( offsetC < offsetB )
return - 1 ; // before
return 1 ; // after
}
// In the fourth case, none of three other cases hold: the containers of A and B
// are siblings or descendants of sibling nodes. In this case, A is before B if
// the container of A is before the container of B in a pre-order traversal of the
// Ranges' context tree and A is after B otherwise.
cmnRoot = dom . findCommonAncestor ( containerA , containerB ) ;
childA = containerA ;
while ( childA && childA . parentNode != cmnRoot )
childA = childA . parentNode ;
if ( ! childA )
childA = cmnRoot ;
childB = containerB ;
while ( childB && childB . parentNode != cmnRoot )
childB = childB . parentNode ;
if ( ! childB )
childB = cmnRoot ;
if ( childA == childB )
return 0 ; // equal
n = cmnRoot . firstChild ;
while ( n ) {
if ( n == childA )
return - 1 ; // before
if ( n == childB )
return 1 ; // after
n = n . nextSibling ;
}
} ;
function _setEndPoint ( st , n , o ) {
var ec , sc ;
if ( st ) {
t [ START _CONTAINER ] = n ;
t [ START _OFFSET ] = o ;
} else {
t [ END _CONTAINER ] = n ;
t [ END _OFFSET ] = o ;
}
// If one boundary-point of a Range is set to have a root container
// other than the current one for the Range, the Range is collapsed to
// the new position. This enforces the restriction that both boundary-
// points of a Range must have the same root container.
ec = t [ END _CONTAINER ] ;
while ( ec . parentNode )
ec = ec . parentNode ;
sc = t [ START _CONTAINER ] ;
while ( sc . parentNode )
sc = sc . parentNode ;
if ( sc == ec ) {
// The start position of a Range is guaranteed to never be after the
// end position. To enforce this restriction, if the start is set to
// be at a position after the end, the Range is collapsed to that
// position.
if ( _compareBoundaryPoints ( t [ START _CONTAINER ] , t [ START _OFFSET ] , t [ END _CONTAINER ] , t [ END _OFFSET ] ) > 0 )
t . collapse ( st ) ;
} else
t . collapse ( st ) ;
t . collapsed = _isCollapsed ( ) ;
t . commonAncestorContainer = dom . findCommonAncestor ( t [ START _CONTAINER ] , t [ END _CONTAINER ] ) ;
} ;
function _traverse ( how ) {
var c , endContainerDepth = 0 , startContainerDepth = 0 , p , depthDiff , startNode , endNode , sp , ep ;
if ( t [ START _CONTAINER ] == t [ END _CONTAINER ] )
return _traverseSameContainer ( how ) ;
for ( c = t [ END _CONTAINER ] , p = c . parentNode ; p ; c = p , p = p . parentNode ) {
if ( p == t [ START _CONTAINER ] )
return _traverseCommonStartContainer ( c , how ) ;
++ endContainerDepth ;
}
for ( c = t [ START _CONTAINER ] , p = c . parentNode ; p ; c = p , p = p . parentNode ) {
if ( p == t [ END _CONTAINER ] )
return _traverseCommonEndContainer ( c , how ) ;
++ startContainerDepth ;
}
depthDiff = startContainerDepth - endContainerDepth ;
startNode = t [ START _CONTAINER ] ;
while ( depthDiff > 0 ) {
startNode = startNode . parentNode ;
depthDiff -- ;
}
endNode = t [ END _CONTAINER ] ;
while ( depthDiff < 0 ) {
endNode = endNode . parentNode ;
depthDiff ++ ;
}
// ascend the ancestor hierarchy until we have a common parent.
for ( sp = startNode . parentNode , ep = endNode . parentNode ; sp != ep ; sp = sp . parentNode , ep = ep . parentNode ) {
startNode = sp ;
endNode = ep ;
}
return _traverseCommonAncestors ( startNode , endNode , how ) ;
} ;
function _traverseSameContainer ( how ) {
var frag , s , sub , n , cnt , sibling , xferNode , start , len ;
if ( how != DELETE )
frag = createDocumentFragment ( ) ;
// If selection is empty, just return the fragment
if ( t [ START _OFFSET ] == t [ END _OFFSET ] )
return frag ;
// Text node needs special case handling
if ( t [ START _CONTAINER ] . nodeType == 3 /* TEXT_NODE */ ) {
// get the substring
s = t [ START _CONTAINER ] . nodeValue ;
sub = s . substring ( t [ START _OFFSET ] , t [ END _OFFSET ] ) ;
// set the original text node to its new value
if ( how != CLONE ) {
n = t [ START _CONTAINER ] ;
start = t [ START _OFFSET ] ;
len = t [ END _OFFSET ] - t [ START _OFFSET ] ;
if ( start === 0 && len >= n . nodeValue . length - 1 ) {
n . parentNode . removeChild ( n ) ;
} else {
n . deleteData ( start , len ) ;
}
// Nothing is partially selected, so collapse to start point
t . collapse ( TRUE ) ;
}
if ( how == DELETE )
return ;
if ( sub . length > 0 ) {
frag . appendChild ( doc . createTextNode ( sub ) ) ;
}
return frag ;
}
// Copy nodes between the start/end offsets.
n = _getSelectedNode ( t [ START _CONTAINER ] , t [ START _OFFSET ] ) ;
cnt = t [ END _OFFSET ] - t [ START _OFFSET ] ;
while ( n && cnt > 0 ) {
sibling = n . nextSibling ;
xferNode = _traverseFullySelected ( n , how ) ;
if ( frag )
frag . appendChild ( xferNode ) ;
-- cnt ;
n = sibling ;
}
// Nothing is partially selected, so collapse to start point
if ( how != CLONE )
t . collapse ( TRUE ) ;
return frag ;
} ;
function _traverseCommonStartContainer ( endAncestor , how ) {
var frag , n , endIdx , cnt , sibling , xferNode ;
if ( how != DELETE )
frag = createDocumentFragment ( ) ;
n = _traverseRightBoundary ( endAncestor , how ) ;
if ( frag )
frag . appendChild ( n ) ;
endIdx = nodeIndex ( endAncestor ) ;
cnt = endIdx - t [ START _OFFSET ] ;
if ( cnt <= 0 ) {
// Collapse to just before the endAncestor, which
// is partially selected.
if ( how != CLONE ) {
t . setEndBefore ( endAncestor ) ;
t . collapse ( FALSE ) ;
}
return frag ;
}
n = endAncestor . previousSibling ;
while ( cnt > 0 ) {
sibling = n . previousSibling ;
xferNode = _traverseFullySelected ( n , how ) ;
if ( frag )
frag . insertBefore ( xferNode , frag . firstChild ) ;
-- cnt ;
n = sibling ;
}
// Collapse to just before the endAncestor, which
// is partially selected.
if ( how != CLONE ) {
t . setEndBefore ( endAncestor ) ;
t . collapse ( FALSE ) ;
}
return frag ;
} ;
function _traverseCommonEndContainer ( startAncestor , how ) {
var frag , startIdx , n , cnt , sibling , xferNode ;
if ( how != DELETE )
frag = createDocumentFragment ( ) ;
n = _traverseLeftBoundary ( startAncestor , how ) ;
if ( frag )
frag . appendChild ( n ) ;
startIdx = nodeIndex ( startAncestor ) ;
++ startIdx ; // Because we already traversed it
cnt = t [ END _OFFSET ] - startIdx ;
n = startAncestor . nextSibling ;
while ( n && cnt > 0 ) {
sibling = n . nextSibling ;
xferNode = _traverseFullySelected ( n , how ) ;
if ( frag )
frag . appendChild ( xferNode ) ;
-- cnt ;
n = sibling ;
}
if ( how != CLONE ) {
t . setStartAfter ( startAncestor ) ;
t . collapse ( TRUE ) ;
}
return frag ;
} ;
function _traverseCommonAncestors ( startAncestor , endAncestor , how ) {
var n , frag , commonParent , startOffset , endOffset , cnt , sibling , nextSibling ;
if ( how != DELETE )
frag = createDocumentFragment ( ) ;
n = _traverseLeftBoundary ( startAncestor , how ) ;
if ( frag )
frag . appendChild ( n ) ;
commonParent = startAncestor . parentNode ;
startOffset = nodeIndex ( startAncestor ) ;
endOffset = nodeIndex ( endAncestor ) ;
++ startOffset ;
cnt = endOffset - startOffset ;
sibling = startAncestor . nextSibling ;
while ( cnt > 0 ) {
nextSibling = sibling . nextSibling ;
n = _traverseFullySelected ( sibling , how ) ;
if ( frag )
frag . appendChild ( n ) ;
sibling = nextSibling ;
-- cnt ;
}
n = _traverseRightBoundary ( endAncestor , how ) ;
if ( frag )
frag . appendChild ( n ) ;
if ( how != CLONE ) {
t . setStartAfter ( startAncestor ) ;
t . collapse ( TRUE ) ;
}
return frag ;
} ;
function _traverseRightBoundary ( root , how ) {
var next = _getSelectedNode ( t [ END _CONTAINER ] , t [ END _OFFSET ] - 1 ) , parent , clonedParent , prevSibling , clonedChild , clonedGrandParent , isFullySelected = next != t [ END _CONTAINER ] ;
if ( next == root )
return _traverseNode ( next , isFullySelected , FALSE , how ) ;
parent = next . parentNode ;
clonedParent = _traverseNode ( parent , FALSE , FALSE , how ) ;
while ( parent ) {
while ( next ) {
prevSibling = next . previousSibling ;
clonedChild = _traverseNode ( next , isFullySelected , FALSE , how ) ;
if ( how != DELETE )
clonedParent . insertBefore ( clonedChild , clonedParent . firstChild ) ;
isFullySelected = TRUE ;
next = prevSibling ;
}
if ( parent == root )
return clonedParent ;
next = parent . previousSibling ;
parent = parent . parentNode ;
clonedGrandParent = _traverseNode ( parent , FALSE , FALSE , how ) ;
if ( how != DELETE )
clonedGrandParent . appendChild ( clonedParent ) ;
clonedParent = clonedGrandParent ;
}
} ;
function _traverseLeftBoundary ( root , how ) {
var next = _getSelectedNode ( t [ START _CONTAINER ] , t [ START _OFFSET ] ) , isFullySelected = next != t [ START _CONTAINER ] , parent , clonedParent , nextSibling , clonedChild , clonedGrandParent ;
if ( next == root )
return _traverseNode ( next , isFullySelected , TRUE , how ) ;
parent = next . parentNode ;
clonedParent = _traverseNode ( parent , FALSE , TRUE , how ) ;
while ( parent ) {
while ( next ) {
nextSibling = next . nextSibling ;
clonedChild = _traverseNode ( next , isFullySelected , TRUE , how ) ;
if ( how != DELETE )
clonedParent . appendChild ( clonedChild ) ;
isFullySelected = TRUE ;
next = nextSibling ;
}
if ( parent == root )
return clonedParent ;
next = parent . nextSibling ;
parent = parent . parentNode ;
clonedGrandParent = _traverseNode ( parent , FALSE , TRUE , how ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( how != DELETE )
clonedGrandParent . appendChild ( clonedParent ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
clonedParent = clonedGrandParent ;
}
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function _traverseNode ( n , isFullySelected , isLeft , how ) {
var txtValue , newNodeValue , oldNodeValue , offset , newNode ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( isFullySelected )
return _traverseFullySelected ( n , how ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( n . nodeType == 3 /* TEXT_NODE */ ) {
txtValue = n . nodeValue ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( isLeft ) {
offset = t [ START _OFFSET ] ;
newNodeValue = txtValue . substring ( offset ) ;
oldNodeValue = txtValue . substring ( 0 , offset ) ;
} else {
offset = t [ END _OFFSET ] ;
newNodeValue = txtValue . substring ( 0 , offset ) ;
oldNodeValue = txtValue . substring ( offset ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
if ( how != CLONE )
n . nodeValue = oldNodeValue ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( how == DELETE )
return ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
newNode = dom . clone ( n , FALSE ) ;
newNode . nodeValue = newNodeValue ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return newNode ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
if ( how == DELETE )
return ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return dom . clone ( n , FALSE ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function _traverseFullySelected ( n , how ) {
if ( how != DELETE )
return how == CLONE ? dom . clone ( n , TRUE ) : n ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
n . parentNode . removeChild ( n ) ;
} ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
ns . Range = Range ;
} ) ( tinymce . dom ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
( function ( ) {
function Selection ( selection ) {
var self = this , dom = selection . dom , TRUE = true , FALSE = false ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function getPosition ( rng , start ) {
var checkRng , startIndex = 0 , endIndex , inside ,
children , child , offset , index , position = - 1 , parent ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Setup test range, collapse it and get the parent
checkRng = rng . duplicate ( ) ;
checkRng . collapse ( start ) ;
parent = checkRng . parentElement ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Check if the selection is within the right document
if ( parent . ownerDocument !== selection . dom . doc )
return ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// IE will report non editable elements as it's parent so look for an editable one
while ( parent . contentEditable === "false" ) {
parent = parent . parentNode ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
// If parent doesn't have any children then return that we are inside the element
if ( ! parent . hasChildNodes ( ) ) {
return { node : parent , inside : 1 } ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Setup node list and endIndex
children = parent . children ;
endIndex = children . length - 1 ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Perform a binary search for the position
while ( startIndex <= endIndex ) {
index = Math . floor ( ( startIndex + endIndex ) / 2 ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Move selection to node and compare the ranges
child = children [ index ] ;
checkRng . moveToElementText ( child ) ;
position = checkRng . compareEndPoints ( start ? 'StartToStart' : 'EndToEnd' , rng ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Before/after or an exact match
if ( position > 0 ) {
endIndex = index - 1 ;
} else if ( position < 0 ) {
startIndex = index + 1 ;
} else {
return { node : child } ;
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Check if child position is before or we didn't find a position
if ( position < 0 ) {
// No element child was found use the parent element and the offset inside that
if ( ! child ) {
checkRng . moveToElementText ( parent ) ;
checkRng . collapse ( true ) ;
child = parent ;
inside = true ;
} else
checkRng . collapse ( false ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one
// We need to walk char by char since rng.text or rng.htmlText will trim line endings
offset = 0 ;
while ( checkRng . compareEndPoints ( start ? 'StartToStart' : 'StartToEnd' , rng ) !== 0 ) {
2012-05-16 02:18:46 +02:00
if ( checkRng . move ( 'character' , 1 ) === 0 || parent != checkRng . parentElement ( ) ) {
2012-03-21 04:47:31 +01:00
break ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
offset ++ ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
} else {
// Child position is after the selection endpoint
checkRng . collapse ( true ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one
offset = 0 ;
while ( checkRng . compareEndPoints ( start ? 'StartToStart' : 'StartToEnd' , rng ) !== 0 ) {
2012-05-16 02:18:46 +02:00
if ( checkRng . move ( 'character' , - 1 ) === 0 || parent != checkRng . parentElement ( ) ) {
2012-03-21 04:47:31 +01:00
break ;
}
offset ++ ;
}
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
return { node : child , position : position , offset : offset , inside : inside } ;
2010-07-02 01:48:07 +02:00
} ;
2012-03-21 04:47:31 +01:00
// Returns a W3C DOM compatible range object by using the IE Range API
function getRange ( ) {
var ieRange = selection . getRng ( ) , domRange = dom . createRng ( ) , element , collapsed , tmpRange , element2 , bookmark , fail ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// If selection is outside the current document just return an empty range
element = ieRange . item ? ieRange . item ( 0 ) : ieRange . parentElement ( ) ;
if ( element . ownerDocument != dom . doc )
return domRange ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
collapsed = selection . isCollapsed ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Handle control selection
if ( ieRange . item ) {
domRange . setStart ( element . parentNode , dom . nodeIndex ( element ) ) ;
domRange . setEnd ( domRange . startContainer , domRange . startOffset + 1 ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return domRange ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
function findEndPoint ( start ) {
var endPoint = getPosition ( ieRange , start ) , container , offset , textNodeOffset = 0 , sibling , undef , nodeValue ;
container = endPoint . node ;
offset = endPoint . offset ;
if ( endPoint . inside && ! container . hasChildNodes ( ) ) {
domRange [ start ? 'setStart' : 'setEnd' ] ( container , 0 ) ;
return ;
}
if ( offset === undef ) {
domRange [ start ? 'setStartBefore' : 'setEndAfter' ] ( container ) ;
return ;
}
if ( endPoint . position < 0 ) {
sibling = endPoint . inside ? container . firstChild : container . nextSibling ;
if ( ! sibling ) {
domRange [ start ? 'setStartAfter' : 'setEndAfter' ] ( container ) ;
return ;
}
if ( ! offset ) {
if ( sibling . nodeType == 3 )
domRange [ start ? 'setStart' : 'setEnd' ] ( sibling , 0 ) ;
else
domRange [ start ? 'setStartBefore' : 'setEndBefore' ] ( sibling ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Find the text node and offset
while ( sibling ) {
nodeValue = sibling . nodeValue ;
textNodeOffset += nodeValue . length ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// We are at or passed the position we where looking for
if ( textNodeOffset >= offset ) {
container = sibling ;
textNodeOffset -= offset ;
textNodeOffset = nodeValue . length - textNodeOffset ;
break ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
sibling = sibling . nextSibling ;
}
} else {
// Find the text node and offset
sibling = container . previousSibling ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! sibling )
return domRange [ start ? 'setStartBefore' : 'setEndBefore' ] ( container ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// If there isn't any text to loop then use the first position
if ( ! offset ) {
if ( container . nodeType == 3 )
domRange [ start ? 'setStart' : 'setEnd' ] ( sibling , container . nodeValue . length ) ;
else
domRange [ start ? 'setStartAfter' : 'setEndAfter' ] ( sibling ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
while ( sibling ) {
textNodeOffset += sibling . nodeValue . length ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// We are at or passed the position we where looking for
if ( textNodeOffset >= offset ) {
container = sibling ;
textNodeOffset -= offset ;
break ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
sibling = sibling . previousSibling ;
}
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
domRange [ start ? 'setStart' : 'setEnd' ] ( container , textNodeOffset ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
try {
// Find start point
findEndPoint ( true ) ;
// Find end point if needed
if ( ! collapsed )
findEndPoint ( ) ;
} catch ( ex ) {
// IE has a nasty bug where text nodes might throw "invalid argument" when you
// access the nodeValue or other properties of text nodes. This seems to happend when
// text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.
if ( ex . number == - 2147024809 ) {
// Get the current selection
bookmark = self . getBookmark ( 2 ) ;
// Get start element
tmpRange = ieRange . duplicate ( ) ;
tmpRange . collapse ( true ) ;
element = tmpRange . parentElement ( ) ;
// Get end element
if ( ! collapsed ) {
tmpRange = ieRange . duplicate ( ) ;
tmpRange . collapse ( false ) ;
element2 = tmpRange . parentElement ( ) ;
element2 . innerHTML = element2 . innerHTML ;
}
// Remove the broken elements
element . innerHTML = element . innerHTML ;
// Restore the selection
self . moveToBookmark ( bookmark ) ;
// Since the range has moved we need to re-get it
ieRange = selection . getRng ( ) ;
// Find start point
findEndPoint ( true ) ;
// Find end point if needed
if ( ! collapsed )
findEndPoint ( ) ;
} else
throw ex ; // Throw other errors
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
return domRange ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
this . getBookmark = function ( type ) {
var rng = selection . getRng ( ) , start , end , bookmark = { } ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function getIndexes ( node ) {
2012-05-16 02:18:46 +02:00
var parent , root , children , i , indexes = [ ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
parent = node . parentNode ;
root = dom . getRoot ( ) . parentNode ;
while ( parent != root && parent . nodeType !== 9 ) {
children = parent . children ;
i = children . length ;
while ( i -- ) {
if ( node === children [ i ] ) {
indexes . push ( i ) ;
2010-07-02 01:48:07 +02:00
break ;
}
}
2012-03-21 04:47:31 +01:00
node = parent ;
parent = parent . parentNode ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
return indexes ;
} ;
function getBookmarkEndPoint ( start ) {
var position ;
position = getPosition ( rng , start ) ;
if ( position ) {
return {
position : position . position ,
offset : position . offset ,
indexes : getIndexes ( position . node ) ,
inside : position . inside
} ;
}
} ;
// Non ubstructive bookmark
if ( type === 2 ) {
// Handle text selection
if ( ! rng . item ) {
bookmark . start = getBookmarkEndPoint ( true ) ;
if ( ! selection . isCollapsed ( ) )
bookmark . end = getBookmarkEndPoint ( ) ;
} else
bookmark . start = { ctrl : true , indexes : getIndexes ( rng . item ( 0 ) ) } ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
return bookmark ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
this . moveToBookmark = function ( bookmark ) {
var rng , body = dom . doc . body ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function resolveIndexes ( indexes ) {
var node , i , idx , children ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
node = dom . getRoot ( ) ;
for ( i = indexes . length - 1 ; i >= 0 ; i -- ) {
children = node . children ;
idx = indexes [ i ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( idx <= children . length - 1 ) {
node = children [ idx ] ;
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return node ;
} ;
function setBookmarkEndPoint ( start ) {
var endPoint = bookmark [ start ? 'start' : 'end' ] , moveLeft , moveRng , undef ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( endPoint ) {
moveLeft = endPoint . position > 0 ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
moveRng = body . createTextRange ( ) ;
moveRng . moveToElementText ( resolveIndexes ( endPoint . indexes ) ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
offset = endPoint . offset ;
if ( offset !== undef ) {
moveRng . collapse ( endPoint . inside || moveLeft ) ;
moveRng . moveStart ( 'character' , moveLeft ? - offset : offset ) ;
} else
moveRng . collapse ( start ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
rng . setEndPoint ( start ? 'StartToStart' : 'EndToStart' , moveRng ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( start )
rng . collapse ( true ) ;
}
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( bookmark . start ) {
if ( bookmark . start . ctrl ) {
rng = body . createControlRange ( ) ;
rng . addElement ( resolveIndexes ( bookmark . start . indexes ) ) ;
rng . select ( ) ;
} else {
rng = body . createTextRange ( ) ;
setBookmarkEndPoint ( true ) ;
setBookmarkEndPoint ( ) ;
rng . select ( ) ;
}
}
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
this . addRange = function ( rng ) {
var ieRng , ctrlRng , startContainer , startOffset , endContainer , endOffset , doc = selection . dom . doc , body = doc . body ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function setEndPoint ( start ) {
var container , offset , marker , tmpRng , nodes ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
marker = dom . create ( 'a' ) ;
container = start ? startContainer : endContainer ;
offset = start ? startOffset : endOffset ;
tmpRng = ieRng . duplicate ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( container == doc || container == doc . documentElement ) {
container = body ;
offset = 0 ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( container . nodeType == 3 ) {
container . parentNode . insertBefore ( marker , container ) ;
tmpRng . moveToElementText ( marker ) ;
tmpRng . moveStart ( 'character' , offset ) ;
dom . remove ( marker ) ;
ieRng . setEndPoint ( start ? 'StartToStart' : 'EndToEnd' , tmpRng ) ;
} else {
nodes = container . childNodes ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( nodes . length ) {
if ( offset >= nodes . length ) {
dom . insertAfter ( marker , nodes [ nodes . length - 1 ] ) ;
} else {
container . insertBefore ( marker , nodes [ offset ] ) ;
}
tmpRng . moveToElementText ( marker ) ;
} else if ( container . canHaveHTML ) {
// Empty node selection for example <div>|</div>
// Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open
container . innerHTML = '<span>\uFEFF</span>' ;
marker = container . firstChild ;
tmpRng . moveToElementText ( marker ) ;
tmpRng . collapse ( FALSE ) ; // Collapse false works better than true for some odd reason
}
ieRng . setEndPoint ( start ? 'StartToStart' : 'EndToEnd' , tmpRng ) ;
dom . remove ( marker ) ;
}
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
// Setup some shorter versions
startContainer = rng . startContainer ;
startOffset = rng . startOffset ;
endContainer = rng . endContainer ;
endOffset = rng . endOffset ;
ieRng = body . createTextRange ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// If single element selection then try making a control selection out of it
if ( startContainer == endContainer && startContainer . nodeType == 1 ) {
// Trick to place the caret inside an empty block element like <p></p>
2012-05-16 02:18:46 +02:00
if ( startOffset == endOffset && ! startContainer . hasChildNodes ( ) ) {
if ( startContainer . canHaveHTML ) {
startContainer . innerHTML = '<span>\uFEFF</span><span>\uFEFF</span>' ;
ieRng . moveToElementText ( startContainer . lastChild ) ;
ieRng . select ( ) ;
dom . doc . selection . clear ( ) ;
startContainer . innerHTML = '' ;
return ;
} else {
startOffset = dom . nodeIndex ( startContainer ) ;
startContainer = startContainer . parentNode ;
}
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( startOffset == endOffset - 1 ) {
try {
ctrlRng = body . createControlRange ( ) ;
ctrlRng . addElement ( startContainer . childNodes [ startOffset ] ) ;
ctrlRng . select ( ) ;
return ;
} catch ( ex ) {
// Ignore
}
}
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
// Set start/end point of selection
setEndPoint ( true ) ;
setEndPoint ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Select the new range and scroll it into view
ieRng . select ( ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Expose range method
this . getRangeAt = getRange ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Expose the selection object
tinymce . dom . TridentSelection = Selection ;
} ) ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
/ *
2012-05-16 02:18:46 +02:00
* Sizzle CSS Selector Engine
* Copyright , The Dojo Foundation
2012-03-21 04:47:31 +01:00
* Released under the MIT , BSD , and GPL Licenses .
* More information : http : //sizzlejs.com/
* /
( function ( ) {
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g ,
2012-05-16 02:18:46 +02:00
expando = "sizcache" ,
2012-03-21 04:47:31 +01:00
done = 0 ,
toString = Object . prototype . toString ,
hasDuplicate = false ,
2012-05-16 02:18:46 +02:00
baseHasDuplicate = true ,
rBackslash = /\\/g ,
rReturn = /\r\n/g ,
rNonWord = /\W/ ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Here we check if the JavaScript engine is using some sort of
// optimization where it does not always call our comparision
// function. If that is the case, discard the hasDuplicate value.
// Thus far that includes Google Chrome.
2012-05-16 02:18:46 +02:00
[ 0 , 0 ] . sort ( function ( ) {
2012-03-21 04:47:31 +01:00
baseHasDuplicate = false ;
return 0 ;
} ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
var Sizzle = function ( selector , context , results , seed ) {
2012-03-21 04:47:31 +01:00
results = results || [ ] ;
context = context || document ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
var origContext = context ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( context . nodeType !== 1 && context . nodeType !== 9 ) {
return [ ] ;
}
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
if ( ! selector || typeof selector !== "string" ) {
return results ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
var m , set , checkSet , extra , ret , cur , pop , i ,
prune = true ,
contextXML = Sizzle . isXML ( context ) ,
parts = [ ] ,
soFar = selector ;
2012-03-21 04:47:31 +01:00
// Reset the position of the chunker regexp (start from head)
do {
2012-05-16 02:18:46 +02:00
chunker . exec ( "" ) ;
m = chunker . exec ( soFar ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( m ) {
soFar = m [ 3 ] ;
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
parts . push ( m [ 1 ] ) ;
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
if ( m [ 2 ] ) {
extra = m [ 3 ] ;
break ;
}
}
} while ( m ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( parts . length > 1 && origPOS . exec ( selector ) ) {
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
if ( parts . length === 2 && Expr . relative [ parts [ 0 ] ] ) {
2012-05-16 02:18:46 +02:00
set = posProcess ( parts [ 0 ] + parts [ 1 ] , context , seed ) ;
2012-03-21 04:47:31 +01:00
} else {
set = Expr . relative [ parts [ 0 ] ] ?
[ context ] :
Sizzle ( parts . shift ( ) , context ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
while ( parts . length ) {
selector = parts . shift ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( Expr . relative [ selector ] ) {
selector += parts . shift ( ) ;
}
2012-05-16 02:18:46 +02:00
set = posProcess ( selector , set , seed ) ;
2012-03-21 04:47:31 +01:00
}
}
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
} else {
// Take a shortcut and set the context if the root selector is an ID
// (but not if it'll be faster if the inner selector is an ID)
if ( ! seed && parts . length > 1 && context . nodeType === 9 && ! contextXML &&
Expr . match . ID . test ( parts [ 0 ] ) && ! Expr . match . ID . test ( parts [ parts . length - 1 ] ) ) {
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
ret = Sizzle . find ( parts . shift ( ) , context , contextXML ) ;
2012-05-16 02:18:46 +02:00
context = ret . expr ?
Sizzle . filter ( ret . expr , ret . set ) [ 0 ] :
ret . set [ 0 ] ;
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( context ) {
ret = seed ?
{ expr : parts . pop ( ) , set : makeArray ( seed ) } :
Sizzle . find ( parts . pop ( ) , parts . length === 1 && ( parts [ 0 ] === "~" || parts [ 0 ] === "+" ) && context . parentNode ? context . parentNode : context , contextXML ) ;
2012-05-16 02:18:46 +02:00
set = ret . expr ?
Sizzle . filter ( ret . expr , ret . set ) :
ret . set ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( parts . length > 0 ) {
2012-05-16 02:18:46 +02:00
checkSet = makeArray ( set ) ;
2012-03-21 04:47:31 +01:00
} else {
prune = false ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
while ( parts . length ) {
cur = parts . pop ( ) ;
pop = cur ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! Expr . relative [ cur ] ) {
cur = "" ;
} else {
pop = parts . pop ( ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
if ( pop == null ) {
pop = context ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
Expr . relative [ cur ] ( checkSet , pop , contextXML ) ;
}
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
} else {
checkSet = parts = [ ] ;
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! checkSet ) {
checkSet = set ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! checkSet ) {
Sizzle . error ( cur || selector ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( toString . call ( checkSet ) === "[object Array]" ) {
if ( ! prune ) {
results . push . apply ( results , checkSet ) ;
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
} else if ( context && context . nodeType === 1 ) {
for ( i = 0 ; checkSet [ i ] != null ; i ++ ) {
if ( checkSet [ i ] && ( checkSet [ i ] === true || checkSet [ i ] . nodeType === 1 && Sizzle . contains ( context , checkSet [ i ] ) ) ) {
results . push ( set [ i ] ) ;
2010-07-02 01:48:07 +02:00
}
}
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
} else {
for ( i = 0 ; checkSet [ i ] != null ; i ++ ) {
if ( checkSet [ i ] && checkSet [ i ] . nodeType === 1 ) {
results . push ( set [ i ] ) ;
2010-07-02 01:48:07 +02:00
}
}
2012-03-21 04:47:31 +01:00
}
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
} else {
makeArray ( checkSet , results ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( extra ) {
Sizzle ( extra , origContext , results , seed ) ;
Sizzle . uniqueSort ( results ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return results ;
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
Sizzle . uniqueSort = function ( results ) {
2012-03-21 04:47:31 +01:00
if ( sortOrder ) {
hasDuplicate = baseHasDuplicate ;
2012-05-16 02:18:46 +02:00
results . sort ( sortOrder ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( hasDuplicate ) {
for ( var i = 1 ; i < results . length ; i ++ ) {
2012-05-16 02:18:46 +02:00
if ( results [ i ] === results [ i - 1 ] ) {
results . splice ( i -- , 1 ) ;
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return results ;
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
Sizzle . matches = function ( expr , set ) {
return Sizzle ( expr , null , null , set ) ;
} ;
Sizzle . matchesSelector = function ( node , expr ) {
return Sizzle ( expr , null , null , [ node ] ) . length > 0 ;
2012-03-21 04:47:31 +01:00
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
Sizzle . find = function ( expr , context , isXML ) {
var set , i , len , match , type , left ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! expr ) {
return [ ] ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
for ( i = 0 , len = Expr . order . length ; i < len ; i ++ ) {
type = Expr . order [ i ] ;
2012-03-21 04:47:31 +01:00
if ( ( match = Expr . leftMatch [ type ] . exec ( expr ) ) ) {
2012-05-16 02:18:46 +02:00
left = match [ 1 ] ;
match . splice ( 1 , 1 ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( left . substr ( left . length - 1 ) !== "\\" ) {
2012-05-16 02:18:46 +02:00
match [ 1 ] = ( match [ 1 ] || "" ) . replace ( rBackslash , "" ) ;
2012-03-21 04:47:31 +01:00
set = Expr . find [ type ] ( match , context , isXML ) ;
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
if ( set != null ) {
expr = expr . replace ( Expr . match [ type ] , "" ) ;
break ;
}
2010-07-02 01:48:07 +02:00
}
}
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! set ) {
2012-05-16 02:18:46 +02:00
set = typeof context . getElementsByTagName !== "undefined" ?
context . getElementsByTagName ( "*" ) :
[ ] ;
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
return { set : set , expr : expr } ;
2012-03-21 04:47:31 +01:00
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
Sizzle . filter = function ( expr , set , inplace , not ) {
var match , anyFound ,
type , found , item , filter , left ,
i , pass ,
old = expr ,
result = [ ] ,
curLoop = set ,
isXMLFilter = set && set [ 0 ] && Sizzle . isXML ( set [ 0 ] ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
while ( expr && set . length ) {
2012-05-16 02:18:46 +02:00
for ( type in Expr . filter ) {
2012-03-21 04:47:31 +01:00
if ( ( match = Expr . leftMatch [ type ] . exec ( expr ) ) != null && match [ 2 ] ) {
2012-05-16 02:18:46 +02:00
filter = Expr . filter [ type ] ;
left = match [ 1 ] ;
2012-03-21 04:47:31 +01:00
anyFound = false ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
match . splice ( 1 , 1 ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( left . substr ( left . length - 1 ) === "\\" ) {
continue ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( curLoop === result ) {
result = [ ] ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( Expr . preFilter [ type ] ) {
match = Expr . preFilter [ type ] ( match , curLoop , inplace , result , not , isXMLFilter ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! match ) {
anyFound = found = true ;
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
} else if ( match === true ) {
continue ;
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( match ) {
2012-05-16 02:18:46 +02:00
for ( i = 0 ; ( item = curLoop [ i ] ) != null ; i ++ ) {
2012-03-21 04:47:31 +01:00
if ( item ) {
found = filter ( item , match , i , curLoop ) ;
2012-05-16 02:18:46 +02:00
pass = not ^ found ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( inplace && found != null ) {
if ( pass ) {
anyFound = true ;
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
} else {
curLoop [ i ] = false ;
}
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
} else if ( pass ) {
result . push ( item ) ;
anyFound = true ;
}
}
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( found !== undefined ) {
if ( ! inplace ) {
curLoop = result ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
expr = expr . replace ( Expr . match [ type ] , "" ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! anyFound ) {
return [ ] ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
break ;
}
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Improper expression
if ( expr === old ) {
if ( anyFound == null ) {
Sizzle . error ( expr ) ;
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
} else {
break ;
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
old = expr ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return curLoop ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
Sizzle . error = function ( msg ) {
2012-05-16 02:18:46 +02:00
throw new Error ( "Syntax error, unrecognized expression: " + msg ) ;
} ;
var getText = Sizzle . getText = function ( elem ) {
var i , node ,
nodeType = elem . nodeType ,
ret = "" ;
if ( nodeType ) {
if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
// Use textContent || innerText for elements
if ( typeof elem . textContent === 'string' ) {
return elem . textContent ;
} else if ( typeof elem . innerText === 'string' ) {
// Replace IE's carriage returns
return elem . innerText . replace ( rReturn , '' ) ;
} else {
// Traverse it's children
for ( elem = elem . firstChild ; elem ; elem = elem . nextSibling ) {
ret += getText ( elem ) ;
}
}
} else if ( nodeType === 3 || nodeType === 4 ) {
return elem . nodeValue ;
}
} else {
// If no nodeType, this is expected to be an array
for ( i = 0 ; ( node = elem [ i ] ) ; i ++ ) {
// Do not traverse comment nodes
if ( node . nodeType !== 8 ) {
ret += getText ( node ) ;
}
}
}
return ret ;
2012-03-21 04:47:31 +01:00
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
var Expr = Sizzle . selectors = {
order : [ "ID" , "NAME" , "TAG" ] ,
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
match : {
ID : /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/ ,
CLASS : /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/ ,
NAME : /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/ ,
2012-05-16 02:18:46 +02:00
ATTR : /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/ ,
2012-03-21 04:47:31 +01:00
TAG : /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/ ,
2012-05-16 02:18:46 +02:00
CHILD : /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/ ,
2012-03-21 04:47:31 +01:00
POS : /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/ ,
PSEUDO : /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
} ,
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
leftMatch : { } ,
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
attrMap : {
"class" : "className" ,
"for" : "htmlFor"
} ,
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
attrHandle : {
2012-05-16 02:18:46 +02:00
href : function ( elem ) {
return elem . getAttribute ( "href" ) ;
} ,
type : function ( elem ) {
return elem . getAttribute ( "type" ) ;
2012-03-21 04:47:31 +01:00
}
} ,
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
relative : {
"+" : function ( checkSet , part ) {
var isPartStr = typeof part === "string" ,
2012-05-16 02:18:46 +02:00
isTag = isPartStr && ! rNonWord . test ( part ) ,
2012-03-21 04:47:31 +01:00
isPartStrNotTag = isPartStr && ! isTag ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( isTag ) {
part = part . toLowerCase ( ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
for ( var i = 0 , l = checkSet . length , elem ; i < l ; i ++ ) {
if ( ( elem = checkSet [ i ] ) ) {
while ( ( elem = elem . previousSibling ) && elem . nodeType !== 1 ) { }
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
checkSet [ i ] = isPartStrNotTag || elem && elem . nodeName . toLowerCase ( ) === part ?
elem || false :
elem === part ;
2010-07-02 01:48:07 +02:00
}
}
2012-03-21 04:47:31 +01:00
if ( isPartStrNotTag ) {
Sizzle . filter ( part , checkSet , true ) ;
}
} ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
">" : function ( checkSet , part ) {
var elem ,
isPartStr = typeof part === "string" ,
i = 0 ,
l = checkSet . length ;
if ( isPartStr && ! rNonWord . test ( part ) ) {
2012-03-21 04:47:31 +01:00
part = part . toLowerCase ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
for ( ; i < l ; i ++ ) {
elem = checkSet [ i ] ;
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
if ( elem ) {
var parent = elem . parentNode ;
checkSet [ i ] = parent . nodeName . toLowerCase ( ) === part ? parent : false ;
}
}
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
} else {
for ( ; i < l ; i ++ ) {
elem = checkSet [ i ] ;
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
if ( elem ) {
checkSet [ i ] = isPartStr ?
elem . parentNode :
elem . parentNode === part ;
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( isPartStr ) {
Sizzle . filter ( part , checkSet , true ) ;
}
}
2010-07-02 01:48:07 +02:00
} ,
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
"" : function ( checkSet , part , isXML ) {
2012-05-16 02:18:46 +02:00
var nodeCheck ,
doneName = done ++ ,
checkFn = dirCheck ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( typeof part === "string" && ! rNonWord . test ( part ) ) {
2012-03-21 04:47:31 +01:00
part = part . toLowerCase ( ) ;
nodeCheck = part ;
checkFn = dirNodeCheck ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
checkFn ( "parentNode" , part , doneName , checkSet , nodeCheck , isXML ) ;
2012-03-21 04:47:31 +01:00
} ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
"~" : function ( checkSet , part , isXML ) {
var nodeCheck ,
doneName = done ++ ,
checkFn = dirCheck ;
if ( typeof part === "string" && ! rNonWord . test ( part ) ) {
2012-03-21 04:47:31 +01:00
part = part . toLowerCase ( ) ;
nodeCheck = part ;
checkFn = dirNodeCheck ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
checkFn ( "previousSibling" , part , doneName , checkSet , nodeCheck , isXML ) ;
2012-03-21 04:47:31 +01:00
}
} ,
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
find : {
2012-05-16 02:18:46 +02:00
ID : function ( match , context , isXML ) {
2012-03-21 04:47:31 +01:00
if ( typeof context . getElementById !== "undefined" && ! isXML ) {
var m = context . getElementById ( match [ 1 ] ) ;
2012-05-16 02:18:46 +02:00
// Check parentNode to catch when Blackberry 4.6 returns
// nodes that are no longer in the document #6963
return m && m . parentNode ? [ m ] : [ ] ;
2012-03-21 04:47:31 +01:00
}
} ,
2012-05-16 02:18:46 +02:00
NAME : function ( match , context ) {
2012-03-21 04:47:31 +01:00
if ( typeof context . getElementsByName !== "undefined" ) {
2012-05-16 02:18:46 +02:00
var ret = [ ] ,
results = context . getElementsByName ( match [ 1 ] ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
for ( var i = 0 , l = results . length ; i < l ; i ++ ) {
if ( results [ i ] . getAttribute ( "name" ) === match [ 1 ] ) {
ret . push ( results [ i ] ) ;
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return ret . length === 0 ? null : ret ;
}
} ,
2012-05-16 02:18:46 +02:00
TAG : function ( match , context ) {
if ( typeof context . getElementsByTagName !== "undefined" ) {
return context . getElementsByTagName ( match [ 1 ] ) ;
}
2012-03-21 04:47:31 +01:00
}
} ,
preFilter : {
2012-05-16 02:18:46 +02:00
CLASS : function ( match , curLoop , inplace , result , not , isXML ) {
match = " " + match [ 1 ] . replace ( rBackslash , "" ) + " " ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( isXML ) {
return match ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
for ( var i = 0 , elem ; ( elem = curLoop [ i ] ) != null ; i ++ ) {
if ( elem ) {
2012-05-16 02:18:46 +02:00
if ( not ^ ( elem . className && ( " " + elem . className + " " ) . replace ( /[\t\n\r]/g , " " ) . indexOf ( match ) >= 0 ) ) {
2012-03-21 04:47:31 +01:00
if ( ! inplace ) {
result . push ( elem ) ;
}
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
} else if ( inplace ) {
curLoop [ i ] = false ;
}
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return false ;
} ,
2012-05-16 02:18:46 +02:00
ID : function ( match ) {
return match [ 1 ] . replace ( rBackslash , "" ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-05-16 02:18:46 +02:00
TAG : function ( match , curLoop ) {
return match [ 1 ] . replace ( rBackslash , "" ) . toLowerCase ( ) ;
2012-03-21 04:47:31 +01:00
} ,
2012-05-16 02:18:46 +02:00
CHILD : function ( match ) {
2012-03-21 04:47:31 +01:00
if ( match [ 1 ] === "nth" ) {
2012-05-16 02:18:46 +02:00
if ( ! match [ 2 ] ) {
Sizzle . error ( match [ 0 ] ) ;
}
match [ 2 ] = match [ 2 ] . replace ( /^\+|\s*/g , '' ) ;
2012-03-21 04:47:31 +01:00
// parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
2012-05-16 02:18:46 +02:00
var test = /(-?)(\d*)(?:n([+\-]?\d*))?/ . exec (
2012-03-21 04:47:31 +01:00
match [ 2 ] === "even" && "2n" || match [ 2 ] === "odd" && "2n+1" ||
! /\D/ . test ( match [ 2 ] ) && "0n+" + match [ 2 ] || match [ 2 ] ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// calculate the numbers (first)n+(last) including if they are negative
match [ 2 ] = ( test [ 1 ] + ( test [ 2 ] || 1 ) ) - 0 ;
match [ 3 ] = test [ 3 ] - 0 ;
}
2012-05-16 02:18:46 +02:00
else if ( match [ 2 ] ) {
Sizzle . error ( match [ 0 ] ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// TODO: Move to normal caching system
match [ 0 ] = done ++ ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return match ;
} ,
2012-05-16 02:18:46 +02:00
ATTR : function ( match , curLoop , inplace , result , not , isXML ) {
var name = match [ 1 ] = match [ 1 ] . replace ( rBackslash , "" ) ;
2012-03-21 04:47:31 +01:00
if ( ! isXML && Expr . attrMap [ name ] ) {
match [ 1 ] = Expr . attrMap [ name ] ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Handle if an un-quoted value was used
match [ 4 ] = ( match [ 4 ] || match [ 5 ] || "" ) . replace ( rBackslash , "" ) ;
2012-03-21 04:47:31 +01:00
if ( match [ 2 ] === "~=" ) {
match [ 4 ] = " " + match [ 4 ] + " " ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return match ;
} ,
2012-05-16 02:18:46 +02:00
PSEUDO : function ( match , curLoop , inplace , result , not ) {
2012-03-21 04:47:31 +01:00
if ( match [ 1 ] === "not" ) {
// If we're dealing with a complex expression, or a simple one
if ( ( chunker . exec ( match [ 3 ] ) || "" ) . length > 1 || /^\w/ . test ( match [ 3 ] ) ) {
match [ 3 ] = Sizzle ( match [ 3 ] , null , null , curLoop ) ;
2012-05-16 02:18:46 +02:00
2010-07-02 01:48:07 +02:00
} else {
2012-03-21 04:47:31 +01:00
var ret = Sizzle . filter ( match [ 3 ] , curLoop , inplace , true ^ not ) ;
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
if ( ! inplace ) {
result . push . apply ( result , ret ) ;
2010-07-02 01:48:07 +02:00
}
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
return false ;
2010-07-02 01:48:07 +02:00
}
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
} else if ( Expr . match . POS . test ( match [ 0 ] ) || Expr . match . CHILD . test ( match [ 0 ] ) ) {
return true ;
2010-07-02 01:48:07 +02:00
}
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
return match ;
2010-07-02 01:48:07 +02:00
} ,
2012-05-16 02:18:46 +02:00
POS : function ( match ) {
2012-03-21 04:47:31 +01:00
match . unshift ( true ) ;
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
return match ;
}
} ,
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
filters : {
2012-05-16 02:18:46 +02:00
enabled : function ( elem ) {
2012-03-21 04:47:31 +01:00
return elem . disabled === false && elem . type !== "hidden" ;
} ,
2012-05-16 02:18:46 +02:00
disabled : function ( elem ) {
2012-03-21 04:47:31 +01:00
return elem . disabled === true ;
} ,
2012-05-16 02:18:46 +02:00
checked : function ( elem ) {
2012-03-21 04:47:31 +01:00
return elem . checked === true ;
} ,
2012-05-16 02:18:46 +02:00
selected : function ( elem ) {
2012-03-21 04:47:31 +01:00
// Accessing this property makes selected-by-default
// options in Safari work properly
2012-05-16 02:18:46 +02:00
if ( elem . parentNode ) {
elem . parentNode . selectedIndex ;
}
2012-03-21 04:47:31 +01:00
return elem . selected === true ;
} ,
2012-05-16 02:18:46 +02:00
parent : function ( elem ) {
2012-03-21 04:47:31 +01:00
return ! ! elem . firstChild ;
} ,
2012-05-16 02:18:46 +02:00
empty : function ( elem ) {
2012-03-21 04:47:31 +01:00
return ! elem . firstChild ;
} ,
2012-05-16 02:18:46 +02:00
has : function ( elem , i , match ) {
2012-03-21 04:47:31 +01:00
return ! ! Sizzle ( match [ 3 ] , elem ) . length ;
} ,
2012-05-16 02:18:46 +02:00
header : function ( elem ) {
2012-03-21 04:47:31 +01:00
return ( /h\d/i ) . test ( elem . nodeName ) ;
} ,
2012-05-16 02:18:46 +02:00
text : function ( elem ) {
var attr = elem . getAttribute ( "type" ) , type = elem . type ;
// IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
// use getAttribute instead to test this case
return elem . nodeName . toLowerCase ( ) === "input" && "text" === type && ( attr === type || attr === null ) ;
} ,
radio : function ( elem ) {
return elem . nodeName . toLowerCase ( ) === "input" && "radio" === elem . type ;
2012-03-21 04:47:31 +01:00
} ,
2012-05-16 02:18:46 +02:00
checkbox : function ( elem ) {
return elem . nodeName . toLowerCase ( ) === "input" && "checkbox" === elem . type ;
2012-03-21 04:47:31 +01:00
} ,
2012-05-16 02:18:46 +02:00
file : function ( elem ) {
return elem . nodeName . toLowerCase ( ) === "input" && "file" === elem . type ;
2012-03-21 04:47:31 +01:00
} ,
2012-05-16 02:18:46 +02:00
password : function ( elem ) {
return elem . nodeName . toLowerCase ( ) === "input" && "password" === elem . type ;
2012-03-21 04:47:31 +01:00
} ,
2012-05-16 02:18:46 +02:00
submit : function ( elem ) {
var name = elem . nodeName . toLowerCase ( ) ;
return ( name === "input" || name === "button" ) && "submit" === elem . type ;
2012-03-21 04:47:31 +01:00
} ,
2012-05-16 02:18:46 +02:00
image : function ( elem ) {
return elem . nodeName . toLowerCase ( ) === "input" && "image" === elem . type ;
2012-03-21 04:47:31 +01:00
} ,
2012-05-16 02:18:46 +02:00
reset : function ( elem ) {
var name = elem . nodeName . toLowerCase ( ) ;
return ( name === "input" || name === "button" ) && "reset" === elem . type ;
2012-03-21 04:47:31 +01:00
} ,
2012-05-16 02:18:46 +02:00
button : function ( elem ) {
var name = elem . nodeName . toLowerCase ( ) ;
return name === "input" && "button" === elem . type || name === "button" ;
2012-03-21 04:47:31 +01:00
} ,
2012-05-16 02:18:46 +02:00
input : function ( elem ) {
return ( /input|select|textarea|button/i ) . test ( elem . nodeName ) ;
2012-03-21 04:47:31 +01:00
} ,
2012-05-16 02:18:46 +02:00
focus : function ( elem ) {
return elem === elem . ownerDocument . activeElement ;
2012-03-21 04:47:31 +01:00
}
} ,
setFilters : {
2012-05-16 02:18:46 +02:00
first : function ( elem , i ) {
2012-03-21 04:47:31 +01:00
return i === 0 ;
} ,
2012-05-16 02:18:46 +02:00
last : function ( elem , i , match , array ) {
2012-03-21 04:47:31 +01:00
return i === array . length - 1 ;
} ,
2012-05-16 02:18:46 +02:00
even : function ( elem , i ) {
2012-03-21 04:47:31 +01:00
return i % 2 === 0 ;
} ,
2012-05-16 02:18:46 +02:00
odd : function ( elem , i ) {
2012-03-21 04:47:31 +01:00
return i % 2 === 1 ;
} ,
2012-05-16 02:18:46 +02:00
lt : function ( elem , i , match ) {
2012-03-21 04:47:31 +01:00
return i < match [ 3 ] - 0 ;
} ,
2012-05-16 02:18:46 +02:00
gt : function ( elem , i , match ) {
2012-03-21 04:47:31 +01:00
return i > match [ 3 ] - 0 ;
} ,
2012-05-16 02:18:46 +02:00
nth : function ( elem , i , match ) {
2012-03-21 04:47:31 +01:00
return match [ 3 ] - 0 === i ;
} ,
2012-05-16 02:18:46 +02:00
eq : function ( elem , i , match ) {
2012-03-21 04:47:31 +01:00
return match [ 3 ] - 0 === i ;
}
} ,
filter : {
2012-05-16 02:18:46 +02:00
PSEUDO : function ( elem , match , i , array ) {
var name = match [ 1 ] ,
filter = Expr . filters [ name ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( filter ) {
return filter ( elem , i , match , array ) ;
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
} else if ( name === "contains" ) {
2012-05-16 02:18:46 +02:00
return ( elem . textContent || elem . innerText || getText ( [ elem ] ) || "" ) . indexOf ( match [ 3 ] ) >= 0 ;
2012-03-21 04:47:31 +01:00
} else if ( name === "not" ) {
var not = match [ 3 ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
for ( var j = 0 , l = not . length ; j < l ; j ++ ) {
if ( not [ j ] === elem ) {
return false ;
2010-07-02 01:48:07 +02:00
}
}
2012-03-21 04:47:31 +01:00
return true ;
2012-05-16 02:18:46 +02:00
2010-07-02 01:48:07 +02:00
} else {
2012-05-16 02:18:46 +02:00
Sizzle . error ( name ) ;
2010-07-02 01:48:07 +02:00
}
} ,
2012-05-16 02:18:46 +02:00
CHILD : function ( elem , match ) {
var first , last ,
doneName , parent , cache ,
count , diff ,
type = match [ 1 ] ,
node = elem ;
switch ( type ) {
case "only" :
case "first" :
while ( ( node = node . previousSibling ) ) {
if ( node . nodeType === 1 ) {
return false ;
2012-03-21 04:47:31 +01:00
}
}
2012-05-16 02:18:46 +02:00
if ( type === "first" ) {
return true ;
2012-03-21 04:47:31 +01:00
}
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
node = elem ;
2012-05-16 02:18:46 +02:00
/* falls through */
case "last" :
while ( ( node = node . nextSibling ) ) {
if ( node . nodeType === 1 ) {
return false ;
2012-03-21 04:47:31 +01:00
}
}
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
return true ;
2012-05-16 02:18:46 +02:00
case "nth" :
first = match [ 2 ] ;
last = match [ 3 ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( first === 1 && last === 0 ) {
return true ;
}
2012-05-16 02:18:46 +02:00
doneName = match [ 0 ] ;
parent = elem . parentNode ;
if ( parent && ( parent [ expando ] !== doneName || ! elem . nodeIndex ) ) {
count = 0 ;
2012-03-21 04:47:31 +01:00
for ( node = parent . firstChild ; node ; node = node . nextSibling ) {
if ( node . nodeType === 1 ) {
node . nodeIndex = ++ count ;
}
2012-05-16 02:18:46 +02:00
}
parent [ expando ] = doneName ;
2012-03-21 04:47:31 +01:00
}
2012-05-16 02:18:46 +02:00
diff = elem . nodeIndex - last ;
2012-03-21 04:47:31 +01:00
if ( first === 0 ) {
return diff === 0 ;
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
} else {
return ( diff % first === 0 && diff / first >= 0 ) ;
}
2010-07-02 01:48:07 +02:00
}
} ,
2012-05-16 02:18:46 +02:00
ID : function ( elem , match ) {
2012-03-21 04:47:31 +01:00
return elem . nodeType === 1 && elem . getAttribute ( "id" ) === match ;
} ,
2012-05-16 02:18:46 +02:00
TAG : function ( elem , match ) {
return ( match === "*" && elem . nodeType === 1 ) || ! ! elem . nodeName && elem . nodeName . toLowerCase ( ) === match ;
2012-03-21 04:47:31 +01:00
} ,
2012-05-16 02:18:46 +02:00
CLASS : function ( elem , match ) {
2012-03-21 04:47:31 +01:00
return ( " " + ( elem . className || elem . getAttribute ( "class" ) ) + " " )
. indexOf ( match ) > - 1 ;
} ,
2012-05-16 02:18:46 +02:00
ATTR : function ( elem , match ) {
2012-03-21 04:47:31 +01:00
var name = match [ 1 ] ,
2012-05-16 02:18:46 +02:00
result = Sizzle . attr ?
Sizzle . attr ( elem , name ) :
Expr . attrHandle [ name ] ?
2012-03-21 04:47:31 +01:00
Expr . attrHandle [ name ] ( elem ) :
elem [ name ] != null ?
elem [ name ] :
elem . getAttribute ( name ) ,
value = result + "" ,
type = match [ 2 ] ,
check = match [ 4 ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return result == null ?
type === "!=" :
2012-05-16 02:18:46 +02:00
! type && Sizzle . attr ?
result != null :
2012-03-21 04:47:31 +01:00
type === "=" ?
value === check :
type === "*=" ?
value . indexOf ( check ) >= 0 :
type === "~=" ?
( " " + value + " " ) . indexOf ( check ) >= 0 :
! check ?
value && result !== false :
type === "!=" ?
value !== check :
type === "^=" ?
value . indexOf ( check ) === 0 :
type === "$=" ?
value . substr ( value . length - check . length ) === check :
type === "|=" ?
value === check || value . substr ( 0 , check . length + 1 ) === check + "-" :
false ;
} ,
2012-05-16 02:18:46 +02:00
POS : function ( elem , match , i , array ) {
var name = match [ 2 ] ,
filter = Expr . setFilters [ name ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( filter ) {
return filter ( elem , i , match , array ) ;
}
}
}
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
var origPOS = Expr . match . POS ,
fescape = function ( all , num ) {
return "\\" + ( num - 0 + 1 ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
for ( var type in Expr . match ) {
Expr . match [ type ] = new RegExp ( Expr . match [ type ] . source + ( /(?![^\[]*\])(?![^\(]*\))/ . source ) ) ;
Expr . leftMatch [ type ] = new RegExp ( /(^(?:.|\r|\n)*?)/ . source + Expr . match [ type ] . source . replace ( /\\(\d+)/g , fescape ) ) ;
}
2012-05-16 02:18:46 +02:00
// Expose origPOS
// "global" as in regardless of relation to brackets/parens
Expr . match . globalPOS = origPOS ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
var makeArray = function ( array , results ) {
2012-03-21 04:47:31 +01:00
array = Array . prototype . slice . call ( array , 0 ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( results ) {
results . push . apply ( results , array ) ;
return results ;
}
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
return array ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Perform a simple check to determine if the browser is capable of
// converting a NodeList to an array using builtin methods.
// Also verifies that the returned array holds DOM nodes
// (which is not the case in the Blackberry browser)
try {
Array . prototype . slice . call ( document . documentElement . childNodes , 0 ) [ 0 ] . nodeType ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Provide a fallback method if it does not work
2012-05-16 02:18:46 +02:00
} catch ( e ) {
makeArray = function ( array , results ) {
var i = 0 ,
ret = results || [ ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( toString . call ( array ) === "[object Array]" ) {
Array . prototype . push . apply ( ret , array ) ;
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
} else {
if ( typeof array . length === "number" ) {
for ( var l = array . length ; i < l ; i ++ ) {
ret . push ( array [ i ] ) ;
}
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
} else {
for ( ; array [ i ] ; i ++ ) {
ret . push ( array [ i ] ) ;
}
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return ret ;
} ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
var sortOrder , siblingCheck ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( document . documentElement . compareDocumentPosition ) {
sortOrder = function ( a , b ) {
2012-05-16 02:18:46 +02:00
if ( a === b ) {
hasDuplicate = true ;
return 0 ;
}
2012-03-21 04:47:31 +01:00
if ( ! a . compareDocumentPosition || ! b . compareDocumentPosition ) {
return a . compareDocumentPosition ? - 1 : 1 ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
return a . compareDocumentPosition ( b ) & 4 ? - 1 : 1 ;
2012-03-21 04:47:31 +01:00
} ;
2012-05-16 02:18:46 +02:00
} else {
2012-03-21 04:47:31 +01:00
sortOrder = function ( a , b ) {
2012-05-16 02:18:46 +02:00
// The nodes are identical, we can exit early
if ( a === b ) {
hasDuplicate = true ;
return 0 ;
// Fallback to using sourceIndex (in IE) if it's available on both nodes
} else if ( a . sourceIndex && b . sourceIndex ) {
return a . sourceIndex - b . sourceIndex ;
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
var al , bl ,
ap = [ ] ,
bp = [ ] ,
aup = a . parentNode ,
bup = b . parentNode ,
cur = aup ;
// If the nodes are siblings (or identical) we can do a quick check
if ( aup === bup ) {
return siblingCheck ( a , b ) ;
// If no parents were found then the nodes are disconnected
} else if ( ! aup ) {
return - 1 ;
} else if ( ! bup ) {
return 1 ;
2012-03-21 04:47:31 +01:00
}
2012-05-16 02:18:46 +02:00
// Otherwise they're somewhere else in the tree so we need
// to build up a full list of the parentNodes for comparison
while ( cur ) {
ap . unshift ( cur ) ;
cur = cur . parentNode ;
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
cur = bup ;
while ( cur ) {
bp . unshift ( cur ) ;
cur = cur . parentNode ;
2012-03-21 04:47:31 +01:00
}
2012-05-16 02:18:46 +02:00
al = ap . length ;
bl = bp . length ;
// Start walking down the tree looking for a discrepancy
for ( var i = 0 ; i < al && i < bl ; i ++ ) {
if ( ap [ i ] !== bp [ i ] ) {
return siblingCheck ( ap [ i ] , bp [ i ] ) ;
}
}
// We ended someplace up the tree so do a sibling check
return i === al ?
siblingCheck ( a , bp [ i ] , - 1 ) :
siblingCheck ( ap [ i ] , b , 1 ) ;
2012-03-21 04:47:31 +01:00
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
siblingCheck = function ( a , b , ret ) {
if ( a === b ) {
return ret ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
var cur = a . nextSibling ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
while ( cur ) {
if ( cur === b ) {
return - 1 ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
cur = cur . nextSibling ;
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
return 1 ;
} ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Check to see if the browser returns elements by name when
// querying by getElementById (and provide a workaround)
( function ( ) {
// We're going to inject a fake input element with a specified name
var form = document . createElement ( "div" ) ,
2012-05-16 02:18:46 +02:00
id = "script" + ( new Date ( ) ) . getTime ( ) ,
root = document . documentElement ;
2012-03-21 04:47:31 +01:00
form . innerHTML = "<a name='" + id + "'/>" ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Inject it into the root element, check its status, and remove it quickly
root . insertBefore ( form , root . firstChild ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// The workaround has to do additional checks after a getElementById
// Which slows things down for other browsers (hence the branching)
if ( document . getElementById ( id ) ) {
2012-05-16 02:18:46 +02:00
Expr . find . ID = function ( match , context , isXML ) {
2012-03-21 04:47:31 +01:00
if ( typeof context . getElementById !== "undefined" && ! isXML ) {
var m = context . getElementById ( match [ 1 ] ) ;
2012-05-16 02:18:46 +02:00
return m ?
m . id === match [ 1 ] || typeof m . getAttributeNode !== "undefined" && m . getAttributeNode ( "id" ) . nodeValue === match [ 1 ] ?
[ m ] :
undefined :
[ ] ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
Expr . filter . ID = function ( elem , match ) {
2012-03-21 04:47:31 +01:00
var node = typeof elem . getAttributeNode !== "undefined" && elem . getAttributeNode ( "id" ) ;
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
return elem . nodeType === 1 && node && node . nodeValue === match ;
} ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
root . removeChild ( form ) ;
2012-05-16 02:18:46 +02:00
// release memory in IE
root = form = null ;
2012-03-21 04:47:31 +01:00
} ) ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
( function ( ) {
// Check to see if the browser returns only elements
// when doing getElementsByTagName("*")
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Create a fake element
var div = document . createElement ( "div" ) ;
div . appendChild ( document . createComment ( "" ) ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Make sure no comments are found
if ( div . getElementsByTagName ( "*" ) . length > 0 ) {
2012-05-16 02:18:46 +02:00
Expr . find . TAG = function ( match , context ) {
var results = context . getElementsByTagName ( match [ 1 ] ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Filter out possible comments
if ( match [ 1 ] === "*" ) {
var tmp = [ ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
for ( var i = 0 ; results [ i ] ; i ++ ) {
if ( results [ i ] . nodeType === 1 ) {
tmp . push ( results [ i ] ) ;
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
results = tmp ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return results ;
} ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Check to see if an attribute returns normalized href attributes
div . innerHTML = "<a href='#'></a>" ;
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
if ( div . firstChild && typeof div . firstChild . getAttribute !== "undefined" &&
div . firstChild . getAttribute ( "href" ) !== "#" ) {
2012-05-16 02:18:46 +02:00
Expr . attrHandle . href = function ( elem ) {
return elem . getAttribute ( "href" , 2 ) ;
2012-03-21 04:47:31 +01:00
} ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// release memory in IE
div = null ;
2012-03-21 04:47:31 +01:00
} ) ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( document . querySelectorAll ) {
( function ( ) {
2012-05-16 02:18:46 +02:00
var oldSizzle = Sizzle ,
div = document . createElement ( "div" ) ,
id = "__sizzle__" ;
2012-03-21 04:47:31 +01:00
div . innerHTML = "<p class='TEST'></p>" ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Safari can't handle uppercase or unicode characters when
// in quirks mode.
if ( div . querySelectorAll && div . querySelectorAll ( ".TEST" ) . length === 0 ) {
return ;
}
2012-05-16 02:18:46 +02:00
Sizzle = function ( query , context , extra , seed ) {
2012-03-21 04:47:31 +01:00
context = context || document ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Only use querySelectorAll on non-XML documents
// (ID selectors don't work in non-HTML documents)
2012-05-16 02:18:46 +02:00
if ( ! seed && ! Sizzle . isXML ( context ) ) {
// See if we find a selector to speed up
var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/ . exec ( query ) ;
if ( match && ( context . nodeType === 1 || context . nodeType === 9 ) ) {
// Speed-up: Sizzle("TAG")
if ( match [ 1 ] ) {
return makeArray ( context . getElementsByTagName ( query ) , extra ) ;
// Speed-up: Sizzle(".CLASS")
} else if ( match [ 2 ] && Expr . find . CLASS && context . getElementsByClassName ) {
return makeArray ( context . getElementsByClassName ( match [ 2 ] ) , extra ) ;
}
}
if ( context . nodeType === 9 ) {
// Speed-up: Sizzle("body")
// The body element only exists once, optimize finding it
if ( query === "body" && context . body ) {
return makeArray ( [ context . body ] , extra ) ;
// Speed-up: Sizzle("#ID")
} else if ( match && match [ 3 ] ) {
var elem = context . getElementById ( match [ 3 ] ) ;
// Check parentNode to catch when Blackberry 4.6 returns
// nodes that are no longer in the document #6963
if ( elem && elem . parentNode ) {
// Handle the case where IE and Opera return items
// by name instead of ID
if ( elem . id === match [ 3 ] ) {
return makeArray ( [ elem ] , extra ) ;
}
} else {
return makeArray ( [ ] , extra ) ;
}
}
try {
return makeArray ( context . querySelectorAll ( query ) , extra ) ;
} catch ( qsaError ) { }
// qSA works strangely on Element-rooted queries
// We can work around this by specifying an extra ID on the root
// and working up from there (Thanks to Andrew Dupont for the technique)
// IE 8 doesn't work on object elements
} else if ( context . nodeType === 1 && context . nodeName . toLowerCase ( ) !== "object" ) {
var oldContext = context ,
old = context . getAttribute ( "id" ) ,
nid = old || id ,
hasParent = context . parentNode ,
relativeHierarchySelector = /^\s*[+~]/ . test ( query ) ;
if ( ! old ) {
context . setAttribute ( "id" , nid ) ;
} else {
nid = nid . replace ( /'/g , "\\$&" ) ;
}
if ( relativeHierarchySelector && hasParent ) {
context = context . parentNode ;
}
try {
if ( ! relativeHierarchySelector || hasParent ) {
return makeArray ( context . querySelectorAll ( "[id='" + nid + "'] " + query ) , extra ) ;
}
} catch ( pseudoError ) {
} finally {
if ( ! old ) {
oldContext . removeAttribute ( "id" ) ;
}
}
}
2012-03-21 04:47:31 +01:00
}
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
return oldSizzle ( query , context , extra , seed ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
for ( var prop in oldSizzle ) {
Sizzle [ prop ] = oldSizzle [ prop ] ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// release memory in IE
div = null ;
2012-03-21 04:47:31 +01:00
} ) ( ) ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
( function ( ) {
var html = document . documentElement ,
matches = html . matchesSelector || html . mozMatchesSelector || html . webkitMatchesSelector || html . msMatchesSelector ;
if ( matches ) {
// Check to see if it's possible to do matchesSelector
// on a disconnected node (IE 9 fails this)
var disconnectedMatch = ! matches . call ( document . createElement ( "div" ) , "div" ) ,
pseudoWorks = false ;
try {
// This should fail with an exception
// Gecko does not error, returns false instead
matches . call ( document . documentElement , "[test!='']:sizzle" ) ;
} catch ( pseudoError ) {
pseudoWorks = true ;
}
Sizzle . matchesSelector = function ( node , expr ) {
// Make sure that attribute selectors are quoted
expr = expr . replace ( /\=\s*([^'"\]]*)\s*\]/g , "='$1']" ) ;
if ( ! Sizzle . isXML ( node ) ) {
try {
if ( pseudoWorks || ! Expr . match . PSEUDO . test ( expr ) && ! /!=/ . test ( expr ) ) {
var ret = matches . call ( node , expr ) ;
// IE 9's matchesSelector returns false on disconnected nodes
if ( ret || ! disconnectedMatch ||
// As well, disconnected nodes are said to be in a document
// fragment in IE 9, so check for that
node . document && node . document . nodeType !== 11 ) {
return ret ;
}
}
} catch ( e ) { }
}
return Sizzle ( expr , null , null , [ node ] ) . length > 0 ;
} ;
}
} ) ( ) ;
2012-03-21 04:47:31 +01:00
( function ( ) {
var div = document . createElement ( "div" ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
div . innerHTML = "<div class='test e'></div><div class='test'></div>" ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Opera can't find a second classname (in 9.6)
// Also, make sure that getElementsByClassName actually exists
if ( ! div . getElementsByClassName || div . getElementsByClassName ( "e" ) . length === 0 ) {
return ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Safari caches class attributes, doesn't catch changes (in 3.2)
div . lastChild . className = "e" ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( div . getElementsByClassName ( "e" ) . length === 1 ) {
return ;
}
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
Expr . order . splice ( 1 , 0 , "CLASS" ) ;
2012-05-16 02:18:46 +02:00
Expr . find . CLASS = function ( match , context , isXML ) {
2012-03-21 04:47:31 +01:00
if ( typeof context . getElementsByClassName !== "undefined" && ! isXML ) {
return context . getElementsByClassName ( match [ 1 ] ) ;
}
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// release memory in IE
div = null ;
2012-03-21 04:47:31 +01:00
} ) ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function dirNodeCheck ( dir , cur , doneName , checkSet , nodeCheck , isXML ) {
for ( var i = 0 , l = checkSet . length ; i < l ; i ++ ) {
var elem = checkSet [ i ] ;
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
if ( elem ) {
var match = false ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
elem = elem [ dir ] ;
2012-03-21 04:47:31 +01:00
while ( elem ) {
2012-05-16 02:18:46 +02:00
if ( elem [ expando ] === doneName ) {
2012-03-21 04:47:31 +01:00
match = checkSet [ elem . sizset ] ;
break ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( elem . nodeType === 1 && ! isXML ) {
2012-05-16 02:18:46 +02:00
elem [ expando ] = doneName ;
2012-03-21 04:47:31 +01:00
elem . sizset = i ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( elem . nodeName . toLowerCase ( ) === cur ) {
match = elem ;
break ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
elem = elem [ dir ] ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
checkSet [ i ] = match ;
}
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function dirCheck ( dir , cur , doneName , checkSet , nodeCheck , isXML ) {
for ( var i = 0 , l = checkSet . length ; i < l ; i ++ ) {
var elem = checkSet [ i ] ;
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
if ( elem ) {
var match = false ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
elem = elem [ dir ] ;
2012-03-21 04:47:31 +01:00
while ( elem ) {
2012-05-16 02:18:46 +02:00
if ( elem [ expando ] === doneName ) {
2012-03-21 04:47:31 +01:00
match = checkSet [ elem . sizset ] ;
break ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( elem . nodeType === 1 ) {
if ( ! isXML ) {
2012-05-16 02:18:46 +02:00
elem [ expando ] = doneName ;
2012-03-21 04:47:31 +01:00
elem . sizset = i ;
}
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
if ( typeof cur !== "string" ) {
if ( elem === cur ) {
match = true ;
break ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
} else if ( Sizzle . filter ( cur , [ elem ] ) . length > 0 ) {
match = elem ;
break ;
}
}
elem = elem [ dir ] ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
checkSet [ i ] = match ;
}
}
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( document . documentElement . contains ) {
Sizzle . contains = function ( a , b ) {
return a !== b && ( a . contains ? a . contains ( b ) : true ) ;
} ;
} else if ( document . documentElement . compareDocumentPosition ) {
Sizzle . contains = function ( a , b ) {
return ! ! ( a . compareDocumentPosition ( b ) & 16 ) ;
} ;
} else {
Sizzle . contains = function ( ) {
return false ;
} ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
Sizzle . isXML = function ( elem ) {
2012-03-21 04:47:31 +01:00
// documentElement is verified for cases where it doesn't yet exist
2012-05-16 02:18:46 +02:00
// (such as loading iframes in IE - #4833)
2012-03-21 04:47:31 +01:00
var documentElement = ( elem ? elem . ownerDocument || elem : 0 ) . documentElement ;
2012-05-16 02:18:46 +02:00
2012-03-21 04:47:31 +01:00
return documentElement ? documentElement . nodeName !== "HTML" : false ;
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
var posProcess = function ( selector , context , seed ) {
var match ,
tmpSet = [ ] ,
later = "" ,
2012-03-21 04:47:31 +01:00
root = context . nodeType ? [ context ] : context ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Position selectors must be done after the filter
// And so must :not(positional) so we move all PSEUDOs to the end
while ( ( match = Expr . match . PSEUDO . exec ( selector ) ) ) {
later += match [ 0 ] ;
selector = selector . replace ( Expr . match . PSEUDO , "" ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
selector = Expr . relative [ selector ] ? selector + "*" : selector ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
for ( var i = 0 , l = root . length ; i < l ; i ++ ) {
2012-05-16 02:18:46 +02:00
Sizzle ( selector , root [ i ] , tmpSet , seed ) ;
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return Sizzle . filter ( later , tmpSet ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// EXPOSE
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
window . tinymce . dom . Sizzle = Sizzle ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
} ) ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
( function ( tinymce ) {
tinymce . dom . Element = function ( id , settings ) {
var t = this , dom , el ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . settings = settings = settings || { } ;
t . id = id ;
t . dom = dom = settings . dom || tinymce . DOM ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Only IE leaks DOM references, this is a lot faster
if ( ! tinymce . isIE )
el = dom . get ( t . id ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinymce . each (
( 'getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' +
'setAttrib,setAttribs,getAttrib,addClass,removeClass,' +
'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' +
2012-05-16 02:18:46 +02:00
'isHidden,setHTML,get' ) . split ( /,/ ) , function ( k ) {
t [ k ] = function ( ) {
var a = [ id ] , i ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
for ( i = 0 ; i < arguments . length ; i ++ )
a . push ( arguments [ i ] ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
a = dom [ k ] . apply ( dom , a ) ;
t . update ( k ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
return a ;
} ;
}
) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinymce . extend ( t , {
on : function ( n , f , s ) {
return tinymce . dom . Event . add ( t . id , n , f , s ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
getXY : function ( ) {
return {
x : parseInt ( t . getStyle ( 'left' ) ) ,
y : parseInt ( t . getStyle ( 'top' ) )
} ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
getSize : function ( ) {
var n = dom . get ( t . id ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return {
w : parseInt ( t . getStyle ( 'width' ) || n . clientWidth ) ,
h : parseInt ( t . getStyle ( 'height' ) || n . clientHeight )
} ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
moveTo : function ( x , y ) {
t . setStyles ( { left : x , top : y } ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
moveBy : function ( x , y ) {
var p = t . getXY ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . moveTo ( p . x + x , p . y + y ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
resizeTo : function ( w , h ) {
t . setStyles ( { width : w , height : h } ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
resizeBy : function ( w , h ) {
var s = t . getSize ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . resizeTo ( s . w + w , s . h + h ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
update : function ( k ) {
var b ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( tinymce . isIE6 && settings . blocker ) {
k = k || '' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Ignore getters
if ( k . indexOf ( 'get' ) === 0 || k . indexOf ( 'has' ) === 0 || k . indexOf ( 'is' ) === 0 )
return ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Remove blocker on remove
if ( k == 'remove' ) {
dom . remove ( t . blocker ) ;
return ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! t . blocker ) {
t . blocker = dom . uniqueId ( ) ;
b = dom . add ( settings . container || dom . getRoot ( ) , 'iframe' , { id : t . blocker , style : 'position:absolute;' , frameBorder : 0 , src : 'javascript:""' } ) ;
dom . setStyle ( b , 'opacity' , 0 ) ;
} else
b = dom . get ( t . blocker ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
dom . setStyles ( b , {
left : t . getStyle ( 'left' , 1 ) ,
top : t . getStyle ( 'top' , 1 ) ,
width : t . getStyle ( 'width' , 1 ) ,
height : t . getStyle ( 'height' , 1 ) ,
display : t . getStyle ( 'display' , 1 ) ,
zIndex : parseInt ( t . getStyle ( 'zIndex' , 1 ) || 0 ) - 1
} ) ;
2010-07-02 01:48:07 +02:00
}
}
2012-03-21 04:47:31 +01:00
} ) ;
} ;
} ) ( tinymce ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
( function ( tinymce ) {
function trimNl ( s ) {
return s . replace ( /[\n\r]+/g , '' ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Shorten names
2012-05-16 02:18:46 +02:00
var is = tinymce . is , isIE = tinymce . isIE , each = tinymce . each , TreeWalker = tinymce . dom . TreeWalker ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinymce . create ( 'tinymce.dom.Selection' , {
Selection : function ( dom , win , serializer ) {
2010-07-02 01:48:07 +02:00
var t = this ;
2012-03-21 04:47:31 +01:00
t . dom = dom ;
t . win = win ;
t . serializer = serializer ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Add events
each ( [
'onBeforeSetContent' ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
'onBeforeGetContent' ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
'onSetContent' ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
'onGetContent'
] , function ( e ) {
t [ e ] = new tinymce . util . Dispatcher ( t ) ;
} ) ;
// No W3C Range support
if ( ! t . win . getSelection )
t . tridentSel = new tinymce . dom . TridentSelection ( t ) ;
if ( tinymce . isIE && dom . boxModel )
this . _fixIESelection ( ) ;
// Prevent leaks
tinymce . addUnload ( t . destroy , t ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
setCursorLocation : function ( node , offset ) {
var t = this ; var r = t . dom . createRng ( ) ;
r . setStart ( node , offset ) ;
r . setEnd ( node , offset ) ;
t . setRng ( r ) ;
t . collapse ( false ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
getContent : function ( s ) {
var t = this , r = t . getRng ( ) , e = t . dom . create ( "body" ) , se = t . getSel ( ) , wb , wa , n ;
s = s || { } ;
wb = wa = '' ;
s . get = true ;
s . format = s . format || 'html' ;
s . forced _root _block = '' ;
t . onBeforeGetContent . dispatch ( t , s ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( s . format == 'text' )
return t . isCollapsed ( ) ? '' : ( r . text || ( se . toString ? se . toString ( ) : '' ) ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( r . cloneContents ) {
n = r . cloneContents ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( n )
e . appendChild ( n ) ;
} else if ( is ( r . item ) || is ( r . htmlText ) ) {
// IE will produce invalid markup if elements are present that
// it doesn't understand like custom elements or HTML5 elements.
// Adding a BR in front of the contents and then remoiving it seems to fix it though.
e . innerHTML = '<br>' + ( r . item ? r . item ( 0 ) . outerHTML : r . htmlText ) ;
e . removeChild ( e . firstChild ) ;
} else
e . innerHTML = r . toString ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Keep whitespace before and after
if ( /^\s/ . test ( e . innerHTML ) )
wb = ' ' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( /\s+$/ . test ( e . innerHTML ) )
wa = ' ' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
s . getInner = true ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
s . content = t . isCollapsed ( ) ? '' : wb + t . serializer . serialize ( e , s ) + wa ;
t . onGetContent . dispatch ( t , s ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return s . content ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
setContent : function ( content , args ) {
var self = this , rng = self . getRng ( ) , caretNode , doc = self . win . document , frag , temp ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
args = args || { format : 'html' } ;
args . set = true ;
content = args . content = content ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Dispatch before set content event
if ( ! args . no _events )
self . onBeforeSetContent . dispatch ( self , args ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
content = args . content ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( rng . insertNode ) {
// Make caret marker since insertNode places the caret in the beginning of text after insert
content += '<span id="__caret">_</span>' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Delete and insert new node
if ( rng . startContainer == doc && rng . endContainer == doc ) {
// WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
doc . body . innerHTML = content ;
} else {
rng . deleteContents ( ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( doc . body . childNodes . length === 0 ) {
2012-03-21 04:47:31 +01:00
doc . body . innerHTML = content ;
} else {
// createContextualFragment doesn't exists in IE 9 DOMRanges
if ( rng . createContextualFragment ) {
rng . insertNode ( rng . createContextualFragment ( content ) ) ;
} else {
// Fake createContextualFragment call in IE 9
frag = doc . createDocumentFragment ( ) ;
temp = doc . createElement ( 'div' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
frag . appendChild ( temp ) ;
temp . outerHTML = content ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
rng . insertNode ( frag ) ;
}
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Move to caret marker
caretNode = self . dom . get ( '__caret' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Make sure we wrap it compleatly, Opera fails with a simple select call
rng = doc . createRange ( ) ;
rng . setStartBefore ( caretNode ) ;
rng . setEndBefore ( caretNode ) ;
self . setRng ( rng ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Remove the caret position
self . dom . remove ( '__caret' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
try {
self . setRng ( rng ) ;
} catch ( ex ) {
// Might fail on Opera for some odd reason
}
} else {
if ( rng . item ) {
// Delete content and get caret text selection
doc . execCommand ( 'Delete' , false , null ) ;
rng = self . getRng ( ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Explorer removes spaces from the beginning of pasted contents
if ( /^\s+/ . test ( content ) ) {
rng . pasteHTML ( '<span id="__mce_tmp">_</span>' + content ) ;
self . dom . remove ( '__mce_tmp' ) ;
} else
rng . pasteHTML ( content ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Dispatch set content event
if ( ! args . no _events )
self . onSetContent . dispatch ( self , args ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
getStart : function ( ) {
var rng = this . getRng ( ) , startElement , parentElement , checkRng , node ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( rng . duplicate || rng . item ) {
// Control selection, return first item
if ( rng . item )
return rng . item ( 0 ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Get start element
checkRng = rng . duplicate ( ) ;
checkRng . collapse ( 1 ) ;
startElement = checkRng . parentElement ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Check if range parent is inside the start element, then return the inner parent element
// This will fix issues when a single element is selected, IE would otherwise return the wrong start element
parentElement = node = rng . parentElement ( ) ;
while ( node = node . parentNode ) {
if ( node == startElement ) {
startElement = parentElement ;
break ;
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return startElement ;
} else {
startElement = rng . startContainer ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( startElement . nodeType == 1 && startElement . hasChildNodes ( ) )
startElement = startElement . childNodes [ Math . min ( startElement . childNodes . length - 1 , rng . startOffset ) ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( startElement && startElement . nodeType == 3 )
return startElement . parentNode ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return startElement ;
2010-07-02 01:48:07 +02:00
}
} ,
2012-03-21 04:47:31 +01:00
getEnd : function ( ) {
var t = this , r = t . getRng ( ) , e , eo ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( r . duplicate || r . item ) {
if ( r . item )
return r . item ( 0 ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
r = r . duplicate ( ) ;
r . collapse ( 0 ) ;
e = r . parentElement ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( e && e . nodeName == 'BODY' )
return e . lastChild || e ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return e ;
} else {
e = r . endContainer ;
eo = r . endOffset ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( e . nodeType == 1 && e . hasChildNodes ( ) )
e = e . childNodes [ eo > 0 ? eo - 1 : eo ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( e && e . nodeType == 3 )
return e . parentNode ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return e ;
}
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
getBookmark : function ( type , normalized ) {
var t = this , dom = t . dom , rng , rng2 , id , collapsed , name , element , index , chr = '\uFEFF' , styles ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function findIndex ( name , element ) {
var index = 0 ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
each ( dom . select ( name ) , function ( node , i ) {
if ( node == element )
index = i ;
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return index ;
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
function normalizeTableCellSelection ( rng ) {
function moveEndPoint ( start ) {
var container , offset , childNodes , prefix = start ? 'start' : 'end' ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
container = rng [ prefix + 'Container' ] ;
offset = rng [ prefix + 'Offset' ] ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( container . nodeType == 1 && container . nodeName == "TR" ) {
childNodes = container . childNodes ;
container = childNodes [ Math . min ( start ? offset : offset - 1 , childNodes . length - 1 ) ] ;
if ( container ) {
offset = start ? 0 : container . childNodes . length ;
rng [ 'set' + ( start ? 'Start' : 'End' ) ] ( container , offset ) ;
}
}
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
moveEndPoint ( true ) ;
moveEndPoint ( ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
return rng ;
} ;
function getLocation ( ) {
var rng = t . getRng ( true ) , root = dom . getRoot ( ) , bookmark = { } ;
function getPoint ( rng , start ) {
var container = rng [ start ? 'startContainer' : 'endContainer' ] ,
offset = rng [ start ? 'startOffset' : 'endOffset' ] , point = [ ] , node , childNodes , after = 0 ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( container . nodeType == 3 ) {
if ( normalized ) {
for ( node = container . previousSibling ; node && node . nodeType == 3 ; node = node . previousSibling )
offset += node . nodeValue . length ;
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
point . push ( offset ) ;
} else {
childNodes = container . childNodes ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( offset >= childNodes . length && childNodes . length ) {
after = 1 ;
offset = Math . max ( 0 , childNodes . length - 1 ) ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
point . push ( t . dom . nodeIndex ( childNodes [ offset ] , normalized ) + after ) ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
for ( ; container && container != root ; container = container . parentNode )
point . push ( t . dom . nodeIndex ( container , normalized ) ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
return point ;
2012-03-21 04:47:31 +01:00
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
bookmark . start = getPoint ( rng , true ) ;
if ( ! t . isCollapsed ( ) )
bookmark . end = getPoint ( rng ) ;
return bookmark ;
} ;
if ( type == 2 ) {
2012-03-21 04:47:31 +01:00
if ( t . tridentSel )
return t . tridentSel . getBookmark ( type ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return getLocation ( ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
// Handle simple range
if ( type )
return { rng : t . getRng ( ) } ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
rng = t . getRng ( ) ;
id = dom . uniqueId ( ) ;
collapsed = tinyMCE . activeEditor . selection . isCollapsed ( ) ;
styles = 'overflow:hidden;line-height:0px' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Explorer method
if ( rng . duplicate || rng . item ) {
// Text selection
if ( ! rng . item ) {
rng2 = rng . duplicate ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
try {
// Insert start marker
rng . collapse ( ) ;
rng . pasteHTML ( '<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Insert end marker
if ( ! collapsed ) {
rng2 . collapse ( false ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>
rng . moveToElementText ( rng2 . parentElement ( ) ) ;
2012-05-16 02:18:46 +02:00
if ( rng . compareEndPoints ( 'StartToEnd' , rng2 ) === 0 )
2012-03-21 04:47:31 +01:00
rng2 . move ( 'character' , - 1 ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
rng2 . pasteHTML ( '<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>' ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
} catch ( ex ) {
// IE might throw unspecified error so lets ignore it
return null ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
} else {
// Control selection
element = rng . item ( 0 ) ;
name = element . nodeName ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return { name : name , index : findIndex ( name , element ) } ;
}
} else {
element = t . getNode ( ) ;
name = element . nodeName ;
if ( name == 'IMG' )
return { name : name , index : findIndex ( name , element ) } ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// W3C method
2012-05-16 02:18:46 +02:00
rng2 = normalizeTableCellSelection ( rng . cloneRange ( ) ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Insert end marker
if ( ! collapsed ) {
rng2 . collapse ( false ) ;
rng2 . insertNode ( dom . create ( 'span' , { 'data-mce-type' : "bookmark" , id : id + '_end' , style : styles } , chr ) ) ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
rng = normalizeTableCellSelection ( rng ) ;
2012-03-21 04:47:31 +01:00
rng . collapse ( true ) ;
rng . insertNode ( dom . create ( 'span' , { 'data-mce-type' : "bookmark" , id : id + '_start' , style : styles } , chr ) ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
t . moveToBookmark ( { id : id , keep : 1 } ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return { id : id } ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
moveToBookmark : function ( bookmark ) {
var t = this , dom = t . dom , marker1 , marker2 , rng , root , startContainer , endContainer , startOffset , endOffset ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
function setEndPoint ( start ) {
var point = bookmark [ start ? 'start' : 'end' ] , i , node , offset , children ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( point ) {
offset = point [ 0 ] ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Find container node
for ( node = root , i = point . length - 1 ; i >= 1 ; i -- ) {
children = node . childNodes ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( point [ i ] > children . length - 1 )
return ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
node = children [ point [ i ] ] ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Move text offset to best suitable location
if ( node . nodeType === 3 )
offset = Math . min ( point [ 0 ] , node . nodeValue . length ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Move element offset to best suitable location
if ( node . nodeType === 1 )
offset = Math . min ( point [ 0 ] , node . childNodes . length ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Set offset within container node
if ( start )
rng . setStart ( node , offset ) ;
else
rng . setEnd ( node , offset ) ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
return true ;
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
function restoreEndPoint ( suffix ) {
var marker = dom . get ( bookmark . id + '_' + suffix ) , node , idx , next , prev , keep = bookmark . keep ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( marker ) {
node = marker . parentNode ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( suffix == 'start' ) {
if ( ! keep ) {
idx = dom . nodeIndex ( marker ) ;
} else {
node = marker . firstChild ;
idx = 1 ;
}
startContainer = endContainer = node ;
startOffset = endOffset = idx ;
} else {
if ( ! keep ) {
idx = dom . nodeIndex ( marker ) ;
} else {
node = marker . firstChild ;
idx = 1 ;
}
endContainer = node ;
endOffset = idx ;
2010-07-02 01:48:07 +02:00
}
2012-05-16 02:18:46 +02:00
if ( ! keep ) {
prev = marker . previousSibling ;
next = marker . nextSibling ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Remove all marker text nodes
each ( tinymce . grep ( marker . childNodes ) , function ( node ) {
if ( node . nodeType == 3 )
node . nodeValue = node . nodeValue . replace ( /\uFEFF/g , '' ) ;
} ) ;
// Remove marker but keep children if for example contents where inserted into the marker
// Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
while ( marker = dom . get ( bookmark . id + '_' + suffix ) )
dom . remove ( marker , 1 ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// If siblings are text nodes then merge them unless it's Opera since it some how removes the node
// and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact
if ( prev && next && prev . nodeType == next . nodeType && prev . nodeType == 3 && ! tinymce . isOpera ) {
idx = prev . nodeValue . length ;
prev . appendData ( next . nodeValue ) ;
dom . remove ( next ) ;
if ( suffix == 'start' ) {
startContainer = endContainer = prev ;
2012-03-21 04:47:31 +01:00
startOffset = endOffset = idx ;
} else {
2012-05-16 02:18:46 +02:00
endContainer = prev ;
2012-03-21 04:47:31 +01:00
endOffset = idx ;
}
2012-05-16 02:18:46 +02:00
}
}
}
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
function addBogus ( node ) {
// Adds a bogus BR element for empty block elements
if ( dom . isBlock ( node ) && ! node . innerHTML && ! isIE )
node . innerHTML = '<br data-mce-bogus="1" />' ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
return node ;
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( bookmark ) {
if ( bookmark . start ) {
rng = dom . createRng ( ) ;
root = dom . getRoot ( ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( t . tridentSel )
return t . tridentSel . moveToBookmark ( bookmark ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( setEndPoint ( true ) && setEndPoint ( ) ) {
t . setRng ( rng ) ;
}
} else if ( bookmark . id ) {
2012-03-21 04:47:31 +01:00
// Restore start/end points
restoreEndPoint ( 'start' ) ;
restoreEndPoint ( 'end' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( startContainer ) {
rng = dom . createRng ( ) ;
rng . setStart ( addBogus ( startContainer ) , startOffset ) ;
rng . setEnd ( addBogus ( endContainer ) , endOffset ) ;
t . setRng ( rng ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
} else if ( bookmark . name ) {
t . select ( dom . select ( bookmark . name ) [ bookmark . index ] ) ;
} else if ( bookmark . rng )
t . setRng ( bookmark . rng ) ;
}
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
select : function ( node , content ) {
var t = this , dom = t . dom , rng = dom . createRng ( ) , idx ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
function setPoint ( node , start ) {
var walker = new TreeWalker ( node , node ) ;
do {
// Text node
if ( node . nodeType == 3 && tinymce . trim ( node . nodeValue ) . length !== 0 ) {
if ( start )
rng . setStart ( node , 0 ) ;
else
rng . setEnd ( node , node . nodeValue . length ) ;
return ;
}
// BR element
if ( node . nodeName == 'BR' ) {
if ( start )
rng . setStartBefore ( node ) ;
else
rng . setEndBefore ( node ) ;
return ;
}
} while ( node = ( start ? walker . next ( ) : walker . prev ( ) ) ) ;
} ;
2012-03-21 04:47:31 +01:00
if ( node ) {
idx = dom . nodeIndex ( node ) ;
rng . setStart ( node . parentNode , idx ) ;
rng . setEnd ( node . parentNode , idx + 1 ) ;
// Find first/last text node or BR element
if ( content ) {
setPoint ( node , 1 ) ;
setPoint ( node ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . setRng ( rng ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
return node ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
isCollapsed : function ( ) {
var t = this , r = t . getRng ( ) , s = t . getSel ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! r || r . item )
return false ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( r . compareEndPoints )
return r . compareEndPoints ( 'StartToEnd' , r ) === 0 ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return ! s || r . collapsed ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
collapse : function ( to _start ) {
var self = this , rng = self . getRng ( ) , node ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Control range on IE
if ( rng . item ) {
node = rng . item ( 0 ) ;
rng = self . win . document . body . createTextRange ( ) ;
rng . moveToElementText ( node ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
rng . collapse ( ! ! to _start ) ;
self . setRng ( rng ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
getSel : function ( ) {
var t = this , w = this . win ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return w . getSelection ? w . getSelection ( ) : w . document . selection ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
getRng : function ( w3c ) {
2012-05-16 02:18:46 +02:00
var self = this , selection , rng , elm , doc = self . win . document ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Found tridentSel object then we need to use that one
2012-05-16 02:18:46 +02:00
if ( w3c && self . tridentSel ) {
return self . tridentSel . getRangeAt ( 0 ) ;
}
2012-03-21 04:47:31 +01:00
try {
2012-05-16 02:18:46 +02:00
if ( selection = self . getSel ( ) ) {
rng = selection . rangeCount > 0 ? selection . getRangeAt ( 0 ) : ( selection . createRange ? selection . createRange ( ) : doc . createRange ( ) ) ;
}
2012-03-21 04:47:31 +01:00
} catch ( ex ) {
// IE throws unspecified error here if TinyMCE is placed in a frame/iframe
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
2012-05-16 02:18:46 +02:00
if ( tinymce . isIE && rng && rng . setStart && doc . selection . createRange ( ) . item ) {
2012-03-21 04:47:31 +01:00
elm = doc . selection . createRange ( ) . item ( 0 ) ;
2012-05-16 02:18:46 +02:00
rng = doc . createRange ( ) ;
rng . setStartBefore ( elm ) ;
rng . setEndAfter ( elm ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
// No range found then create an empty one
// This can occur when the editor is placed in a hidden container element on Gecko
// Or on IE when there was an exception
2012-05-16 02:18:46 +02:00
if ( ! rng ) {
rng = doc . createRange ? doc . createRange ( ) : doc . body . createTextRange ( ) ;
}
// If range is at start of document then move it to start of body
if ( rng . setStart && rng . startContainer . nodeType === 9 && rng . collapsed ) {
elm = self . dom . getRoot ( ) ;
rng . setStart ( elm , 0 ) ;
rng . setEnd ( elm , 0 ) ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( self . selectedRange && self . explicitRange ) {
if ( rng . compareBoundaryPoints ( rng . START _TO _START , self . selectedRange ) === 0 && rng . compareBoundaryPoints ( rng . END _TO _END , self . selectedRange ) === 0 ) {
2012-03-21 04:47:31 +01:00
// Safari, Opera and Chrome only ever select text which causes the range to change.
// This lets us use the originally set range if the selection hasn't been changed by the user.
2012-05-16 02:18:46 +02:00
rng = self . explicitRange ;
2012-03-21 04:47:31 +01:00
} else {
2012-05-16 02:18:46 +02:00
self . selectedRange = null ;
self . explicitRange = null ;
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
}
2012-05-16 02:18:46 +02:00
return rng ;
2012-03-21 04:47:31 +01:00
} ,
2012-05-16 02:18:46 +02:00
setRng : function ( r , forward ) {
2012-03-21 04:47:31 +01:00
var s , t = this ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! t . tridentSel ) {
s = t . getSel ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( s ) {
t . explicitRange = r ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
try {
s . removeAllRanges ( ) ;
} catch ( ex ) {
// IE9 might throw errors here don't know why
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
s . addRange ( r ) ;
2012-05-16 02:18:46 +02:00
// Forward is set to false and we have an extend function
if ( forward === false && s . extend ) {
s . collapse ( r . endContainer , r . endOffset ) ;
s . extend ( r . startContainer , r . startOffset ) ;
}
2012-03-21 04:47:31 +01:00
// adding range isn't always successful so we need to check range count otherwise an exception can occur
t . selectedRange = s . rangeCount > 0 ? s . getRangeAt ( 0 ) : null ;
}
} else {
// Is W3C Range
if ( r . cloneRange ) {
try {
t . tridentSel . addRange ( r ) ;
return ;
} catch ( ex ) {
//IE9 throws an error here if called before selection is placed in the editor
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Is IE specific range
try {
r . select ( ) ;
} catch ( ex ) {
// Needed for some odd IE bug #1843306
}
}
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
setNode : function ( n ) {
var t = this ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . setContent ( t . dom . getOuterHTML ( n ) ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return n ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
getNode : function ( ) {
var t = this , rng = t . getRng ( ) , sel = t . getSel ( ) , elm , start = rng . startContainer , end = rng . endContainer ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
function skipEmptyTextNodes ( n , forwards ) {
var orig = n ;
while ( n && n . nodeType === 3 && n . length === 0 ) {
n = forwards ? n . nextSibling : n . previousSibling ;
}
return n || orig ;
} ;
2012-03-21 04:47:31 +01:00
// Range maybe lost after the editor is made visible again
if ( ! rng )
return t . dom . getRoot ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( rng . setStart ) {
elm = rng . commonAncestorContainer ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Handle selection a image or other control like element such as anchors
if ( ! rng . collapsed ) {
if ( rng . startContainer == rng . endContainer ) {
if ( rng . endOffset - rng . startOffset < 2 ) {
if ( rng . startContainer . hasChildNodes ( ) )
elm = rng . startContainer . childNodes [ rng . startOffset ] ;
}
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
// If the anchor node is a element instead of a text node then return this element
//if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
// return sel.anchorNode.childNodes[sel.anchorOffset];
// Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
// This happens when you double click an underlined word in FireFox.
if ( start . nodeType === 3 && end . nodeType === 3 ) {
if ( start . length === rng . startOffset ) {
start = skipEmptyTextNodes ( start . nextSibling , true ) ;
} else {
start = start . parentNode ;
}
if ( rng . endOffset === 0 ) {
end = skipEmptyTextNodes ( end . previousSibling , false ) ;
} else {
end = end . parentNode ;
}
if ( start && start === end )
return start ;
2010-07-02 01:48:07 +02:00
}
}
2012-03-21 04:47:31 +01:00
if ( elm && elm . nodeType == 3 )
return elm . parentNode ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return elm ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return rng . item ? rng . item ( 0 ) : rng . parentElement ( ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
getSelectedBlocks : function ( st , en ) {
var t = this , dom = t . dom , sb , eb , n , bl = [ ] ;
sb = dom . getParent ( st || t . getStart ( ) , dom . isBlock ) ;
eb = dom . getParent ( en || t . getEnd ( ) , dom . isBlock ) ;
if ( sb )
bl . push ( sb ) ;
if ( sb && eb && sb != eb ) {
n = sb ;
2012-05-16 02:18:46 +02:00
var walker = new TreeWalker ( sb , dom . getRoot ( ) ) ;
2012-03-21 04:47:31 +01:00
while ( ( n = walker . next ( ) ) && n != eb ) {
if ( dom . isBlock ( n ) )
bl . push ( n ) ;
}
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
if ( eb && sb != eb )
bl . push ( eb ) ;
return bl ;
2010-07-02 01:48:07 +02:00
} ,
2012-05-16 02:18:46 +02:00
isForward : function ( ) {
var dom = this . dom , sel = this . getSel ( ) , anchorRange , focusRange ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// No support for selection direction then always return true
if ( ! sel || sel . anchorNode == null || sel . focusNode == null ) {
return true ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
anchorRange = dom . createRng ( ) ;
anchorRange . setStart ( sel . anchorNode , sel . anchorOffset ) ;
anchorRange . collapse ( true ) ;
focusRange = dom . createRng ( ) ;
focusRange . setStart ( sel . focusNode , sel . focusOffset ) ;
focusRange . collapse ( true ) ;
return anchorRange . compareBoundaryPoints ( anchorRange . START _TO _START , focusRange ) <= 0 ;
} ,
normalize : function ( ) {
var self = this , rng , normalized , collapsed , node , sibling ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function normalizeEndPoint ( start ) {
2012-05-16 02:18:46 +02:00
var container , offset , walker , dom = self . dom , body = dom . getRoot ( ) , node , nonEmptyElementsMap , nodeName ;
function hasBrBeforeAfter ( node , left ) {
var walker = new TreeWalker ( node , dom . getParent ( node . parentNode , dom . isBlock ) || body ) ;
while ( node = walker [ left ? 'prev' : 'next' ] ( ) ) {
if ( node . nodeName === "BR" ) {
return true ;
}
}
} ;
// Walks the dom left/right to find a suitable text node to move the endpoint into
// It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
function findTextNodeRelative ( left , startNode ) {
var walker , lastInlineElement ;
startNode = startNode || container ;
walker = new TreeWalker ( startNode , dom . getParent ( startNode . parentNode , dom . isBlock ) || body ) ;
// Walk left until we hit a text node we can move to or a block/br/img
while ( node = walker [ left ? 'prev' : 'next' ] ( ) ) {
// Found text node that has a length
if ( node . nodeType === 3 && node . nodeValue . length > 0 ) {
container = node ;
offset = left ? node . nodeValue . length : 0 ;
normalized = true ;
return ;
}
// Break if we find a block or a BR/IMG/INPUT etc
if ( dom . isBlock ( node ) || nonEmptyElementsMap [ node . nodeName . toLowerCase ( ) ] ) {
return ;
}
lastInlineElement = node ;
}
// Only fetch the last inline element when in caret mode for now
if ( collapsed && lastInlineElement ) {
container = lastInlineElement ;
normalized = true ;
offset = 0 ;
}
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
container = rng [ ( start ? 'start' : 'end' ) + 'Container' ] ;
offset = rng [ ( start ? 'start' : 'end' ) + 'Offset' ] ;
2012-05-16 02:18:46 +02:00
nonEmptyElementsMap = dom . schema . getNonEmptyElements ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// If the container is a document move it to the body element
if ( container . nodeType === 9 ) {
2012-05-16 02:18:46 +02:00
container = dom . getRoot ( ) ;
2012-03-21 04:47:31 +01:00
offset = 0 ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// If the container is body try move it into the closest text node or position
if ( container === body ) {
2012-05-16 02:18:46 +02:00
// If start is before/after a image, table etc
if ( start ) {
node = container . childNodes [ offset > 0 ? offset - 1 : 0 ] ;
if ( node ) {
nodeName = node . nodeName . toLowerCase ( ) ;
if ( nonEmptyElementsMap [ node . nodeName ] || node . nodeName == "TABLE" ) {
return ;
}
}
}
2012-03-21 04:47:31 +01:00
// Resolve the index
if ( container . hasChildNodes ( ) ) {
container = container . childNodes [ Math . min ( ! start && offset > 0 ? offset - 1 : offset , container . childNodes . length - 1 ) ] ;
offset = 0 ;
// Don't walk into elements that doesn't have any child nodes like a IMG
2012-05-16 02:18:46 +02:00
if ( container . hasChildNodes ( ) && ! /TABLE/ . test ( container . nodeName ) ) {
2012-03-21 04:47:31 +01:00
// Walk the DOM to find a text node to place the caret at or a BR
node = container ;
2012-05-16 02:18:46 +02:00
walker = new TreeWalker ( container , body ) ;
2012-03-21 04:47:31 +01:00
do {
// Found a text node use that position
2012-05-16 02:18:46 +02:00
if ( node . nodeType === 3 && node . nodeValue . length > 0 ) {
offset = start ? 0 : node . nodeValue . length ;
2012-03-21 04:47:31 +01:00
container = node ;
normalized = true ;
break ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Found a BR/IMG element that we can place the caret before
2012-05-16 02:18:46 +02:00
if ( nonEmptyElementsMap [ node . nodeName . toLowerCase ( ) ] ) {
2012-03-21 04:47:31 +01:00
offset = dom . nodeIndex ( node ) ;
container = node . parentNode ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Put caret after image when moving the end point
if ( node . nodeName == "IMG" && ! start ) {
offset ++ ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
normalized = true ;
break ;
}
} while ( node = ( start ? walker . next ( ) : walker . prev ( ) ) ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
}
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Lean the caret to the left if possible
if ( collapsed ) {
// So this: <b>x</b><i>|x</i>
// Becomes: <b>x|</b><i>x</i>
// Seems that only gecko has issues with this
if ( container . nodeType === 3 && offset === 0 ) {
findTextNodeRelative ( true ) ;
}
// Lean left into empty inline elements when the caret is before a BR
// So this: <i><b></b><i>|<br></i>
// Becomes: <i><b>|</b><i><br></i>
// Seems that only gecko has issues with this
if ( container . nodeType === 1 ) {
node = container . childNodes [ offset ] ;
if ( node && node . nodeName === 'BR' && ! hasBrBeforeAfter ( node ) && ! hasBrBeforeAfter ( node , true ) ) {
findTextNodeRelative ( true , container . childNodes [ offset ] ) ;
}
}
}
// Lean the start of the selection right if possible
// So this: x[<b>x]</b>
// Becomes: x<b>[x]</b>
if ( start && ! collapsed && container . nodeType === 3 && offset === container . nodeValue . length ) {
findTextNodeRelative ( false ) ;
}
2012-03-21 04:47:31 +01:00
// Set endpoint if it was normalized
if ( normalized )
rng [ 'set' + ( start ? 'Start' : 'End' ) ] ( container , offset ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Normalize only on non IE browsers for now
if ( tinymce . isIE )
return ;
2012-03-21 04:47:31 +01:00
rng = self . getRng ( ) ;
2012-05-16 02:18:46 +02:00
collapsed = rng . collapsed ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Normalize the end points
normalizeEndPoint ( true ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( ! collapsed )
2012-03-21 04:47:31 +01:00
normalizeEndPoint ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Set the selection if it was normalized
if ( normalized ) {
2012-05-16 02:18:46 +02:00
// If it was collapsed then make sure it still is
if ( collapsed ) {
rng . collapse ( true ) ;
}
2012-03-21 04:47:31 +01:00
//console.log(self.dom.dumpRng(rng));
2012-05-16 02:18:46 +02:00
self . setRng ( rng , self . isForward ( ) ) ;
2012-03-21 04:47:31 +01:00
}
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
destroy : function ( s ) {
var t = this ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . win = null ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Manual destroy then remove unload handler
if ( ! s )
tinymce . removeUnload ( t . destroy ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
_fixIESelection : function ( ) {
var dom = this . dom , doc = dom . doc , body = doc . body , started , startRng , htmlElm ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Return range from point or null if it failed
function rngFromPoint ( x , y ) {
var rng = body . createTextRange ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
try {
rng . moveToPoint ( x , y ) ;
} catch ( ex ) {
// IE sometimes throws and exception, so lets just ignore it
rng = null ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return rng ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Fires while the selection is changing
function selectionChange ( e ) {
var pointRng ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Check if the button is down or not
if ( e . button ) {
// Create range from mouse position
pointRng = rngFromPoint ( e . x , e . y ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( pointRng ) {
// Check if pointRange is before/after selection then change the endPoint
if ( pointRng . compareEndPoints ( 'StartToStart' , startRng ) > 0 )
pointRng . setEndPoint ( 'StartToStart' , startRng ) ;
else
pointRng . setEndPoint ( 'EndToEnd' , startRng ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
pointRng . select ( ) ;
}
} else
endSelection ( ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Removes listeners
function endSelection ( ) {
var rng = doc . selection . createRange ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// If the range is collapsed then use the last start range
if ( startRng && ! rng . item && rng . compareEndPoints ( 'StartToEnd' , rng ) === 0 )
startRng . select ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
dom . unbind ( doc , 'mouseup' , endSelection ) ;
dom . unbind ( doc , 'mousemove' , selectionChange ) ;
startRng = started = 0 ;
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Make HTML element unselectable since we are going to handle selection by hand
doc . documentElement . unselectable = true ;
2012-03-21 04:47:31 +01:00
// Detect when user selects outside BODY
dom . bind ( doc , [ 'mousedown' , 'contextmenu' ] , function ( e ) {
if ( e . target . nodeName === 'HTML' ) {
if ( started )
endSelection ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
htmlElm = doc . documentElement ;
if ( htmlElm . scrollHeight > htmlElm . clientHeight )
return ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
started = 1 ;
// Setup start position
startRng = rngFromPoint ( e . x , e . y ) ;
if ( startRng ) {
// Listen for selection change events
dom . bind ( doc , 'mouseup' , endSelection ) ;
dom . bind ( doc , 'mousemove' , selectionChange ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
dom . win . focus ( ) ;
startRng . select ( ) ;
}
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
} ) ;
}
} ) ;
} ) ( tinymce ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
( function ( tinymce ) {
tinymce . dom . Serializer = function ( settings , dom , schema ) {
var onPreProcess , onPostProcess , isIE = tinymce . isIE , each = tinymce . each , htmlParser ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Support the old apply_source_formatting option
if ( ! settings . apply _source _formatting )
settings . indent = false ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Default DOM and Schema if they are undefined
dom = dom || tinymce . DOM ;
schema = schema || new tinymce . html . Schema ( settings ) ;
settings . entity _encoding = settings . entity _encoding || 'named' ;
settings . remove _trailing _brs = "remove_trailing_brs" in settings ? settings . remove _trailing _brs : true ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
onPreProcess = new tinymce . util . Dispatcher ( self ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
onPostProcess = new tinymce . util . Dispatcher ( self ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
htmlParser = new tinymce . html . DomParser ( settings , schema ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
htmlParser . addAttributeFilter ( 'src,href,style' , function ( nodes , name ) {
var i = nodes . length , node , value , internalName = 'data-mce-' + name , urlConverter = settings . url _converter , urlConverterScope = settings . url _converter _scope , undef ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
while ( i -- ) {
node = nodes [ i ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
value = node . attributes . map [ internalName ] ;
if ( value !== undef ) {
// Set external name to internal value and remove internal
node . attr ( name , value . length > 0 ? value : null ) ;
node . attr ( internalName , null ) ;
} else {
// No internal attribute found then convert the value we have in the DOM
value = node . attributes . map [ name ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( name === "style" )
value = dom . serializeStyle ( dom . parseStyle ( value ) , node . name ) ;
else if ( urlConverter )
value = urlConverter . call ( urlConverterScope , value , name , node . name ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
node . attr ( name , value . length > 0 ? value : null ) ;
}
}
} ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Remove internal classes mceItem<..> or mceSelected
2012-03-21 04:47:31 +01:00
htmlParser . addAttributeFilter ( 'class' , function ( nodes , name ) {
var i = nodes . length , node , value ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
while ( i -- ) {
node = nodes [ i ] ;
2012-05-16 02:18:46 +02:00
value = node . attr ( 'class' ) . replace ( /(?:^|\s)mce(Item\w+|Selected)(?!\S)/g , '' ) ;
2012-03-21 04:47:31 +01:00
node . attr ( 'class' , value . length > 0 ? value : null ) ;
}
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Remove bookmark elements
htmlParser . addAttributeFilter ( 'data-mce-type' , function ( nodes , name , args ) {
var i = nodes . length , node ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
while ( i -- ) {
node = nodes [ i ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( node . attributes . map [ 'data-mce-type' ] === 'bookmark' && ! args . cleanup )
node . remove ( ) ;
}
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Remove expando attributes
htmlParser . addAttributeFilter ( 'data-mce-expando' , function ( nodes , name , args ) {
var i = nodes . length ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
while ( i -- ) {
nodes [ i ] . attr ( name , null ) ;
}
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Force script into CDATA sections and remove the mce- prefix also add comments around styles
htmlParser . addNodeFilter ( 'script,style' , function ( nodes , name ) {
var i = nodes . length , node , value ;
function trim ( value ) {
return value . replace ( /(<!--\[CDATA\[|\]\]-->)/g , '\n' )
. replace ( /^[\r\n]*|[\r\n]*$/g , '' )
. replace ( /^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi , '' )
. replace ( /\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g , '' ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
while ( i -- ) {
node = nodes [ i ] ;
value = node . firstChild ? node . firstChild . value : '' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( name === "script" ) {
// Remove mce- prefix from script elements
node . attr ( 'type' , ( node . attr ( 'type' ) || 'text/javascript' ) . replace ( /^mce\-/ , '' ) ) ;
if ( value . length > 0 )
node . firstChild . value = '// <![CDATA[\n' + trim ( value ) + '\n// ]]>' ;
} else {
if ( value . length > 0 )
node . firstChild . value = '<!--\n' + trim ( value ) + '\n-->' ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
}
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Convert comments to cdata and handle protected comments
htmlParser . addNodeFilter ( '#comment' , function ( nodes , name ) {
var i = nodes . length , node ;
while ( i -- ) {
node = nodes [ i ] ;
if ( node . value . indexOf ( '[CDATA[' ) === 0 ) {
node . name = '#cdata' ;
node . type = 4 ;
node . value = node . value . replace ( /^\[CDATA\[|\]\]$/g , '' ) ;
} else if ( node . value . indexOf ( 'mce:protected ' ) === 0 ) {
node . name = "#text" ;
node . type = 3 ;
node . raw = true ;
node . value = unescape ( node . value ) . substr ( 14 ) ;
2010-07-02 01:48:07 +02:00
}
}
2012-03-21 04:47:31 +01:00
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
htmlParser . addNodeFilter ( 'xml:namespace,input' , function ( nodes , name ) {
var i = nodes . length , node ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
while ( i -- ) {
node = nodes [ i ] ;
if ( node . type === 7 )
node . remove ( ) ;
else if ( node . type === 1 ) {
if ( name === "input" && ! ( "type" in node . attributes . map ) )
node . attr ( 'type' , 'text' ) ;
}
}
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Fix list elements, TODO: Replace this later
if ( settings . fix _list _elements ) {
htmlParser . addNodeFilter ( 'ul,ol' , function ( nodes , name ) {
var i = nodes . length , node , parentNode ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
while ( i -- ) {
node = nodes [ i ] ;
parentNode = node . parent ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( parentNode . name === 'ul' || parentNode . name === 'ol' ) {
if ( node . prev && node . prev . name === 'li' ) {
node . prev . append ( node ) ;
}
}
}
} ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Remove internal data attributes
htmlParser . addAttributeFilter ( 'data-mce-src,data-mce-href,data-mce-style' , function ( nodes , name ) {
var i = nodes . length ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
while ( i -- ) {
nodes [ i ] . attr ( name , null ) ;
}
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Return public methods
return {
schema : schema ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
addNodeFilter : htmlParser . addNodeFilter ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
addAttributeFilter : htmlParser . addAttributeFilter ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
onPreProcess : onPreProcess ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
onPostProcess : onPostProcess ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
serialize : function ( node , args ) {
var impl , doc , oldDoc , htmlSerializer , content ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Explorer won't clone contents of script and style and the
// selected index of select elements are cleared on a clone operation.
if ( isIE && dom . select ( 'script,style,select,map' ) . length > 0 ) {
content = node . innerHTML ;
node = node . cloneNode ( false ) ;
dom . setHTML ( node , content ) ;
} else
node = node . cloneNode ( true ) ;
// Nodes needs to be attached to something in WebKit/Opera
// Older builds of Opera crashes if you attach the node to an document created dynamically
// and since we can't feature detect a crash we need to sniff the acutal build number
// This fix will make DOM ranges and make Sizzle happy!
impl = node . ownerDocument . implementation ;
if ( impl . createHTMLDocument ) {
// Create an empty HTML document
doc = impl . createHTMLDocument ( "" ) ;
// Add the element or it's children if it's a body element to the new document
each ( node . nodeName == 'BODY' ? node . childNodes : [ node ] , function ( node ) {
doc . body . appendChild ( doc . importNode ( node , true ) ) ;
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Grab first child or body element for serialization
if ( node . nodeName != 'BODY' )
node = doc . body . firstChild ;
else
node = doc . body ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// set the new document in DOMUtils so createElement etc works
oldDoc = dom . doc ;
dom . doc = doc ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
args = args || { } ;
args . format = args . format || 'html' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Pre process
if ( ! args . no _events ) {
args . node = node ;
onPreProcess . dispatch ( self , args ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Setup serializer
htmlSerializer = new tinymce . html . Serializer ( settings , schema ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Parse and serialize HTML
args . content = htmlSerializer . serialize (
2012-05-16 02:18:46 +02:00
htmlParser . parse ( tinymce . trim ( args . getInner ? node . innerHTML : dom . getOuterHTML ( node ) ) , args )
2012-03-21 04:47:31 +01:00
) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Replace all BOM characters for now until we can find a better solution
if ( ! args . cleanup )
args . content = args . content . replace ( /\uFEFF|\u200B/g , '' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Post process
if ( ! args . no _events )
onPostProcess . dispatch ( self , args ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Restore the old document if it was changed
if ( oldDoc )
dom . doc = oldDoc ;
args . node = null ;
return args . content ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
addRules : function ( rules ) {
schema . addValidElements ( rules ) ;
} ,
setRules : function ( rules ) {
schema . setValidElements ( rules ) ;
}
} ;
} ;
} ) ( tinymce ) ;
2010-07-02 01:48:07 +02:00
( function ( tinymce ) {
tinymce . dom . ScriptLoader = function ( settings ) {
var QUEUED = 0 ,
LOADING = 1 ,
LOADED = 2 ,
states = { } ,
queue = [ ] ,
scriptLoadedCallbacks = { } ,
queueLoadedCallbacks = [ ] ,
loading = 0 ,
2012-05-16 02:18:46 +02:00
undef ;
2010-07-02 01:48:07 +02:00
function loadScript ( url , callback ) {
var t = this , dom = tinymce . DOM , elm , uri , loc , id ;
// Execute callback when script is loaded
function done ( ) {
dom . remove ( id ) ;
if ( elm )
elm . onreadystatechange = elm . onload = elm = null ;
callback ( ) ;
} ;
2012-03-21 04:47:31 +01:00
function error ( ) {
// Report the error so it's easier for people to spot loading errors
if ( typeof ( console ) !== "undefined" && console . log )
console . log ( "Failed to load: " + url ) ;
// We can't mark it as done if there is a load error since
// A) We don't want to produce 404 errors on the server and
// B) the onerror event won't fire on all browsers.
// done();
} ;
2010-07-02 01:48:07 +02:00
id = dom . uniqueId ( ) ;
if ( tinymce . isIE6 ) {
uri = new tinymce . util . URI ( url ) ;
loc = location ;
// If script is from same domain and we
// use IE 6 then use XHR since it's more reliable
2012-03-21 04:47:31 +01:00
if ( uri . host == loc . hostname && uri . port == loc . port && ( uri . protocol + ':' ) == loc . protocol && uri . protocol . toLowerCase ( ) != 'file' ) {
2010-07-02 01:48:07 +02:00
tinymce . util . XHR . send ( {
url : tinymce . _addVer ( uri . getURI ( ) ) ,
success : function ( content ) {
// Create new temp script element
var script = dom . create ( 'script' , {
type : 'text/javascript'
} ) ;
// Evaluate script in global scope
script . text = content ;
document . getElementsByTagName ( 'head' ) [ 0 ] . appendChild ( script ) ;
dom . remove ( script ) ;
done ( ) ;
2012-03-21 04:47:31 +01:00
} ,
error : error
2010-07-02 01:48:07 +02:00
} ) ;
return ;
}
}
// Create new script element
elm = dom . create ( 'script' , {
id : id ,
type : 'text/javascript' ,
src : tinymce . _addVer ( url )
} ) ;
2012-03-21 04:47:31 +01:00
// Add onload listener for non IE browsers since IE9
// fires onload event before the script is parsed and executed
if ( ! tinymce . isIE )
elm . onload = done ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Add onerror event will get fired on some browsers but not all of them
elm . onerror = error ;
// Opera 9.60 doesn't seem to fire the onreadystate event at correctly
if ( ! tinymce . isOpera ) {
elm . onreadystatechange = function ( ) {
var state = elm . readyState ;
// Loaded state is passed on IE 6 however there
// are known issues with this method but we can't use
// XHR in a cross domain loading
if ( state == 'complete' || state == 'loaded' )
done ( ) ;
} ;
}
2010-07-02 01:48:07 +02:00
// Most browsers support this feature so we report errors
// for those at least to help users track their missing plugins etc
// todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
/ * e l m . o n e r r o r = f u n c t i o n ( ) {
alert ( 'Failed to load: ' + url ) ;
} ; * /
// Add script to document
( document . getElementsByTagName ( 'head' ) [ 0 ] || document . body ) . appendChild ( elm ) ;
} ;
this . isDone = function ( url ) {
return states [ url ] == LOADED ;
} ;
this . markDone = function ( url ) {
states [ url ] = LOADED ;
} ;
this . add = this . load = function ( url , callback , scope ) {
var item , state = states [ url ] ;
// Add url to load queue
2012-05-16 02:18:46 +02:00
if ( state == undef ) {
2010-07-02 01:48:07 +02:00
queue . push ( url ) ;
states [ url ] = QUEUED ;
}
if ( callback ) {
// Store away callback for later execution
if ( ! scriptLoadedCallbacks [ url ] )
scriptLoadedCallbacks [ url ] = [ ] ;
scriptLoadedCallbacks [ url ] . push ( {
func : callback ,
scope : scope || this
} ) ;
}
} ;
this . loadQueue = function ( callback , scope ) {
this . loadScripts ( queue , callback , scope ) ;
} ;
this . loadScripts = function ( scripts , callback , scope ) {
var loadScripts ;
function execScriptLoadedCallbacks ( url ) {
// Execute URL callback functions
tinymce . each ( scriptLoadedCallbacks [ url ] , function ( callback ) {
callback . func . call ( callback . scope ) ;
} ) ;
2012-05-16 02:18:46 +02:00
scriptLoadedCallbacks [ url ] = undef ;
2010-07-02 01:48:07 +02:00
} ;
queueLoadedCallbacks . push ( {
func : callback ,
scope : scope || this
} ) ;
loadScripts = function ( ) {
var loadingScripts = tinymce . grep ( scripts ) ;
// Current scripts has been handled
scripts . length = 0 ;
// Load scripts that needs to be loaded
tinymce . each ( loadingScripts , function ( url ) {
// Script is already loaded then execute script callbacks directly
if ( states [ url ] == LOADED ) {
execScriptLoadedCallbacks ( url ) ;
return ;
}
// Is script not loading then start loading it
if ( states [ url ] != LOADING ) {
states [ url ] = LOADING ;
loading ++ ;
loadScript ( url , function ( ) {
states [ url ] = LOADED ;
loading -- ;
execScriptLoadedCallbacks ( url ) ;
// Load more scripts if they where added by the recently loaded script
loadScripts ( ) ;
} ) ;
}
} ) ;
// No scripts are currently loading then execute all pending queue loaded callbacks
if ( ! loading ) {
tinymce . each ( queueLoadedCallbacks , function ( callback ) {
callback . func . call ( callback . scope ) ;
} ) ;
queueLoadedCallbacks . length = 0 ;
}
} ;
loadScripts ( ) ;
} ;
} ;
// Global script loader
tinymce . ScriptLoader = new tinymce . dom . ScriptLoader ( ) ;
} ) ( tinymce ) ;
( function ( tinymce ) {
tinymce . dom . RangeUtils = function ( dom ) {
var INVISIBLE _CHAR = '\uFEFF' ;
this . walk = function ( rng , callback ) {
var startContainer = rng . startContainer ,
startOffset = rng . startOffset ,
endContainer = rng . endContainer ,
endOffset = rng . endOffset ,
ancestor , startPoint ,
endPoint , node , parent , siblings , nodes ;
// Handle table cell selection the table plugin enables
// you to fake select table cells and perform formatting actions on them
nodes = dom . select ( 'td.mceSelected,th.mceSelected' ) ;
if ( nodes . length > 0 ) {
tinymce . each ( nodes , function ( node ) {
callback ( [ node ] ) ;
} ) ;
return ;
}
2012-03-21 04:47:31 +01:00
function exclude ( nodes ) {
var node ;
// First node is excluded
node = nodes [ 0 ] ;
if ( node . nodeType === 3 && node === startContainer && startOffset >= node . nodeValue . length ) {
nodes . splice ( 0 , 1 ) ;
}
// Last node is excluded
node = nodes [ nodes . length - 1 ] ;
if ( endOffset === 0 && nodes . length > 0 && node === endContainer && node . nodeType === 3 ) {
nodes . splice ( nodes . length - 1 , 1 ) ;
}
return nodes ;
} ;
2010-07-02 01:48:07 +02:00
function collectSiblings ( node , name , end _node ) {
var siblings = [ ] ;
for ( ; node && node != end _node ; node = node [ name ] )
siblings . push ( node ) ;
return siblings ;
} ;
function findEndPoint ( node , root ) {
do {
if ( node . parentNode == root )
return node ;
node = node . parentNode ;
} while ( node ) ;
} ;
function walkBoundary ( start _node , end _node , next ) {
var siblingName = next ? 'nextSibling' : 'previousSibling' ;
for ( node = start _node , parent = node . parentNode ; node && node != end _node ; node = parent ) {
parent = node . parentNode ;
siblings = collectSiblings ( node == start _node ? node : node [ siblingName ] , siblingName ) ;
if ( siblings . length ) {
if ( ! next )
siblings . reverse ( ) ;
2012-03-21 04:47:31 +01:00
callback ( exclude ( siblings ) ) ;
2010-07-02 01:48:07 +02:00
}
}
} ;
// If index based start position then resolve it
if ( startContainer . nodeType == 1 && startContainer . hasChildNodes ( ) )
startContainer = startContainer . childNodes [ startOffset ] ;
// If index based end position then resolve it
if ( endContainer . nodeType == 1 && endContainer . hasChildNodes ( ) )
2012-03-21 04:47:31 +01:00
endContainer = endContainer . childNodes [ Math . min ( endOffset - 1 , endContainer . childNodes . length - 1 ) ] ;
2010-07-02 01:48:07 +02:00
// Same container
if ( startContainer == endContainer )
2012-03-21 04:47:31 +01:00
return callback ( exclude ( [ startContainer ] ) ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Find common ancestor and end points
ancestor = dom . findCommonAncestor ( startContainer , endContainer ) ;
2010-07-02 01:48:07 +02:00
// Process left side
for ( node = startContainer ; node ; node = node . parentNode ) {
2012-03-21 04:47:31 +01:00
if ( node === endContainer )
2010-07-02 01:48:07 +02:00
return walkBoundary ( startContainer , ancestor , true ) ;
2012-03-21 04:47:31 +01:00
if ( node === ancestor )
2010-07-02 01:48:07 +02:00
break ;
}
// Process right side
for ( node = endContainer ; node ; node = node . parentNode ) {
2012-03-21 04:47:31 +01:00
if ( node === startContainer )
2010-07-02 01:48:07 +02:00
return walkBoundary ( endContainer , ancestor ) ;
2012-03-21 04:47:31 +01:00
if ( node === ancestor )
2010-07-02 01:48:07 +02:00
break ;
}
// Find start/end point
startPoint = findEndPoint ( startContainer , ancestor ) || startContainer ;
endPoint = findEndPoint ( endContainer , ancestor ) || endContainer ;
// Walk left leaf
walkBoundary ( startContainer , startPoint , true ) ;
// Walk the middle from start to end point
siblings = collectSiblings (
startPoint == startContainer ? startPoint : startPoint . nextSibling ,
'nextSibling' ,
endPoint == endContainer ? endPoint . nextSibling : endPoint
) ;
if ( siblings . length )
2012-03-21 04:47:31 +01:00
callback ( exclude ( siblings ) ) ;
2010-07-02 01:48:07 +02:00
// Walk right leaf
walkBoundary ( endContainer , endPoint ) ;
} ;
2012-03-21 04:47:31 +01:00
this . split = function ( rng ) {
2010-07-02 01:48:07 +02:00
var startContainer = rng . startContainer ,
startOffset = rng . startOffset ,
endContainer = rng . endContainer ,
endOffset = rng . endOffset ;
function splitText ( node , offset ) {
2012-03-21 04:47:31 +01:00
return node . splitText ( offset ) ;
2010-07-02 01:48:07 +02:00
} ;
// Handle single text node
2012-03-21 04:47:31 +01:00
if ( startContainer == endContainer && startContainer . nodeType == 3 ) {
if ( startOffset > 0 && startOffset < startContainer . nodeValue . length ) {
endContainer = splitText ( startContainer , startOffset ) ;
startContainer = endContainer . previousSibling ;
if ( endOffset > startOffset ) {
endOffset = endOffset - startOffset ;
startContainer = endContainer = splitText ( endContainer , endOffset ) . previousSibling ;
endOffset = endContainer . nodeValue . length ;
startOffset = 0 ;
} else {
endOffset = 0 ;
}
2010-07-02 01:48:07 +02:00
}
} else {
// Split startContainer text node if needed
2012-03-21 04:47:31 +01:00
if ( startContainer . nodeType == 3 && startOffset > 0 && startOffset < startContainer . nodeValue . length ) {
2010-07-02 01:48:07 +02:00
startContainer = splitText ( startContainer , startOffset ) ;
startOffset = 0 ;
}
// Split endContainer text node if needed
2012-03-21 04:47:31 +01:00
if ( endContainer . nodeType == 3 && endOffset > 0 && endOffset < endContainer . nodeValue . length ) {
2010-07-02 01:48:07 +02:00
endContainer = splitText ( endContainer , endOffset ) . previousSibling ;
endOffset = endContainer . nodeValue . length ;
}
}
return {
startContainer : startContainer ,
startOffset : startOffset ,
endContainer : endContainer ,
endOffset : endOffset
} ;
} ;
2012-03-21 04:47:31 +01:00
2010-07-02 01:48:07 +02:00
} ;
tinymce . dom . RangeUtils . compareRanges = function ( rng1 , rng2 ) {
if ( rng1 && rng2 ) {
// Compare native IE ranges
if ( rng1 . item || rng1 . duplicate ) {
// Both are control ranges and the selected element matches
if ( rng1 . item && rng2 . item && rng1 . item ( 0 ) === rng2 . item ( 0 ) )
return true ;
2012-03-21 04:47:31 +01:00
// Both are text ranges and the range matches
if ( rng1 . isEqual && rng2 . isEqual && rng2 . isEqual ( rng1 ) )
return true ;
} else {
// Compare w3c ranges
return rng1 . startContainer == rng2 . startContainer && rng1 . startOffset == rng2 . startOffset ;
}
}
return false ;
} ;
} ) ( tinymce ) ;
( function ( tinymce ) {
var Event = tinymce . dom . Event , each = tinymce . each ;
tinymce . create ( 'tinymce.ui.KeyboardNavigation' , {
KeyboardNavigation : function ( settings , dom ) {
var t = this , root = settings . root , items = settings . items ,
enableUpDown = settings . enableUpDown , enableLeftRight = settings . enableLeftRight || ! settings . enableUpDown ,
excludeFromTabOrder = settings . excludeFromTabOrder ,
itemFocussed , itemBlurred , rootKeydown , rootFocussed , focussedId ;
dom = dom || tinymce . DOM ;
itemFocussed = function ( evt ) {
focussedId = evt . target . id ;
} ;
itemBlurred = function ( evt ) {
dom . setAttrib ( evt . target . id , 'tabindex' , '-1' ) ;
} ;
rootFocussed = function ( evt ) {
var item = dom . get ( focussedId ) ;
dom . setAttrib ( item , 'tabindex' , '0' ) ;
item . focus ( ) ;
} ;
t . focus = function ( ) {
dom . get ( focussedId ) . focus ( ) ;
} ;
t . destroy = function ( ) {
each ( items , function ( item ) {
dom . unbind ( dom . get ( item . id ) , 'focus' , itemFocussed ) ;
dom . unbind ( dom . get ( item . id ) , 'blur' , itemBlurred ) ;
} ) ;
dom . unbind ( dom . get ( root ) , 'focus' , rootFocussed ) ;
dom . unbind ( dom . get ( root ) , 'keydown' , rootKeydown ) ;
items = dom = root = t . focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null ;
t . destroy = function ( ) { } ;
} ;
t . moveFocus = function ( dir , evt ) {
var idx = - 1 , controls = t . controls , newFocus ;
if ( ! focussedId )
return ;
each ( items , function ( item , index ) {
if ( item . id === focussedId ) {
idx = index ;
return false ;
}
} ) ;
idx += dir ;
if ( idx < 0 ) {
idx = items . length - 1 ;
} else if ( idx >= items . length ) {
idx = 0 ;
}
newFocus = items [ idx ] ;
dom . setAttrib ( focussedId , 'tabindex' , '-1' ) ;
dom . setAttrib ( newFocus . id , 'tabindex' , '0' ) ;
dom . get ( newFocus . id ) . focus ( ) ;
if ( settings . actOnFocus ) {
settings . onAction ( newFocus . id ) ;
}
if ( evt )
Event . cancel ( evt ) ;
} ;
rootKeydown = function ( evt ) {
var DOM _VK _LEFT = 37 , DOM _VK _RIGHT = 39 , DOM _VK _UP = 38 , DOM _VK _DOWN = 40 , DOM _VK _ESCAPE = 27 , DOM _VK _ENTER = 14 , DOM _VK _RETURN = 13 , DOM _VK _SPACE = 32 ;
switch ( evt . keyCode ) {
case DOM _VK _LEFT :
if ( enableLeftRight ) t . moveFocus ( - 1 ) ;
break ;
case DOM _VK _RIGHT :
if ( enableLeftRight ) t . moveFocus ( 1 ) ;
break ;
case DOM _VK _UP :
if ( enableUpDown ) t . moveFocus ( - 1 ) ;
break ;
case DOM _VK _DOWN :
if ( enableUpDown ) t . moveFocus ( 1 ) ;
break ;
case DOM _VK _ESCAPE :
if ( settings . onCancel ) {
settings . onCancel ( ) ;
Event . cancel ( evt ) ;
}
break ;
case DOM _VK _ENTER :
case DOM _VK _RETURN :
case DOM _VK _SPACE :
if ( settings . onAction ) {
settings . onAction ( focussedId ) ;
Event . cancel ( evt ) ;
}
break ;
}
} ;
// Set up state and listeners for each item.
each ( items , function ( item , idx ) {
var tabindex ;
if ( ! item . id ) {
item . id = dom . uniqueId ( '_mce_item_' ) ;
}
if ( excludeFromTabOrder ) {
dom . bind ( item . id , 'blur' , itemBlurred ) ;
tabindex = '-1' ;
} else {
tabindex = ( idx === 0 ? '0' : '-1' ) ;
}
dom . setAttrib ( item . id , 'tabindex' , tabindex ) ;
dom . bind ( dom . get ( item . id ) , 'focus' , itemFocussed ) ;
} ) ;
// Setup initial state for root element.
if ( items [ 0 ] ) {
focussedId = items [ 0 ] . id ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
dom . setAttrib ( root , 'tabindex' , '-1' ) ;
// Setup listeners for root element.
dom . bind ( dom . get ( root ) , 'focus' , rootFocussed ) ;
dom . bind ( dom . get ( root ) , 'keydown' , rootKeydown ) ;
}
} ) ;
2010-07-02 01:48:07 +02:00
} ) ( tinymce ) ;
( function ( tinymce ) {
// Shorten class names
var DOM = tinymce . DOM , is = tinymce . is ;
tinymce . create ( 'tinymce.ui.Control' , {
2012-03-21 04:47:31 +01:00
Control : function ( id , s , editor ) {
2010-07-02 01:48:07 +02:00
this . id = id ;
this . settings = s = s || { } ;
this . rendered = false ;
this . onRender = new tinymce . util . Dispatcher ( this ) ;
this . classPrefix = '' ;
this . scope = s . scope || this ;
this . disabled = 0 ;
this . active = 0 ;
2012-03-21 04:47:31 +01:00
this . editor = editor ;
} ,
setAriaProperty : function ( property , value ) {
var element = DOM . get ( this . id + '_aria' ) || DOM . get ( this . id ) ;
if ( element ) {
DOM . setAttrib ( element , 'aria-' + property , ! ! value ) ;
}
} ,
focus : function ( ) {
DOM . get ( this . id ) . focus ( ) ;
2010-07-02 01:48:07 +02:00
} ,
setDisabled : function ( s ) {
if ( s != this . disabled ) {
2012-03-21 04:47:31 +01:00
this . setAriaProperty ( 'disabled' , s ) ;
2010-07-02 01:48:07 +02:00
this . setState ( 'Disabled' , s ) ;
this . setState ( 'Enabled' , ! s ) ;
this . disabled = s ;
}
} ,
isDisabled : function ( ) {
return this . disabled ;
} ,
setActive : function ( s ) {
if ( s != this . active ) {
this . setState ( 'Active' , s ) ;
this . active = s ;
2012-03-21 04:47:31 +01:00
this . setAriaProperty ( 'pressed' , s ) ;
2010-07-02 01:48:07 +02:00
}
} ,
isActive : function ( ) {
return this . active ;
} ,
setState : function ( c , s ) {
var n = DOM . get ( this . id ) ;
c = this . classPrefix + c ;
if ( s )
DOM . addClass ( n , c ) ;
else
DOM . removeClass ( n , c ) ;
} ,
isRendered : function ( ) {
return this . rendered ;
} ,
renderHTML : function ( ) {
} ,
renderTo : function ( n ) {
DOM . setHTML ( n , this . renderHTML ( ) ) ;
} ,
postRender : function ( ) {
var t = this , b ;
// Set pending states
if ( is ( t . disabled ) ) {
b = t . disabled ;
t . disabled = - 1 ;
t . setDisabled ( b ) ;
}
if ( is ( t . active ) ) {
b = t . active ;
t . active = - 1 ;
t . setActive ( b ) ;
}
} ,
remove : function ( ) {
DOM . remove ( this . id ) ;
this . destroy ( ) ;
} ,
destroy : function ( ) {
tinymce . dom . Event . clear ( this . id ) ;
}
} ) ;
} ) ( tinymce ) ;
tinymce . create ( 'tinymce.ui.Container:tinymce.ui.Control' , {
2012-03-21 04:47:31 +01:00
Container : function ( id , s , editor ) {
this . parent ( id , s , editor ) ;
2010-07-02 01:48:07 +02:00
this . controls = [ ] ;
this . lookup = { } ;
} ,
add : function ( c ) {
this . lookup [ c . id ] = c ;
this . controls . push ( c ) ;
return c ;
} ,
get : function ( n ) {
return this . lookup [ n ] ;
}
} ) ;
tinymce . create ( 'tinymce.ui.Separator:tinymce.ui.Control' , {
Separator : function ( id , s ) {
this . parent ( id , s ) ;
this . classPrefix = 'mceSeparator' ;
2012-03-21 04:47:31 +01:00
this . setDisabled ( true ) ;
2010-07-02 01:48:07 +02:00
} ,
renderHTML : function ( ) {
2012-03-21 04:47:31 +01:00
return tinymce . DOM . createHTML ( 'span' , { 'class' : this . classPrefix , role : 'separator' , 'aria-orientation' : 'vertical' , tabindex : '-1' } ) ;
2010-07-02 01:48:07 +02:00
}
} ) ;
( function ( tinymce ) {
var is = tinymce . is , DOM = tinymce . DOM , each = tinymce . each , walk = tinymce . walk ;
tinymce . create ( 'tinymce.ui.MenuItem:tinymce.ui.Control' , {
MenuItem : function ( id , s ) {
this . parent ( id , s ) ;
this . classPrefix = 'mceMenuItem' ;
} ,
setSelected : function ( s ) {
this . setState ( 'Selected' , s ) ;
2012-03-21 04:47:31 +01:00
this . setAriaProperty ( 'checked' , ! ! s ) ;
2010-07-02 01:48:07 +02:00
this . selected = s ;
} ,
isSelected : function ( ) {
return this . selected ;
} ,
postRender : function ( ) {
var t = this ;
t . parent ( ) ;
// Set pending state
if ( is ( t . selected ) )
t . setSelected ( t . selected ) ;
}
} ) ;
} ) ( tinymce ) ;
( function ( tinymce ) {
var is = tinymce . is , DOM = tinymce . DOM , each = tinymce . each , walk = tinymce . walk ;
tinymce . create ( 'tinymce.ui.Menu:tinymce.ui.MenuItem' , {
Menu : function ( id , s ) {
var t = this ;
t . parent ( id , s ) ;
t . items = { } ;
t . collapsed = false ;
t . menuCount = 0 ;
t . onAddItem = new tinymce . util . Dispatcher ( this ) ;
} ,
expand : function ( d ) {
var t = this ;
if ( d ) {
walk ( t , function ( o ) {
if ( o . expand )
o . expand ( ) ;
} , 'items' , t ) ;
}
t . collapsed = false ;
} ,
collapse : function ( d ) {
var t = this ;
if ( d ) {
walk ( t , function ( o ) {
if ( o . collapse )
o . collapse ( ) ;
} , 'items' , t ) ;
}
t . collapsed = true ;
} ,
isCollapsed : function ( ) {
return this . collapsed ;
} ,
add : function ( o ) {
if ( ! o . settings )
o = new tinymce . ui . MenuItem ( o . id || DOM . uniqueId ( ) , o ) ;
this . onAddItem . dispatch ( this , o ) ;
return this . items [ o . id ] = o ;
} ,
addSeparator : function ( ) {
return this . add ( { separator : true } ) ;
} ,
addMenu : function ( o ) {
if ( ! o . collapse )
o = this . createMenu ( o ) ;
this . menuCount ++ ;
return this . add ( o ) ;
} ,
hasMenus : function ( ) {
return this . menuCount !== 0 ;
} ,
remove : function ( o ) {
delete this . items [ o . id ] ;
} ,
removeAll : function ( ) {
var t = this ;
walk ( t , function ( o ) {
if ( o . removeAll )
o . removeAll ( ) ;
else
o . remove ( ) ;
o . destroy ( ) ;
} , 'items' , t ) ;
t . items = { } ;
} ,
createMenu : function ( o ) {
var m = new tinymce . ui . Menu ( o . id || DOM . uniqueId ( ) , o ) ;
m . onAddItem . add ( this . onAddItem . dispatch , this . onAddItem ) ;
return m ;
}
} ) ;
} ) ( tinymce ) ;
( function ( tinymce ) {
var is = tinymce . is , DOM = tinymce . DOM , each = tinymce . each , Event = tinymce . dom . Event , Element = tinymce . dom . Element ;
tinymce . create ( 'tinymce.ui.DropMenu:tinymce.ui.Menu' , {
DropMenu : function ( id , s ) {
s = s || { } ;
s . container = s . container || DOM . doc . body ;
s . offset _x = s . offset _x || 0 ;
s . offset _y = s . offset _y || 0 ;
s . vp _offset _x = s . vp _offset _x || 0 ;
s . vp _offset _y = s . vp _offset _y || 0 ;
if ( is ( s . icons ) && ! s . icons )
s [ 'class' ] += ' mceNoIcons' ;
this . parent ( id , s ) ;
this . onShowMenu = new tinymce . util . Dispatcher ( this ) ;
this . onHideMenu = new tinymce . util . Dispatcher ( this ) ;
this . classPrefix = 'mceMenu' ;
} ,
createMenu : function ( s ) {
var t = this , cs = t . settings , m ;
s . container = s . container || cs . container ;
s . parent = t ;
s . constrain = s . constrain || cs . constrain ;
s [ 'class' ] = s [ 'class' ] || cs [ 'class' ] ;
s . vp _offset _x = s . vp _offset _x || cs . vp _offset _x ;
s . vp _offset _y = s . vp _offset _y || cs . vp _offset _y ;
2012-03-21 04:47:31 +01:00
s . keyboard _focus = cs . keyboard _focus ;
2010-07-02 01:48:07 +02:00
m = new tinymce . ui . DropMenu ( s . id || DOM . uniqueId ( ) , s ) ;
m . onAddItem . add ( t . onAddItem . dispatch , t . onAddItem ) ;
return m ;
} ,
2012-03-21 04:47:31 +01:00
focus : function ( ) {
var t = this ;
if ( t . keyboardNav ) {
t . keyboardNav . focus ( ) ;
}
} ,
2010-07-02 01:48:07 +02:00
update : function ( ) {
var t = this , s = t . settings , tb = DOM . get ( 'menu_' + t . id + '_tbl' ) , co = DOM . get ( 'menu_' + t . id + '_co' ) , tw , th ;
2012-05-16 02:18:46 +02:00
tw = s . max _width ? Math . min ( tb . offsetWidth , s . max _width ) : tb . offsetWidth ;
th = s . max _height ? Math . min ( tb . offsetHeight , s . max _height ) : tb . offsetHeight ;
2010-07-02 01:48:07 +02:00
if ( ! DOM . boxModel )
t . element . setStyles ( { width : tw + 2 , height : th + 2 } ) ;
else
t . element . setStyles ( { width : tw , height : th } ) ;
if ( s . max _width )
DOM . setStyle ( co , 'width' , tw ) ;
if ( s . max _height ) {
DOM . setStyle ( co , 'height' , th ) ;
if ( tb . clientHeight < s . max _height )
DOM . setStyle ( co , 'overflow' , 'hidden' ) ;
}
} ,
showMenu : function ( x , y , px ) {
var t = this , s = t . settings , co , vp = DOM . getViewPort ( ) , w , h , mx , my , ot = 2 , dm , tb , cp = t . classPrefix ;
t . collapse ( 1 ) ;
if ( t . isMenuVisible )
return ;
if ( ! t . rendered ) {
co = DOM . add ( t . settings . container , t . renderNode ( ) ) ;
each ( t . items , function ( o ) {
o . postRender ( ) ;
} ) ;
t . element = new Element ( 'menu_' + t . id , { blocker : 1 , container : s . container } ) ;
} else
co = DOM . get ( 'menu_' + t . id ) ;
// Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
if ( ! tinymce . isOpera )
DOM . setStyles ( co , { left : - 0xFFFF , top : - 0xFFFF } ) ;
DOM . show ( co ) ;
t . update ( ) ;
x += s . offset _x || 0 ;
y += s . offset _y || 0 ;
vp . w -= 4 ;
vp . h -= 4 ;
// Move inside viewport if not submenu
if ( s . constrain ) {
w = co . clientWidth - ot ;
h = co . clientHeight - ot ;
mx = vp . x + vp . w ;
my = vp . y + vp . h ;
if ( ( x + s . vp _offset _x + w ) > mx )
x = px ? px - w : Math . max ( 0 , ( mx - s . vp _offset _x ) - w ) ;
if ( ( y + s . vp _offset _y + h ) > my )
y = Math . max ( 0 , ( my - s . vp _offset _y ) - h ) ;
}
DOM . setStyles ( co , { left : x , top : y } ) ;
t . element . update ( ) ;
t . isMenuVisible = 1 ;
t . mouseClickFunc = Event . add ( co , 'click' , function ( e ) {
var m ;
e = e . target ;
if ( e && ( e = DOM . getParent ( e , 'tr' ) ) && ! DOM . hasClass ( e , cp + 'ItemSub' ) ) {
m = t . items [ e . id ] ;
if ( m . isDisabled ( ) )
return ;
dm = t ;
while ( dm ) {
if ( dm . hideMenu )
dm . hideMenu ( ) ;
dm = dm . settings . parent ;
}
if ( m . settings . onclick )
m . settings . onclick ( e ) ;
2012-03-21 04:47:31 +01:00
return false ; // Cancel to fix onbeforeunload problem
2010-07-02 01:48:07 +02:00
}
} ) ;
if ( t . hasMenus ( ) ) {
t . mouseOverFunc = Event . add ( co , 'mouseover' , function ( e ) {
var m , r , mi ;
e = e . target ;
if ( e && ( e = DOM . getParent ( e , 'tr' ) ) ) {
m = t . items [ e . id ] ;
if ( t . lastMenu )
t . lastMenu . collapse ( 1 ) ;
if ( m . isDisabled ( ) )
return ;
if ( e && DOM . hasClass ( e , cp + 'ItemSub' ) ) {
//p = DOM.getPos(s.container);
r = DOM . getRect ( e ) ;
m . showMenu ( ( r . x + r . w - ot ) , r . y - ot , r . x ) ;
t . lastMenu = m ;
DOM . addClass ( DOM . get ( m . id ) . firstChild , cp + 'ItemActive' ) ;
}
}
} ) ;
}
2012-03-21 04:47:31 +01:00
Event . add ( co , 'keydown' , t . _keyHandler , t ) ;
2010-07-02 01:48:07 +02:00
t . onShowMenu . dispatch ( t ) ;
2012-03-21 04:47:31 +01:00
if ( s . keyboard _focus ) {
t . _setupKeyboardNav ( ) ;
2010-07-02 01:48:07 +02:00
}
} ,
hideMenu : function ( c ) {
var t = this , co = DOM . get ( 'menu_' + t . id ) , e ;
if ( ! t . isMenuVisible )
return ;
2012-03-21 04:47:31 +01:00
if ( t . keyboardNav ) t . keyboardNav . destroy ( ) ;
2010-07-02 01:48:07 +02:00
Event . remove ( co , 'mouseover' , t . mouseOverFunc ) ;
Event . remove ( co , 'click' , t . mouseClickFunc ) ;
Event . remove ( co , 'keydown' , t . _keyHandler ) ;
DOM . hide ( co ) ;
t . isMenuVisible = 0 ;
if ( ! c )
t . collapse ( 1 ) ;
if ( t . element )
t . element . hide ( ) ;
if ( e = DOM . get ( t . id ) )
DOM . removeClass ( e . firstChild , t . classPrefix + 'ItemActive' ) ;
t . onHideMenu . dispatch ( t ) ;
} ,
add : function ( o ) {
var t = this , co ;
o = t . parent ( o ) ;
if ( t . isRendered && ( co = DOM . get ( 'menu_' + t . id ) ) )
t . _add ( DOM . select ( 'tbody' , co ) [ 0 ] , o ) ;
return o ;
} ,
collapse : function ( d ) {
this . parent ( d ) ;
this . hideMenu ( 1 ) ;
} ,
remove : function ( o ) {
DOM . remove ( o . id ) ;
this . destroy ( ) ;
return this . parent ( o ) ;
} ,
destroy : function ( ) {
var t = this , co = DOM . get ( 'menu_' + t . id ) ;
2012-03-21 04:47:31 +01:00
if ( t . keyboardNav ) t . keyboardNav . destroy ( ) ;
2010-07-02 01:48:07 +02:00
Event . remove ( co , 'mouseover' , t . mouseOverFunc ) ;
2012-03-21 04:47:31 +01:00
Event . remove ( DOM . select ( 'a' , co ) , 'focus' , t . mouseOverFunc ) ;
2010-07-02 01:48:07 +02:00
Event . remove ( co , 'click' , t . mouseClickFunc ) ;
2012-03-21 04:47:31 +01:00
Event . remove ( co , 'keydown' , t . _keyHandler ) ;
2010-07-02 01:48:07 +02:00
if ( t . element )
t . element . remove ( ) ;
DOM . remove ( co ) ;
} ,
renderNode : function ( ) {
var t = this , s = t . settings , n , tb , co , w ;
2012-03-21 04:47:31 +01:00
w = DOM . create ( 'div' , { role : 'listbox' , id : 'menu_' + t . id , 'class' : s [ 'class' ] , 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0' } ) ;
if ( t . settings . parent ) {
DOM . setAttrib ( w , 'aria-parent' , 'menu_' + t . settings . parent . id ) ;
}
co = DOM . add ( w , 'div' , { role : 'presentation' , id : 'menu_' + t . id + '_co' , 'class' : t . classPrefix + ( s [ 'class' ] ? ' ' + s [ 'class' ] : '' ) } ) ;
2010-07-02 01:48:07 +02:00
t . element = new Element ( 'menu_' + t . id , { blocker : 1 , container : s . container } ) ;
if ( s . menu _line )
DOM . add ( co , 'span' , { 'class' : t . classPrefix + 'Line' } ) ;
// n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
2012-03-21 04:47:31 +01:00
n = DOM . add ( co , 'table' , { role : 'presentation' , id : 'menu_' + t . id + '_tbl' , border : 0 , cellPadding : 0 , cellSpacing : 0 } ) ;
tb = DOM . add ( n , 'tbody' ) ;
2010-07-02 01:48:07 +02:00
each ( t . items , function ( o ) {
2012-03-21 04:47:31 +01:00
t . _add ( tb , o ) ;
2010-07-02 01:48:07 +02:00
} ) ;
2012-03-21 04:47:31 +01:00
t . rendered = true ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return w ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
// Internal functions
_setupKeyboardNav : function ( ) {
var contextMenu , menuItems , t = this ;
contextMenu = DOM . get ( 'menu_' + t . id ) ;
menuItems = DOM . select ( 'a[role=option]' , 'menu_' + t . id ) ;
menuItems . splice ( 0 , 0 , contextMenu ) ;
t . keyboardNav = new tinymce . ui . KeyboardNavigation ( {
root : 'menu_' + t . id ,
items : menuItems ,
onCancel : function ( ) {
t . hideMenu ( ) ;
} ,
enableUpDown : true
} ) ;
contextMenu . focus ( ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
_keyHandler : function ( evt ) {
var t = this , e ;
switch ( evt . keyCode ) {
case 37 : // Left
if ( t . settings . parent ) {
t . hideMenu ( ) ;
t . settings . parent . focus ( ) ;
Event . cancel ( evt ) ;
}
break ;
case 39 : // Right
if ( t . mouseOverFunc )
t . mouseOverFunc ( evt ) ;
break ;
2010-07-02 01:48:07 +02:00
}
} ,
2012-03-21 04:47:31 +01:00
_add : function ( tb , o ) {
var n , s = o . settings , a , ro , it , cp = this . classPrefix , ic ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( s . separator ) {
ro = DOM . add ( tb , 'tr' , { id : o . id , 'class' : cp + 'ItemSeparator' } ) ;
DOM . add ( ro , 'td' , { 'class' : cp + 'ItemSeparator' } ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( n = ro . previousSibling )
DOM . addClass ( n , 'mceLast' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
n = ro = DOM . add ( tb , 'tr' , { id : o . id , 'class' : cp + 'Item ' + cp + 'ItemEnabled' } ) ;
n = it = DOM . add ( n , s . titleItem ? 'th' : 'td' ) ;
n = a = DOM . add ( n , 'a' , { id : o . id + '_aria' , role : s . titleItem ? 'presentation' : 'option' , href : 'javascript:;' , onclick : "return false;" , onmousedown : 'return false;' } ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( s . parent ) {
DOM . setAttrib ( a , 'aria-haspopup' , 'true' ) ;
DOM . setAttrib ( a , 'aria-owns' , 'menu_' + o . id ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
DOM . addClass ( it , s [ 'class' ] ) ;
// n = DOM.add(n, 'span', {'class' : 'item'});
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
ic = DOM . add ( n , 'span' , { 'class' : 'mceIcon' + ( s . icon ? ' mce_' + s . icon : '' ) } ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( s . icon _src )
DOM . add ( ic , 'img' , { src : s . icon _src } ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
n = DOM . add ( n , s . element || 'span' , { 'class' : 'mceText' , title : o . settings . title } , o . settings . title ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( o . settings . style ) {
if ( typeof o . settings . style == "function" )
o . settings . style = o . settings . style ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
DOM . setAttrib ( n , 'style' , o . settings . style ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( tb . childNodes . length == 1 )
DOM . addClass ( ro , 'mceFirst' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ( n = ro . previousSibling ) && DOM . hasClass ( n , cp + 'ItemSeparator' ) )
DOM . addClass ( ro , 'mceFirst' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( o . collapse )
DOM . addClass ( ro , cp + 'ItemSub' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( n = ro . previousSibling )
DOM . removeClass ( n , 'mceLast' ) ;
DOM . addClass ( ro , 'mceLast' ) ;
}
} ) ;
} ) ( tinymce ) ;
( function ( tinymce ) {
var DOM = tinymce . DOM ;
tinymce . create ( 'tinymce.ui.Button:tinymce.ui.Control' , {
Button : function ( id , s , ed ) {
this . parent ( id , s , ed ) ;
this . classPrefix = 'mceButton' ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
renderHTML : function ( ) {
var cp = this . classPrefix , s = this . settings , h , l ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
l = DOM . encode ( s . label || '' ) ;
h = '<a role="button" id="' + this . id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s [ 'class' ] + ( l ? ' ' + cp + 'Labeled' : '' ) + '" onmousedown="return false;" onclick="return false;" aria-labelledby="' + this . id + '_voice" title="' + DOM . encode ( s . title ) + '">' ;
if ( s . image && ! ( this . editor && this . editor . forcedHighContrastMode ) )
h += '<img class="mceIcon" src="' + s . image + '" alt="' + DOM . encode ( s . title ) + '" />' + l ;
else
h += '<span class="mceIcon ' + s [ 'class' ] + '"></span>' + ( l ? '<span class="' + cp + 'Label">' + l + '</span>' : '' ) ;
h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this . id + '_voice">' + s . title + '</span>' ;
h += '</a>' ;
return h ;
} ,
postRender : function ( ) {
var t = this , s = t . settings , imgBookmark ;
// In IE a large image that occupies the entire editor area will be deselected when a button is clicked, so
// need to keep the selection in case the selection is lost
if ( tinymce . isIE && t . editor ) {
tinymce . dom . Event . add ( t . id , 'mousedown' , function ( e ) {
var nodeName = t . editor . selection . getNode ( ) . nodeName ;
imgBookmark = nodeName === 'IMG' ? t . editor . selection . getBookmark ( ) : null ;
} ) ;
}
tinymce . dom . Event . add ( t . id , 'click' , function ( e ) {
if ( ! t . isDisabled ( ) ) {
// restore the selection in case the selection is lost in IE
if ( tinymce . isIE && t . editor && imgBookmark !== null ) {
t . editor . selection . moveToBookmark ( imgBookmark ) ;
}
return s . onclick . call ( s . scope , e ) ;
}
} ) ;
tinymce . dom . Event . add ( t . id , 'keyup' , function ( e ) {
if ( ! t . isDisabled ( ) && e . keyCode == tinymce . VK . SPACEBAR )
return s . onclick . call ( s . scope , e ) ;
} ) ;
2010-07-02 01:48:07 +02:00
}
} ) ;
} ) ( tinymce ) ;
2012-03-21 04:47:31 +01:00
2010-07-02 01:48:07 +02:00
( function ( tinymce ) {
2012-05-16 02:18:46 +02:00
var DOM = tinymce . DOM , Event = tinymce . dom . Event , each = tinymce . each , Dispatcher = tinymce . util . Dispatcher , undef ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinymce . create ( 'tinymce.ui.ListBox:tinymce.ui.Control' , {
ListBox : function ( id , s , ed ) {
var t = this ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . parent ( id , s , ed ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . items = [ ] ;
t . onChange = new Dispatcher ( t ) ;
t . onPostRender = new Dispatcher ( t ) ;
t . onAdd = new Dispatcher ( t ) ;
t . onRenderMenu = new tinymce . util . Dispatcher ( this ) ;
t . classPrefix = 'mceListBox' ;
t . marked = { } ;
2010-07-02 01:48:07 +02:00
} ,
select : function ( va ) {
var t = this , fv , f ;
2012-03-21 04:47:31 +01:00
t . marked = { } ;
2012-05-16 02:18:46 +02:00
if ( va == undef )
2010-07-02 01:48:07 +02:00
return t . selectByIndex ( - 1 ) ;
// Is string or number make function selector
2012-03-21 04:47:31 +01:00
if ( va && typeof ( va ) == "function" )
2010-07-02 01:48:07 +02:00
f = va ;
else {
f = function ( v ) {
return v == va ;
} ;
}
// Do we need to do something?
if ( va != t . selectedValue ) {
// Find item
each ( t . items , function ( o , i ) {
if ( f ( o . value ) ) {
fv = 1 ;
t . selectByIndex ( i ) ;
return false ;
}
} ) ;
if ( ! fv )
t . selectByIndex ( - 1 ) ;
}
} ,
selectByIndex : function ( idx ) {
2012-03-21 04:47:31 +01:00
var t = this , e , o , label ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . marked = { } ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( idx != t . selectedIndex ) {
e = DOM . get ( t . id + '_text' ) ;
label = DOM . get ( t . id + '_voiceDesc' ) ;
o = t . items [ idx ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( o ) {
t . selectedValue = o . value ;
t . selectedIndex = idx ;
DOM . setHTML ( e , DOM . encode ( o . title ) ) ;
DOM . setHTML ( label , t . settings . title + " - " + o . title ) ;
DOM . removeClass ( e , 'mceTitle' ) ;
DOM . setAttrib ( t . id , 'aria-valuenow' , o . title ) ;
} else {
DOM . setHTML ( e , DOM . encode ( t . settings . title ) ) ;
DOM . setHTML ( label , DOM . encode ( t . settings . title ) ) ;
DOM . addClass ( e , 'mceTitle' ) ;
t . selectedValue = t . selectedIndex = null ;
DOM . setAttrib ( t . id , 'aria-valuenow' , t . settings . title ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
e = 0 ;
}
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
mark : function ( value ) {
this . marked [ value ] = true ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
add : function ( n , v , o ) {
var t = this ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
o = o || { } ;
o = tinymce . extend ( o , {
title : n ,
value : v
2010-07-02 01:48:07 +02:00
} ) ;
2012-03-21 04:47:31 +01:00
t . items . push ( o ) ;
t . onAdd . dispatch ( t , o ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
getLength : function ( ) {
return this . items . length ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
renderHTML : function ( ) {
var h = '' , t = this , s = t . settings , cp = t . classPrefix ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
h = '<span role="listbox" aria-haspopup="true" aria-labelledby="' + t . id + '_voiceDesc" aria-describedby="' + t . id + '_voiceDesc"><table role="presentation" tabindex="0" id="' + t . id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + ( s [ 'class' ] ? ( ' ' + s [ 'class' ] ) : '' ) + '"><tbody><tr>' ;
h += '<td>' + DOM . createHTML ( 'span' , { id : t . id + '_voiceDesc' , 'class' : 'voiceLabel' , style : 'display:none;' } , t . settings . title ) ;
h += DOM . createHTML ( 'a' , { id : t . id + '_text' , tabindex : - 1 , href : 'javascript:;' , 'class' : 'mceText' , onclick : "return false;" , onmousedown : 'return false;' } , DOM . encode ( t . settings . title ) ) + '</td>' ;
h += '<td>' + DOM . createHTML ( 'a' , { id : t . id + '_open' , tabindex : - 1 , href : 'javascript:;' , 'class' : 'mceOpen' , onclick : "return false;" , onmousedown : 'return false;' } , '<span><span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span></span>' ) + '</td>' ;
h += '</tr></tbody></table></span>' ;
return h ;
2010-07-02 01:48:07 +02:00
} ,
showMenu : function ( ) {
2012-03-21 04:47:31 +01:00
var t = this , p2 , e = DOM . get ( this . id ) , m ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( t . isDisabled ( ) || t . items . length === 0 )
2010-07-02 01:48:07 +02:00
return ;
2012-03-21 04:47:31 +01:00
if ( t . menu && t . menu . isMenuVisible )
return t . hideMenu ( ) ;
2010-07-02 01:48:07 +02:00
if ( ! t . isMenuRendered ) {
t . renderMenu ( ) ;
t . isMenuRendered = true ;
}
p2 = DOM . getPos ( e ) ;
m = t . menu ;
m . settings . offset _x = p2 . x ;
m . settings . offset _y = p2 . y ;
2012-03-21 04:47:31 +01:00
m . settings . keyboard _focus = ! tinymce . isOpera ; // Opera is buggy when it comes to auto focus
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Select in menu
each ( t . items , function ( o ) {
if ( m . items [ o . id ] ) {
m . items [ o . id ] . setSelected ( 0 ) ;
}
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
each ( t . items , function ( o ) {
if ( m . items [ o . id ] && t . marked [ o . value ] ) {
m . items [ o . id ] . setSelected ( 1 ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( o . value === t . selectedValue ) {
m . items [ o . id ] . setSelected ( 1 ) ;
}
2010-07-02 01:48:07 +02:00
} ) ;
2012-03-21 04:47:31 +01:00
m . showMenu ( 0 , e . clientHeight ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
Event . add ( DOM . doc , 'mousedown' , t . hideMenu , t ) ;
DOM . addClass ( t . id , t . classPrefix + 'Selected' ) ;
//DOM.get(t.id + '_text').focus();
2010-07-02 01:48:07 +02:00
} ,
hideMenu : function ( e ) {
var t = this ;
2012-03-21 04:47:31 +01:00
if ( t . menu && t . menu . isMenuVisible ) {
DOM . removeClass ( t . id , t . classPrefix + 'Selected' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Prevent double toogles by canceling the mouse click event to the button
if ( e && e . type == "mousedown" && ( e . target . id == t . id + '_text' || e . target . id == t . id + '_open' ) )
return ;
if ( ! e || ! DOM . getParent ( e . target , '.mceMenu' ) ) {
DOM . removeClass ( t . id , t . classPrefix + 'Selected' ) ;
Event . remove ( DOM . doc , 'mousedown' , t . hideMenu , t ) ;
2010-07-02 01:48:07 +02:00
t . menu . hideMenu ( ) ;
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
}
} ,
2012-03-21 04:47:31 +01:00
renderMenu : function ( ) {
var t = this , m ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
m = t . settings . control _manager . createDropMenu ( t . id + '_menu' , {
menu _line : 1 ,
'class' : t . classPrefix + 'Menu mceNoIcons' ,
2012-05-16 02:18:46 +02:00
max _width : 250 ,
2012-03-21 04:47:31 +01:00
max _height : 150
2010-07-02 01:48:07 +02:00
} ) ;
2012-03-21 04:47:31 +01:00
m . onHideMenu . add ( function ( ) {
t . hideMenu ( ) ;
t . focus ( ) ;
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
m . add ( {
title : t . settings . title ,
'class' : 'mceMenuItemTitle' ,
onclick : function ( ) {
if ( t . settings . onselect ( '' ) !== false )
t . select ( '' ) ; // Must be runned after
}
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
each ( t . items , function ( o ) {
// No value then treat it as a title
2012-05-16 02:18:46 +02:00
if ( o . value === undef ) {
2012-03-21 04:47:31 +01:00
m . add ( {
title : o . title ,
role : "option" ,
'class' : 'mceMenuItemTitle' ,
onclick : function ( ) {
if ( t . settings . onselect ( '' ) !== false )
t . select ( '' ) ; // Must be runned after
}
} ) ;
} else {
o . id = DOM . uniqueId ( ) ;
o . role = "option" ;
o . onclick = function ( ) {
if ( t . settings . onselect ( o . value ) !== false )
t . select ( o . value ) ; // Must be runned after
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
m . add ( o ) ;
}
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . onRenderMenu . dispatch ( t , m ) ;
t . menu = m ;
2010-07-02 01:48:07 +02:00
} ,
postRender : function ( ) {
2012-03-21 04:47:31 +01:00
var t = this , cp = t . classPrefix ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
Event . add ( t . id , 'click' , t . showMenu , t ) ;
Event . add ( t . id , 'keydown' , function ( evt ) {
if ( evt . keyCode == 32 ) { // Space
t . showMenu ( evt ) ;
Event . cancel ( evt ) ;
}
2010-07-02 01:48:07 +02:00
} ) ;
2012-03-21 04:47:31 +01:00
Event . add ( t . id , 'focus' , function ( ) {
if ( ! t . _focused ) {
t . keyDownHandler = Event . add ( t . id , 'keydown' , function ( e ) {
if ( e . keyCode == 40 ) {
t . showMenu ( ) ;
Event . cancel ( e ) ;
}
} ) ;
t . keyPressHandler = Event . add ( t . id , 'keypress' , function ( e ) {
var v ;
if ( e . keyCode == 13 ) {
// Fake select on enter
v = t . selectedValue ;
t . selectedValue = null ; // Needs to be null to fake change
Event . cancel ( e ) ;
t . settings . onselect ( v ) ;
}
} ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . _focused = 1 ;
} ) ;
Event . add ( t . id , 'blur' , function ( ) {
Event . remove ( t . id , 'keydown' , t . keyDownHandler ) ;
Event . remove ( t . id , 'keypress' , t . keyPressHandler ) ;
t . _focused = 0 ;
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Old IE doesn't have hover on all elements
if ( tinymce . isIE6 || ! DOM . boxModel ) {
Event . add ( t . id , 'mouseover' , function ( ) {
if ( ! DOM . hasClass ( t . id , cp + 'Disabled' ) )
DOM . addClass ( t . id , cp + 'Hover' ) ;
2010-07-02 01:48:07 +02:00
} ) ;
2012-03-21 04:47:31 +01:00
Event . add ( t . id , 'mouseout' , function ( ) {
if ( ! DOM . hasClass ( t . id , cp + 'Disabled' ) )
DOM . removeClass ( t . id , cp + 'Hover' ) ;
} ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
t . onPostRender . dispatch ( t , DOM . get ( t . id ) ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
destroy : function ( ) {
this . parent ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
Event . clear ( this . id + '_text' ) ;
Event . clear ( this . id + '_open' ) ;
}
} ) ;
} ) ( tinymce ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
( function ( tinymce ) {
2012-05-16 02:18:46 +02:00
var DOM = tinymce . DOM , Event = tinymce . dom . Event , each = tinymce . each , Dispatcher = tinymce . util . Dispatcher , undef ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinymce . create ( 'tinymce.ui.NativeListBox:tinymce.ui.ListBox' , {
NativeListBox : function ( id , s ) {
this . parent ( id , s ) ;
this . classPrefix = 'mceNativeListBox' ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
setDisabled : function ( s ) {
DOM . get ( this . id ) . disabled = s ;
this . setAriaProperty ( 'disabled' , s ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
isDisabled : function ( ) {
return DOM . get ( this . id ) . disabled ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
select : function ( va ) {
var t = this , fv , f ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( va == undef )
2012-03-21 04:47:31 +01:00
return t . selectByIndex ( - 1 ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Is string or number make function selector
if ( va && typeof ( va ) == "function" )
f = va ;
else {
f = function ( v ) {
return v == va ;
} ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Do we need to do something?
if ( va != t . selectedValue ) {
// Find item
each ( t . items , function ( o , i ) {
if ( f ( o . value ) ) {
fv = 1 ;
t . selectByIndex ( i ) ;
return false ;
}
2010-07-02 01:48:07 +02:00
} ) ;
2012-03-21 04:47:31 +01:00
if ( ! fv )
t . selectByIndex ( - 1 ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
selectByIndex : function ( idx ) {
DOM . get ( this . id ) . selectedIndex = idx + 1 ;
this . selectedValue = this . items [ idx ] ? this . items [ idx ] . value : null ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
add : function ( n , v , a ) {
var o , t = this ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
a = a || { } ;
a . value = v ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( t . isRendered ( ) )
DOM . add ( DOM . get ( this . id ) , 'option' , a , n ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
o = {
title : n ,
value : v ,
attribs : a
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . items . push ( o ) ;
t . onAdd . dispatch ( t , o ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
getLength : function ( ) {
return this . items . length ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
renderHTML : function ( ) {
var h , t = this ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
h = DOM . createHTML ( 'option' , { value : '' } , '-- ' + t . settings . title + ' --' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
each ( t . items , function ( it ) {
h += DOM . createHTML ( 'option' , { value : it . value } , it . title ) ;
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
h = DOM . createHTML ( 'select' , { id : t . id , 'class' : 'mceNativeListBox' , 'aria-labelledby' : t . id + '_aria' } , h ) ;
h += DOM . createHTML ( 'span' , { id : t . id + '_aria' , 'style' : 'display: none' } , t . settings . title ) ;
return h ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
postRender : function ( ) {
var t = this , ch , changeListenerAdded = true ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . rendered = true ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function onChange ( e ) {
var v = t . items [ e . target . selectedIndex - 1 ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( v && ( v = v . value ) ) {
t . onChange . dispatch ( t , v ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( t . settings . onselect )
t . settings . onselect ( v ) ;
}
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
Event . add ( t . id , 'change' , onChange ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Accessibility keyhandler
Event . add ( t . id , 'keydown' , function ( e ) {
var bf ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
Event . remove ( t . id , 'change' , ch ) ;
changeListenerAdded = false ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
bf = Event . add ( t . id , 'blur' , function ( ) {
if ( changeListenerAdded ) return ;
changeListenerAdded = true ;
Event . add ( t . id , 'change' , onChange ) ;
Event . remove ( t . id , 'blur' , bf ) ;
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
//prevent default left and right keys on chrome - so that the keyboard navigation is used.
if ( tinymce . isWebKit && ( e . keyCode == 37 || e . keyCode == 39 ) ) {
return Event . prevent ( e ) ;
}
if ( e . keyCode == 13 || e . keyCode == 32 ) {
onChange ( e ) ;
return Event . cancel ( e ) ;
}
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . onPostRender . dispatch ( t , DOM . get ( t . id ) ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
} ) ;
} ) ( tinymce ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
( function ( tinymce ) {
var DOM = tinymce . DOM , Event = tinymce . dom . Event , each = tinymce . each ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinymce . create ( 'tinymce.ui.MenuButton:tinymce.ui.Button' , {
MenuButton : function ( id , s , ed ) {
this . parent ( id , s , ed ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
this . onRenderMenu = new tinymce . util . Dispatcher ( this ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
s . menu _container = s . menu _container || DOM . doc . body ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
showMenu : function ( ) {
var t = this , p1 , p2 , e = DOM . get ( t . id ) , m ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( t . isDisabled ( ) )
return ;
if ( ! t . isMenuRendered ) {
t . renderMenu ( ) ;
t . isMenuRendered = true ;
}
if ( t . isMenuVisible )
return t . hideMenu ( ) ;
p1 = DOM . getPos ( t . settings . menu _container ) ;
p2 = DOM . getPos ( e ) ;
m = t . menu ;
m . settings . offset _x = p2 . x ;
m . settings . offset _y = p2 . y ;
m . settings . vp _offset _x = p2 . x ;
m . settings . vp _offset _y = p2 . y ;
m . settings . keyboard _focus = t . _focused ;
2012-05-16 02:18:46 +02:00
m . showMenu ( 0 , e . firstChild . clientHeight ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
Event . add ( DOM . doc , 'mousedown' , t . hideMenu , t ) ;
t . setState ( 'Selected' , 1 ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . isMenuVisible = 1 ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
renderMenu : function ( ) {
var t = this , m ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
m = t . settings . control _manager . createDropMenu ( t . id + '_menu' , {
menu _line : 1 ,
'class' : this . classPrefix + 'Menu' ,
icons : t . settings . icons
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
m . onHideMenu . add ( function ( ) {
t . hideMenu ( ) ;
t . focus ( ) ;
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . onRenderMenu . dispatch ( t , m ) ;
t . menu = m ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
hideMenu : function ( e ) {
2010-07-02 01:48:07 +02:00
var t = this ;
2012-03-21 04:47:31 +01:00
// Prevent double toogles by canceling the mouse click event to the button
if ( e && e . type == "mousedown" && DOM . getParent ( e . target , function ( e ) { return e . id === t . id || e . id === t . id + '_open' ; } ) )
2010-07-02 01:48:07 +02:00
return ;
2012-03-21 04:47:31 +01:00
if ( ! e || ! DOM . getParent ( e . target , '.mceMenu' ) ) {
t . setState ( 'Selected' , 0 ) ;
Event . remove ( DOM . doc , 'mousedown' , t . hideMenu , t ) ;
if ( t . menu )
t . menu . hideMenu ( ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . isMenuVisible = 0 ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
postRender : function ( ) {
var t = this , s = t . settings ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
Event . add ( t . id , 'click' , function ( ) {
if ( ! t . isDisabled ( ) ) {
if ( s . onclick )
s . onclick ( t . value ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . showMenu ( ) ;
}
} ) ;
}
} ) ;
} ) ( tinymce ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
( function ( tinymce ) {
var DOM = tinymce . DOM , Event = tinymce . dom . Event , each = tinymce . each ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinymce . create ( 'tinymce.ui.SplitButton:tinymce.ui.MenuButton' , {
SplitButton : function ( id , s , ed ) {
this . parent ( id , s , ed ) ;
this . classPrefix = 'mceSplitButton' ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
renderHTML : function ( ) {
var h , t = this , s = t . settings , h1 ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
h = '<tbody><tr>' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( s . image )
h1 = DOM . createHTML ( 'img ' , { src : s . image , role : 'presentation' , 'class' : 'mceAction ' + s [ 'class' ] } ) ;
else
h1 = DOM . createHTML ( 'span' , { 'class' : 'mceAction ' + s [ 'class' ] } , '' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
h1 += DOM . createHTML ( 'span' , { 'class' : 'mceVoiceLabel mceIconOnly' , id : t . id + '_voice' , style : 'display:none;' } , s . title ) ;
h += '<td >' + DOM . createHTML ( 'a' , { role : 'button' , id : t . id + '_action' , tabindex : '-1' , href : 'javascript:;' , 'class' : 'mceAction ' + s [ 'class' ] , onclick : "return false;" , onmousedown : 'return false;' , title : s . title } , h1 ) + '</td>' ;
h1 = DOM . createHTML ( 'span' , { 'class' : 'mceOpen ' + s [ 'class' ] } , '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>' ) ;
h += '<td >' + DOM . createHTML ( 'a' , { role : 'button' , id : t . id + '_open' , tabindex : '-1' , href : 'javascript:;' , 'class' : 'mceOpen ' + s [ 'class' ] , onclick : "return false;" , onmousedown : 'return false;' , title : s . title } , h1 ) + '</td>' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
h += '</tr></tbody>' ;
h = DOM . createHTML ( 'table' , { role : 'presentation' , 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s [ 'class' ] , cellpadding : '0' , cellspacing : '0' , title : s . title } , h ) ;
return DOM . createHTML ( 'div' , { id : t . id , role : 'button' , tabindex : '0' , 'aria-labelledby' : t . id + '_voice' , 'aria-haspopup' : 'true' } , h ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
postRender : function ( ) {
var t = this , s = t . settings , activate ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( s . onclick ) {
activate = function ( evt ) {
if ( ! t . isDisabled ( ) ) {
s . onclick ( t . value ) ;
Event . cancel ( evt ) ;
}
} ;
Event . add ( t . id + '_action' , 'click' , activate ) ;
Event . add ( t . id , [ 'click' , 'keydown' ] , function ( evt ) {
var DOM _VK _SPACE = 32 , DOM _VK _ENTER = 14 , DOM _VK _RETURN = 13 , DOM _VK _UP = 38 , DOM _VK _DOWN = 40 ;
if ( ( evt . keyCode === 32 || evt . keyCode === 13 || evt . keyCode === 14 ) && ! evt . altKey && ! evt . ctrlKey && ! evt . metaKey ) {
activate ( ) ;
Event . cancel ( evt ) ;
} else if ( evt . type === 'click' || evt . keyCode === DOM _VK _DOWN ) {
t . showMenu ( ) ;
Event . cancel ( evt ) ;
}
} ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
Event . add ( t . id + '_open' , 'click' , function ( evt ) {
t . showMenu ( ) ;
Event . cancel ( evt ) ;
} ) ;
Event . add ( [ t . id , t . id + '_open' ] , 'focus' , function ( ) { t . _focused = 1 ; } ) ;
Event . add ( [ t . id , t . id + '_open' ] , 'blur' , function ( ) { t . _focused = 0 ; } ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Old IE doesn't have hover on all elements
if ( tinymce . isIE6 || ! DOM . boxModel ) {
Event . add ( t . id , 'mouseover' , function ( ) {
if ( ! DOM . hasClass ( t . id , 'mceSplitButtonDisabled' ) )
DOM . addClass ( t . id , 'mceSplitButtonHover' ) ;
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
Event . add ( t . id , 'mouseout' , function ( ) {
if ( ! DOM . hasClass ( t . id , 'mceSplitButtonDisabled' ) )
DOM . removeClass ( t . id , 'mceSplitButtonHover' ) ;
} ) ;
}
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
destroy : function ( ) {
this . parent ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
Event . clear ( this . id + '_action' ) ;
Event . clear ( this . id + '_open' ) ;
Event . clear ( this . id ) ;
}
} ) ;
} ) ( tinymce ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
( function ( tinymce ) {
var DOM = tinymce . DOM , Event = tinymce . dom . Event , is = tinymce . is , each = tinymce . each ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinymce . create ( 'tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton' , {
ColorSplitButton : function ( id , s , ed ) {
var t = this ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . parent ( id , s , ed ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . settings = s = tinymce . extend ( {
colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF' ,
grid _width : 8 ,
default _color : '#888888'
} , t . settings ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . onShowMenu = new tinymce . util . Dispatcher ( t ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . onHideMenu = new tinymce . util . Dispatcher ( t ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . value = s . default _color ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
showMenu : function ( ) {
var t = this , r , p , e , p2 ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( t . isDisabled ( ) )
return ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! t . isMenuRendered ) {
t . renderMenu ( ) ;
t . isMenuRendered = true ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( t . isMenuVisible )
return t . hideMenu ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
e = DOM . get ( t . id ) ;
DOM . show ( t . id + '_menu' ) ;
DOM . addClass ( e , 'mceSplitButtonSelected' ) ;
p2 = DOM . getPos ( e ) ;
DOM . setStyles ( t . id + '_menu' , {
left : p2 . x ,
2012-05-16 02:18:46 +02:00
top : p2 . y + e . firstChild . clientHeight ,
2012-03-21 04:47:31 +01:00
zIndex : 200000
} ) ;
e = 0 ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
Event . add ( DOM . doc , 'mousedown' , t . hideMenu , t ) ;
t . onShowMenu . dispatch ( t ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( t . _focused ) {
t . _keyHandler = Event . add ( t . id + '_menu' , 'keydown' , function ( e ) {
if ( e . keyCode == 27 )
t . hideMenu ( ) ;
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
DOM . select ( 'a' , t . id + '_menu' ) [ 0 ] . focus ( ) ; // Select first link
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . isMenuVisible = 1 ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
hideMenu : function ( e ) {
var t = this ;
if ( t . isMenuVisible ) {
// Prevent double toogles by canceling the mouse click event to the button
if ( e && e . type == "mousedown" && DOM . getParent ( e . target , function ( e ) { return e . id === t . id + '_open' ; } ) )
return ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! e || ! DOM . getParent ( e . target , '.mceSplitButtonMenu' ) ) {
DOM . removeClass ( t . id , 'mceSplitButtonSelected' ) ;
Event . remove ( DOM . doc , 'mousedown' , t . hideMenu , t ) ;
Event . remove ( t . id + '_menu' , 'keydown' , t . _keyHandler ) ;
DOM . hide ( t . id + '_menu' ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . isMenuVisible = 0 ;
t . onHideMenu . dispatch ( ) ;
}
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
renderMenu : function ( ) {
var t = this , m , i = 0 , s = t . settings , n , tb , tr , w , context ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
w = DOM . add ( s . menu _container , 'div' , { role : 'listbox' , id : t . id + '_menu' , 'class' : s . menu _class + ' ' + s [ 'class' ] , style : 'position:absolute;left:0;top:-1000px;' } ) ;
2012-03-21 04:47:31 +01:00
m = DOM . add ( w , 'div' , { 'class' : s [ 'class' ] + ' mceSplitButtonMenu' } ) ;
DOM . add ( m , 'span' , { 'class' : 'mceMenuLine' } ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
n = DOM . add ( m , 'table' , { role : 'presentation' , 'class' : 'mceColorSplitMenu' } ) ;
tb = DOM . add ( n , 'tbody' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Generate color grid
i = 0 ;
each ( is ( s . colors , 'array' ) ? s . colors : s . colors . split ( ',' ) , function ( c ) {
c = c . replace ( /^#/ , '' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! i -- ) {
tr = DOM . add ( tb , 'tr' ) ;
i = s . grid _width - 1 ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
n = DOM . add ( tr , 'td' ) ;
var settings = {
href : 'javascript:;' ,
style : {
backgroundColor : '#' + c
} ,
'title' : t . editor . getLang ( 'colors.' + c , c ) ,
'data-mce-color' : '#' + c
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// adding a proper ARIA role = button causes JAWS to read things incorrectly on IE.
if ( ! tinymce . isIE ) {
2012-05-16 02:18:46 +02:00
settings . role = 'option' ;
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
n = DOM . add ( n , 'a' , settings ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( t . editor . forcedHighContrastMode ) {
n = DOM . add ( n , 'canvas' , { width : 16 , height : 16 , 'aria-hidden' : 'true' } ) ;
if ( n . getContext && ( context = n . getContext ( "2d" ) ) ) {
context . fillStyle = '#' + c ;
context . fillRect ( 0 , 0 , 16 , 16 ) ;
} else {
// No point leaving a canvas element around if it's not supported for drawing on anyway.
DOM . remove ( n ) ;
}
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
} ) ;
if ( s . more _colors _func ) {
n = DOM . add ( tb , 'tr' ) ;
n = DOM . add ( n , 'td' , { colspan : s . grid _width , 'class' : 'mceMoreColors' } ) ;
n = DOM . add ( n , 'a' , { role : 'option' , id : t . id + '_more' , href : 'javascript:;' , onclick : 'return false;' , 'class' : 'mceMoreColors' } , s . more _colors _title ) ;
Event . add ( n , 'click' , function ( e ) {
s . more _colors _func . call ( s . more _colors _scope || this ) ;
return Event . cancel ( e ) ; // Cancel to fix onbeforeunload problem
} ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
DOM . addClass ( m , 'mceColorSplitMenu' ) ;
new tinymce . ui . KeyboardNavigation ( {
root : t . id + '_menu' ,
items : DOM . select ( 'a' , t . id + '_menu' ) ,
onCancel : function ( ) {
t . hideMenu ( ) ;
t . focus ( ) ;
}
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Prevent IE from scrolling and hindering click to occur #4019
Event . add ( t . id + '_menu' , 'mousedown' , function ( e ) { return Event . cancel ( e ) ; } ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
Event . add ( t . id + '_menu' , 'click' , function ( e ) {
var c ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
e = DOM . getParent ( e . target , 'a' , tb ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( e && e . nodeName . toLowerCase ( ) == 'a' && ( c = e . getAttribute ( 'data-mce-color' ) ) )
t . setColor ( c ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return false ; // Prevent IE auto save warning
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return w ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
setColor : function ( c ) {
this . displayColor ( c ) ;
this . hideMenu ( ) ;
this . settings . onselect ( c ) ;
} ,
displayColor : function ( c ) {
var t = this ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
DOM . setStyle ( t . id + '_preview' , 'backgroundColor' , c ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . value = c ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
postRender : function ( ) {
var t = this , id = t . id ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . parent ( ) ;
DOM . add ( id + '_action' , 'div' , { id : id + '_preview' , 'class' : 'mceColorPreview' } ) ;
DOM . setStyle ( t . id + '_preview' , 'backgroundColor' , t . value ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
destroy : function ( ) {
this . parent ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
Event . clear ( this . id + '_menu' ) ;
Event . clear ( this . id + '_more' ) ;
DOM . remove ( this . id + '_menu' ) ;
}
} ) ;
} ) ( tinymce ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
( function ( tinymce ) {
// Shorten class names
var dom = tinymce . DOM , each = tinymce . each , Event = tinymce . dom . Event ;
tinymce . create ( 'tinymce.ui.ToolbarGroup:tinymce.ui.Container' , {
renderHTML : function ( ) {
var t = this , h = [ ] , controls = t . controls , each = tinymce . each , settings = t . settings ;
h . push ( '<div id="' + t . id + '" role="group" aria-labelledby="' + t . id + '_voice">' ) ;
//TODO: ACC test this out - adding a role = application for getting the landmarks working well.
h . push ( "<span role='application'>" ) ;
h . push ( '<span id="' + t . id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom . encode ( settings . name ) + '</span>' ) ;
each ( controls , function ( toolbar ) {
h . push ( toolbar . renderHTML ( ) ) ;
} ) ;
h . push ( "</span>" ) ;
h . push ( '</div>' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return h . join ( '' ) ;
} ,
focus : function ( ) {
var t = this ;
dom . get ( t . id ) . focus ( ) ;
} ,
postRender : function ( ) {
var t = this , items = [ ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
each ( t . controls , function ( toolbar ) {
each ( toolbar . controls , function ( control ) {
if ( control . id ) {
items . push ( control ) ;
}
} ) ;
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . keyNav = new tinymce . ui . KeyboardNavigation ( {
root : t . id ,
items : items ,
onCancel : function ( ) {
//Move focus if webkit so that navigation back will read the item.
if ( tinymce . isWebKit ) {
dom . get ( t . editor . id + "_ifr" ) . focus ( ) ;
}
t . editor . focus ( ) ;
} ,
excludeFromTabOrder : ! t . settings . tab _focus _toolbar
} ) ;
} ,
destroy : function ( ) {
var self = this ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . parent ( ) ;
self . keyNav . destroy ( ) ;
Event . clear ( self . id ) ;
}
} ) ;
} ) ( tinymce ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
( function ( tinymce ) {
// Shorten class names
var dom = tinymce . DOM , each = tinymce . each ;
tinymce . create ( 'tinymce.ui.Toolbar:tinymce.ui.Container' , {
renderHTML : function ( ) {
var t = this , h = '' , c , co , s = t . settings , i , pr , nx , cl ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
cl = t . controls ;
for ( i = 0 ; i < cl . length ; i ++ ) {
// Get current control, prev control, next control and if the control is a list box or not
co = cl [ i ] ;
pr = cl [ i - 1 ] ;
nx = cl [ i + 1 ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Add toolbar start
if ( i === 0 ) {
c = 'mceToolbarStart' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( co . Button )
c += ' mceToolbarStartButton' ;
else if ( co . SplitButton )
c += ' mceToolbarStartSplitButton' ;
else if ( co . ListBox )
c += ' mceToolbarStartListBox' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
h += dom . createHTML ( 'td' , { 'class' : c } , dom . createHTML ( 'span' , null , '<!-- IE -->' ) ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Add toolbar end before list box and after the previous button
// This is to fix the o2k7 editor skins
if ( pr && co . ListBox ) {
if ( pr . Button || pr . SplitButton )
h += dom . createHTML ( 'td' , { 'class' : 'mceToolbarEnd' } , dom . createHTML ( 'span' , null , '<!-- IE -->' ) ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Render control HTML
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// IE 8 quick fix, needed to propertly generate a hit area for anchors
if ( dom . stdMode )
h += '<td style="position: relative">' + co . renderHTML ( ) + '</td>' ;
else
h += '<td>' + co . renderHTML ( ) + '</td>' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Add toolbar start after list box and before the next button
// This is to fix the o2k7 editor skins
if ( nx && co . ListBox ) {
if ( nx . Button || nx . SplitButton )
h += dom . createHTML ( 'td' , { 'class' : 'mceToolbarStart' } , dom . createHTML ( 'span' , null , '<!-- IE -->' ) ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
c = 'mceToolbarEnd' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( co . Button )
c += ' mceToolbarEndButton' ;
else if ( co . SplitButton )
c += ' mceToolbarEndSplitButton' ;
else if ( co . ListBox )
c += ' mceToolbarEndListBox' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
h += dom . createHTML ( 'td' , { 'class' : c } , dom . createHTML ( 'span' , null , '<!-- IE -->' ) ) ;
return dom . createHTML ( 'table' , { id : t . id , 'class' : 'mceToolbar' + ( s [ 'class' ] ? ' ' + s [ 'class' ] : '' ) , cellpadding : '0' , cellspacing : '0' , align : t . settings . align || '' , role : 'presentation' , tabindex : '-1' } , '<tbody><tr>' + h + '</tr></tbody>' ) ;
}
} ) ;
} ) ( tinymce ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
( function ( tinymce ) {
var Dispatcher = tinymce . util . Dispatcher , each = tinymce . each ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinymce . create ( 'tinymce.AddOnManager' , {
AddOnManager : function ( ) {
var self = this ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . items = [ ] ;
self . urls = { } ;
self . lookup = { } ;
self . onAdd = new Dispatcher ( self ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
get : function ( n ) {
if ( this . lookup [ n ] ) {
return this . lookup [ n ] . instance ;
} else {
return undefined ;
}
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
dependencies : function ( n ) {
var result ;
if ( this . lookup [ n ] ) {
result = this . lookup [ n ] . dependencies ;
}
return result || [ ] ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
requireLangPack : function ( n ) {
var s = tinymce . settings ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( s && s . language && s . language _load !== false )
tinymce . ScriptLoader . add ( this . urls [ n ] + '/langs/' + s . language + '.js' ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
add : function ( id , o , dependencies ) {
this . items . push ( o ) ;
this . lookup [ id ] = { instance : o , dependencies : dependencies } ;
this . onAdd . dispatch ( this , id , o ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return o ;
} ,
createUrl : function ( baseUrl , dep ) {
if ( typeof dep === "object" ) {
return dep
} else {
return { prefix : baseUrl . prefix , resource : dep , suffix : baseUrl . suffix } ;
}
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
addComponents : function ( pluginName , scripts ) {
var pluginUrl = this . urls [ pluginName ] ;
tinymce . each ( scripts , function ( script ) {
tinymce . ScriptLoader . add ( pluginUrl + "/" + script ) ;
} ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
load : function ( n , u , cb , s ) {
var t = this , url = u ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function loadDependencies ( ) {
var dependencies = t . dependencies ( n ) ;
tinymce . each ( dependencies , function ( dep ) {
var newUrl = t . createUrl ( u , dep ) ;
t . load ( newUrl . resource , newUrl , undefined , undefined ) ;
} ) ;
if ( cb ) {
if ( s ) {
cb . call ( s ) ;
} else {
cb . call ( tinymce . ScriptLoader ) ;
}
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( t . urls [ n ] )
return ;
if ( typeof u === "object" )
url = u . prefix + u . resource + u . suffix ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( url . indexOf ( '/' ) !== 0 && url . indexOf ( '://' ) == - 1 )
2012-03-21 04:47:31 +01:00
url = tinymce . baseURL + '/' + url ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . urls [ n ] = url . substring ( 0 , url . lastIndexOf ( '/' ) ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( t . lookup [ n ] ) {
loadDependencies ( ) ;
} else {
tinymce . ScriptLoader . add ( url , loadDependencies , s ) ;
}
}
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Create plugin and theme managers
tinymce . PluginManager = new tinymce . AddOnManager ( ) ;
tinymce . ThemeManager = new tinymce . AddOnManager ( ) ;
} ( tinymce ) ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
( function ( tinymce ) {
// Shorten names
var each = tinymce . each , extend = tinymce . extend ,
DOM = tinymce . DOM , Event = tinymce . dom . Event ,
ThemeManager = tinymce . ThemeManager , PluginManager = tinymce . PluginManager ,
explode = tinymce . explode ,
2012-05-16 02:18:46 +02:00
Dispatcher = tinymce . util . Dispatcher , undef , instanceCounter = 0 ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Setup some URLs where the editor API is located and where the document is
tinymce . documentBaseURL = window . location . href . replace ( /[\?#].*$/ , '' ) . replace ( /[\/\\][^\/]+$/ , '' ) ;
if ( ! /[\/\\]$/ . test ( tinymce . documentBaseURL ) )
tinymce . documentBaseURL += '/' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinymce . baseURL = new tinymce . util . URI ( tinymce . documentBaseURL ) . toAbsolute ( tinymce . baseURL ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinymce . baseURI = new tinymce . util . URI ( tinymce . baseURL ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Add before unload listener
// This was required since IE was leaking memory if you added and removed beforeunload listeners
// with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
tinymce . onBeforeUnload = new Dispatcher ( tinymce ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Must be on window or IE will leak if the editor is placed in frame or iframe
Event . add ( window , 'beforeunload' , function ( e ) {
tinymce . onBeforeUnload . dispatch ( tinymce , e ) ;
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinymce . onAddEditor = new Dispatcher ( tinymce ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinymce . onRemoveEditor = new Dispatcher ( tinymce ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinymce . EditorManager = extend ( tinymce , {
editors : [ ] ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
i18n : { } ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
activeEditor : null ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
init : function ( s ) {
var t = this , pl , sl = tinymce . ScriptLoader , e , el = [ ] , ed ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function createId ( elm ) {
var id = elm . id ;
// Use element id, or unique name or generate a unique id
if ( ! id ) {
id = elm . name ;
if ( id && ! DOM . get ( id ) ) {
id = elm . name ;
} else {
// Generate unique name
id = DOM . uniqueId ( ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
elm . setAttribute ( 'id' , id ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return id ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function execCallback ( se , n , s ) {
var f = se [ n ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! f )
return ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( tinymce . is ( f , 'string' ) ) {
s = f . replace ( /\.\w+$/ , '' ) ;
s = s ? tinymce . resolve ( s ) : 0 ;
f = tinymce . resolve ( f ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return f . apply ( s || this , Array . prototype . slice . call ( arguments , 2 ) ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
function hasClass ( n , c ) {
return c . constructor === RegExp ? c . test ( n . className ) : DOM . hasClass ( n , c ) ;
} ;
2012-03-21 04:47:31 +01:00
s = extend ( {
theme : "simple" ,
language : "en"
} , s ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . settings = s ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Legacy call
Event . bind ( window , 'ready' , function ( ) {
var l , co ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
execCallback ( s , 'onpageload' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
switch ( s . mode ) {
case "exact" :
l = s . elements || '' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( l . length > 0 ) {
each ( explode ( l ) , function ( v ) {
if ( DOM . get ( v ) ) {
ed = new tinymce . Editor ( v , s ) ;
el . push ( ed ) ;
ed . render ( 1 ) ;
} else {
each ( document . forms , function ( f ) {
each ( f . elements , function ( e ) {
if ( e . name === v ) {
v = 'mce_editor_' + instanceCounter ++ ;
DOM . setAttrib ( e , 'id' , v ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
ed = new tinymce . Editor ( v , s ) ;
el . push ( ed ) ;
ed . render ( 1 ) ;
}
} ) ;
} ) ;
}
} ) ;
}
break ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
case "textareas" :
case "specific_textareas" :
each ( DOM . select ( 'textarea' ) , function ( elm ) {
if ( s . editor _deselector && hasClass ( elm , s . editor _deselector ) )
return ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! s . editor _selector || hasClass ( elm , s . editor _selector ) ) {
ed = new tinymce . Editor ( createId ( elm ) , s ) ;
el . push ( ed ) ;
ed . render ( 1 ) ;
}
} ) ;
break ;
default :
if ( s . types ) {
// Process type specific selector
each ( s . types , function ( type ) {
each ( DOM . select ( type . selector ) , function ( elm ) {
var editor = new tinymce . Editor ( createId ( elm ) , tinymce . extend ( { } , s , type ) ) ;
el . push ( editor ) ;
editor . render ( 1 ) ;
} ) ;
} ) ;
} else if ( s . selector ) {
// Process global selector
each ( DOM . select ( s . selector ) , function ( elm ) {
var editor = new tinymce . Editor ( createId ( elm ) , s ) ;
el . push ( editor ) ;
editor . render ( 1 ) ;
} ) ;
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Call onInit when all editors are initialized
if ( s . oninit ) {
l = co = 0 ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
each ( el , function ( ed ) {
co ++ ;
if ( ! ed . initialized ) {
// Wait for it
ed . onInit . add ( function ( ) {
l ++ ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// All done
if ( l == co )
execCallback ( s , 'oninit' ) ;
} ) ;
} else
l ++ ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// All done
if ( l == co )
execCallback ( s , 'oninit' ) ;
} ) ;
}
} ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
get : function ( id ) {
2012-05-16 02:18:46 +02:00
if ( id === undef )
2012-03-21 04:47:31 +01:00
return this . editors ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return this . editors [ id ] ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
getInstanceById : function ( id ) {
return this . get ( id ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
add : function ( editor ) {
var self = this , editors = self . editors ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Add named and index editor instance
editors [ editor . id ] = editor ;
editors . push ( editor ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . _setActive ( editor ) ;
self . onAddEditor . dispatch ( self , editor ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return editor ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
remove : function ( editor ) {
var t = this , i , editors = t . editors ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Not in the collection
if ( ! editors [ editor . id ] )
return null ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
delete editors [ editor . id ] ;
for ( i = 0 ; i < editors . length ; i ++ ) {
if ( editors [ i ] == editor ) {
editors . splice ( i , 1 ) ;
break ;
}
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
// Select another editor since the active one was removed
if ( t . activeEditor == editor )
t . _setActive ( editors [ 0 ] ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
editor . destroy ( ) ;
t . onRemoveEditor . dispatch ( t , editor ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return editor ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
execCommand : function ( c , u , v ) {
var t = this , ed = t . get ( v ) , w ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
function clr ( ) {
ed . destroy ( ) ;
w . detachEvent ( 'onunload' , clr ) ;
w = w . tinyMCE = w . tinymce = null ; // IE leak
} ;
2012-03-21 04:47:31 +01:00
// Manager commands
switch ( c ) {
case "mceFocus" :
ed . focus ( ) ;
return true ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
case "mceAddEditor" :
case "mceAddControl" :
if ( ! t . get ( v ) )
new tinymce . Editor ( v , t . settings ) . render ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return true ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
case "mceAddFrameControl" :
w = v . window ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Add tinyMCE global instance and tinymce namespace to specified window
w . tinyMCE = tinyMCE ;
w . tinymce = tinymce ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinymce . DOM . doc = w . document ;
tinymce . DOM . win = w ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
ed = new tinymce . Editor ( v . element _id , v ) ;
ed . render ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Fix IE memory leaks
if ( tinymce . isIE ) {
w . attachEvent ( 'onunload' , clr ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
v . page _window = null ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return true ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
case "mceRemoveEditor" :
case "mceRemoveControl" :
if ( ed )
ed . remove ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return true ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
case 'mceToggleEditor' :
if ( ! ed ) {
t . execCommand ( 'mceAddControl' , 0 , v ) ;
return true ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
if ( ed . isHidden ( ) )
ed . show ( ) ;
else
ed . hide ( ) ;
return true ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
// Run command on active editor
if ( t . activeEditor )
return t . activeEditor . execCommand ( c , u , v ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return false ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
execInstanceCommand : function ( id , c , u , v ) {
var ed = this . get ( id ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ed )
return ed . execCommand ( c , u , v ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return false ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
triggerSave : function ( ) {
each ( this . editors , function ( e ) {
e . save ( ) ;
} ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
addI18n : function ( p , o ) {
var lo , i18n = this . i18n ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! tinymce . is ( p , 'string' ) ) {
each ( p , function ( o , lc ) {
each ( o , function ( o , g ) {
each ( o , function ( o , k ) {
if ( g === 'common' )
i18n [ lc + '.' + k ] = o ;
else
i18n [ lc + '.' + g + '.' + k ] = o ;
} ) ;
} ) ;
} ) ;
} else {
each ( o , function ( o , k ) {
i18n [ p + '.' + k ] = o ;
2010-07-02 01:48:07 +02:00
} ) ;
}
2012-03-21 04:47:31 +01:00
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Private methods
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
_setActive : function ( editor ) {
this . selectedInstance = this . activeEditor = editor ;
}
} ) ;
} ) ( tinymce ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
( function ( tinymce ) {
// Shorten these names
var DOM = tinymce . DOM , Event = tinymce . dom . Event , extend = tinymce . extend ,
2012-05-16 02:18:46 +02:00
each = tinymce . each , isGecko = tinymce . isGecko ,
2012-03-21 04:47:31 +01:00
isIE = tinymce . isIE , isWebKit = tinymce . isWebKit , is = tinymce . is ,
ThemeManager = tinymce . ThemeManager , PluginManager = tinymce . PluginManager ,
2012-05-16 02:18:46 +02:00
explode = tinymce . explode ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinymce . create ( 'tinymce.Editor' , {
2012-05-16 02:18:46 +02:00
Editor : function ( id , settings ) {
var self = this , TRUE = true ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
self . settings = settings = extend ( {
2012-03-21 04:47:31 +01:00
id : id ,
language : 'en' ,
theme : 'simple' ,
skin : 'default' ,
delta _width : 0 ,
delta _height : 0 ,
popup _css : '' ,
plugins : '' ,
document _base _url : tinymce . documentBaseURL ,
2012-05-16 02:18:46 +02:00
add _form _submit _trigger : TRUE ,
submit _patch : TRUE ,
add _unload _trigger : TRUE ,
convert _urls : TRUE ,
relative _urls : TRUE ,
remove _script _host : TRUE ,
table _inline _editing : false ,
object _resizing : TRUE ,
accessibility _focus : TRUE ,
2012-03-21 04:47:31 +01:00
doctype : tinymce . isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>' , // Use old doctype on IE 6 to avoid horizontal scroll
2012-05-16 02:18:46 +02:00
visual : TRUE ,
2012-03-21 04:47:31 +01:00
font _size _style _values : 'xx-small,x-small,small,medium,large,x-large,xx-large' ,
font _size _legacy _values : 'xx-small,small,medium,large,x-large,xx-large,300%' , // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
2012-05-16 02:18:46 +02:00
apply _source _formatting : TRUE ,
2012-03-21 04:47:31 +01:00
directionality : 'ltr' ,
forced _root _block : 'p' ,
2012-05-16 02:18:46 +02:00
hidden _input : TRUE ,
padd _empty _editor : TRUE ,
render _ui : TRUE ,
2012-03-21 04:47:31 +01:00
indentation : '30px' ,
2012-05-16 02:18:46 +02:00
fix _table _elements : TRUE ,
inline _styles : TRUE ,
convert _fonts _to _spans : TRUE ,
2012-03-21 04:47:31 +01:00
indent : 'simple' ,
2012-05-16 02:18:46 +02:00
indent _before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure' ,
indent _after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure' ,
validate : TRUE ,
2012-03-21 04:47:31 +01:00
entity _encoding : 'named' ,
2012-05-16 02:18:46 +02:00
url _converter : self . convertURL ,
url _converter _scope : self ,
ie7 _compat : TRUE
} , settings ) ;
self . id = self . editorId = id ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
self . isNotDirty = false ;
self . plugins = { } ;
self . documentBaseURI = new tinymce . util . URI ( settings . document _base _url || tinymce . documentBaseURL , {
2012-03-21 04:47:31 +01:00
base _uri : tinyMCE . baseURI
2010-07-02 01:48:07 +02:00
} ) ;
2012-05-16 02:18:46 +02:00
self . baseURI = tinymce . baseURI ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
self . contentCSS = [ ] ;
// Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic
self . setupEvents ( ) ;
// Internal command handler objects
self . execCommands = { } ;
self . queryStateCommands = { } ;
self . queryValueCommands = { } ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Call setup
2012-05-16 02:18:46 +02:00
self . execCallback ( 'setup' , self ) ;
2012-03-21 04:47:31 +01:00
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
render : function ( nst ) {
var t = this , s = t . settings , id = t . id , sl = tinymce . ScriptLoader ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Page is not loaded yet, wait for it
if ( ! Event . domLoaded ) {
Event . add ( window , 'ready' , function ( ) {
t . render ( ) ;
} ) ;
return ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinyMCE . settings = s ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Element not found, then skip initialization
if ( ! t . getElement ( ) )
return ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff
2012-05-16 02:18:46 +02:00
// here since the browser says it has contentEditable support but there is no visible caret.
2012-03-21 04:47:31 +01:00
if ( tinymce . isIDevice && ! tinymce . isIOS5 )
return ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Add hidden input for non input elements inside form elements
if ( ! /TEXTAREA|INPUT/i . test ( t . getElement ( ) . nodeName ) && s . hidden _input && DOM . getParent ( id , 'form' ) )
DOM . insertAfter ( DOM . create ( 'input' , { type : 'hidden' , name : id } ) , id ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( tinymce . WindowManager )
t . windowManager = new tinymce . WindowManager ( t ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( s . encoding == 'xml' ) {
t . onGetContent . add ( function ( ed , o ) {
if ( o . save )
o . content = DOM . encode ( o . content ) ;
2010-07-02 01:48:07 +02:00
} ) ;
}
2012-03-21 04:47:31 +01:00
if ( s . add _form _submit _trigger ) {
t . onSubmit . addToTop ( function ( ) {
if ( t . initialized ) {
t . save ( ) ;
t . isNotDirty = 1 ;
}
2010-07-02 01:48:07 +02:00
} ) ;
}
2012-03-21 04:47:31 +01:00
if ( s . add _unload _trigger ) {
t . _beforeUnload = tinyMCE . onBeforeUnload . add ( function ( ) {
if ( t . initialized && ! t . destroyed && ! t . isHidden ( ) )
t . save ( { format : 'raw' , no _events : true } ) ;
2010-07-02 01:48:07 +02:00
} ) ;
}
2012-03-21 04:47:31 +01:00
tinymce . addUnload ( t . destroy , t ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( s . submit _patch ) {
t . onBeforeRenderUI . add ( function ( ) {
var n = t . getElement ( ) . form ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! n )
return ;
// Already patched
if ( n . _mceOldSubmit )
return ;
// Check page uses id="submit" or name="submit" for it's submit button
if ( ! n . submit . nodeType && ! n . submit . length ) {
t . formElement = n ;
n . _mceOldSubmit = n . submit ;
n . submit = function ( ) {
// Save all instances
tinymce . triggerSave ( ) ;
t . isNotDirty = 1 ;
return t . formElement . _mceOldSubmit ( t . formElement ) ;
} ;
}
n = null ;
2010-07-02 01:48:07 +02:00
} ) ;
}
2012-03-21 04:47:31 +01:00
// Load scripts
function loadScripts ( ) {
if ( s . language && s . language _load !== false )
sl . add ( tinymce . baseURL + '/langs/' + s . language + '.js' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( s . theme && s . theme . charAt ( 0 ) != '-' && ! ThemeManager . urls [ s . theme ] )
ThemeManager . load ( s . theme , 'themes/' + s . theme + '/editor_template' + tinymce . suffix + '.js' ) ;
each ( explode ( s . plugins ) , function ( p ) {
if ( p && ! PluginManager . urls [ p ] ) {
if ( p . charAt ( 0 ) == '-' ) {
p = p . substr ( 1 , p . length ) ;
var dependencies = PluginManager . dependencies ( p ) ;
each ( dependencies , function ( dep ) {
var defaultSettings = { prefix : 'plugins/' , resource : dep , suffix : '/editor_plugin' + tinymce . suffix + '.js' } ;
2012-05-16 02:18:46 +02:00
dep = PluginManager . createUrl ( defaultSettings , dep ) ;
2012-03-21 04:47:31 +01:00
PluginManager . load ( dep . resource , dep ) ;
} ) ;
} else {
// Skip safari plugin, since it is removed as of 3.3b1
if ( p == 'safari' ) {
return ;
}
PluginManager . load ( p , { prefix : 'plugins/' , resource : p , suffix : '/editor_plugin' + tinymce . suffix + '.js' } ) ;
}
}
2010-07-02 01:48:07 +02:00
} ) ;
2012-03-21 04:47:31 +01:00
// Init when que is loaded
sl . loadQueue ( function ( ) {
if ( ! t . removed )
t . init ( ) ;
} ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
loadScripts ( ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
init : function ( ) {
var n , t = this , s = t . settings , w , h , e = t . getElement ( ) , o , ti , u , bi , bc , re , i , initializedPlugins = [ ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinymce . add ( t ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
s . aria _label = s . aria _label || DOM . getAttrib ( e , 'aria-label' , t . getLang ( 'aria.rich_text_area' ) ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( s . theme ) {
s . theme = s . theme . replace ( /-/ , '' ) ;
o = ThemeManager . get ( s . theme ) ;
t . theme = new o ( ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( t . theme . init )
2012-03-21 04:47:31 +01:00
t . theme . init ( t , ThemeManager . urls [ s . theme ] || tinymce . documentBaseURL . replace ( /\/$/ , '' ) ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
function initPlugin ( p ) {
var c = PluginManager . get ( p ) , u = PluginManager . urls [ p ] || tinymce . documentBaseURL . replace ( /\/$/ , '' ) , po ;
if ( c && tinymce . inArray ( initializedPlugins , p ) === - 1 ) {
each ( PluginManager . dependencies ( p ) , function ( dep ) {
initPlugin ( dep ) ;
} ) ;
po = new c ( t , u ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . plugins [ p ] = po ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( po . init ) {
po . init ( t , u ) ;
initializedPlugins . push ( p ) ;
}
}
}
// Create all plugins
each ( explode ( s . plugins . replace ( /\-/g , '' ) ) , initPlugin ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Setup popup CSS path(s)
if ( s . popup _css !== false ) {
if ( s . popup _css )
s . popup _css = t . documentBaseURI . toAbsolute ( s . popup _css ) ;
else
s . popup _css = t . baseURI . toAbsolute ( "themes/" + s . theme + "/skins/" + s . skin + "/dialog.css" ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( s . popup _css _add )
s . popup _css += ',' + t . documentBaseURI . toAbsolute ( s . popup _css _add ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . controlManager = new tinymce . ControlManager ( t ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . onExecCommand . add ( function ( ed , c ) {
// Don't refresh the select lists until caret move
if ( ! /^(FontName|FontSize)$/ . test ( c ) )
t . nodeChanged ( ) ;
2010-07-02 01:48:07 +02:00
} ) ;
2012-03-21 04:47:31 +01:00
// Enables users to override the control factory
t . onBeforeRenderUI . dispatch ( t , t . controlManager ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Measure box
2012-05-16 02:18:46 +02:00
if ( s . render _ui && t . theme ) {
2012-03-21 04:47:31 +01:00
w = s . width || e . style . width || e . offsetWidth ;
h = s . height || e . style . height || e . offsetHeight ;
t . orgDisplay = e . style . display ;
re = /^[0-9\.]+(|px)$/i ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( re . test ( '' + w ) )
2012-05-16 02:18:46 +02:00
w = Math . max ( parseInt ( w , 10 ) + ( o . deltaWidth || 0 ) , 100 ) ;
2012-03-21 04:47:31 +01:00
if ( re . test ( '' + h ) )
2012-05-16 02:18:46 +02:00
h = Math . max ( parseInt ( h , 10 ) + ( o . deltaHeight || 0 ) , 100 ) ;
2012-03-21 04:47:31 +01:00
// Render UI
o = t . theme . renderUI ( {
targetNode : e ,
width : w ,
height : h ,
deltaWidth : s . delta _width ,
deltaHeight : s . delta _height
2010-07-02 01:48:07 +02:00
} ) ;
2012-03-21 04:47:31 +01:00
t . editorContainer = o . editorContainer ;
}
2012-05-16 02:18:46 +02:00
// Load specified content CSS last
if ( s . content _css ) {
each ( explode ( s . content _css ) , function ( u ) {
t . contentCSS . push ( t . documentBaseURI . toAbsolute ( u ) ) ;
} ) ;
}
// Content editable mode ends here
if ( s . content _editable ) {
e = n = o = null ; // Fix IE leak
return t . initContentBody ( ) ;
}
2012-03-21 04:47:31 +01:00
// User specified a document.domain value
if ( document . domain && location . hostname != document . domain )
tinymce . relaxedDomain = document . domain ;
// Resize editor
DOM . setStyles ( o . sizeContainer || o . editorContainer , {
width : w ,
height : h
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
h = ( o . iframeHeight || h ) + ( typeof ( h ) == 'number' ? ( o . deltaHeight || 0 ) : '' ) ;
if ( h < 100 )
h = 100 ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . iframeHTML = s . doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// We only need to override paths if we have to
// IE has a bug where it remove site absolute urls to relative ones if this is specified
if ( s . document _base _url != tinymce . documentBaseURL )
t . iframeHTML += '<base href="' + t . documentBaseURI . getURI ( ) + '" />' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
if ( s . ie7 _compat )
t . iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />' ;
else
t . iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Load the CSS by injecting them into the HTML this will reduce "flicker"
for ( i = 0 ; i < t . contentCSS . length ; i ++ ) {
t . iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t . contentCSS [ i ] + '" />' ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . contentCSS = [ ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
bi = s . body _id || 'tinymce' ;
if ( bi . indexOf ( '=' ) != - 1 ) {
bi = t . getParam ( 'body_id' , '' , 'hash' ) ;
bi = bi [ t . id ] || bi ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
bc = s . body _class || '' ;
if ( bc . indexOf ( '=' ) != - 1 ) {
bc = t . getParam ( 'body_class' , '' , 'hash' ) ;
bc = bc [ t . id ] || '' ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '" onload="window.parent.tinyMCE.get(\'' + t . id + '\').onLoad.dispatch();"><br></body></html>' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Domain relaxing enabled, then set document domain
if ( tinymce . relaxedDomain && ( isIE || ( tinymce . isOpera && parseFloat ( opera . version ( ) ) < 11 ) ) ) {
// We need to write the contents here in IE since multiple writes messes up refresh button and back button
2012-05-16 02:18:46 +02:00
u = 'javascript:(function(){document.open();document.domain="' + document . domain + '";var ed = window.parent.tinyMCE.get("' + t . id + '");document.write(ed.iframeHTML);document.close();ed.initContentBody();})()' ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
// Create iframe
// TODO: ACC add the appropriate description on this.
n = DOM . add ( o . iframeContainer , 'iframe' , {
id : t . id + "_ifr" ,
src : u || 'javascript:""' , // Workaround for HTTPS warning in IE6/7
frameBorder : '0' ,
allowTransparency : "true" ,
title : s . aria _label ,
style : {
width : '100%' ,
height : h ,
display : 'block' // Important for Gecko to render the iframe correctly
}
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . contentAreaContainer = o . iframeContainer ;
DOM . get ( o . editorContainer ) . style . display = t . orgDisplay ;
DOM . get ( t . id ) . style . display = 'none' ;
DOM . setAttrib ( t . id , 'aria-hidden' , true ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! tinymce . relaxedDomain || ! u )
2012-05-16 02:18:46 +02:00
t . initContentBody ( ) ;
2012-03-21 04:47:31 +01:00
e = n = o = null ; // Cleanup
2010-07-02 01:48:07 +02:00
} ,
2012-05-16 02:18:46 +02:00
initContentBody : function ( ) {
var self = this , settings = self . settings , targetElm = DOM . get ( self . id ) , doc = self . getDoc ( ) , html , body ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Setup iframe body
2012-05-16 02:18:46 +02:00
if ( ( ! isIE || ! tinymce . relaxedDomain ) && ! settings . content _editable ) {
doc . open ( ) ;
doc . write ( self . iframeHTML ) ;
doc . close ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( tinymce . relaxedDomain )
2012-05-16 02:18:46 +02:00
doc . domain = tinymce . relaxedDomain ;
}
if ( settings . content _editable ) {
DOM . addClass ( targetElm , 'mceContentBody' ) ;
self . contentDocument = doc = settings . content _document || document ;
self . contentWindow = settings . content _window || window ;
self . bodyElement = targetElm ;
// Prevent leak in IE
settings . content _document = settings . content _window = null ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
// It will not steal focus while setting contentEditable
2012-05-16 02:18:46 +02:00
body = self . getBody ( ) ;
body . disabled = true ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( ! settings . readonly )
body . contentEditable = self . getParam ( 'content_editable_state' , true ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
body . disabled = false ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
self . schema = new tinymce . html . Schema ( settings ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
self . dom = new tinymce . dom . DOMUtils ( doc , {
2012-03-21 04:47:31 +01:00
keep _values : true ,
2012-05-16 02:18:46 +02:00
url _converter : self . convertURL ,
url _converter _scope : self ,
hex _colors : settings . force _hex _style _colors ,
class _filter : settings . class _filter ,
update _styles : true ,
root _element : settings . content _editable ? self . id : null ,
schema : self . schema
2010-07-02 01:48:07 +02:00
} ) ;
2012-05-16 02:18:46 +02:00
self . parser = new tinymce . html . DomParser ( settings , self . schema ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Convert src and href into data-mce-src, data-mce-href and data-mce-style
2012-05-16 02:18:46 +02:00
self . parser . addAttributeFilter ( 'src,href,style' , function ( nodes , name ) {
var i = nodes . length , node , dom = self . dom , value , internalName ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
while ( i -- ) {
node = nodes [ i ] ;
value = node . attr ( name ) ;
internalName = 'data-mce-' + name ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Add internal attribute if we need to we don't on a refresh of the document
if ( ! node . attributes . map [ internalName ] ) {
if ( name === "style" )
node . attr ( internalName , dom . serializeStyle ( dom . parseStyle ( value ) , node . name ) ) ;
2010-07-02 01:48:07 +02:00
else
2012-05-16 02:18:46 +02:00
node . attr ( internalName , self . convertURL ( value , name , node . name ) ) ;
2012-03-21 04:47:31 +01:00
}
}
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Keep scripts from executing
2012-05-16 02:18:46 +02:00
self . parser . addNodeFilter ( 'script' , function ( nodes , name ) {
2012-03-21 04:47:31 +01:00
var i = nodes . length , node ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
while ( i -- ) {
node = nodes [ i ] ;
node . attr ( 'type' , 'mce-' + ( node . attr ( 'type' ) || 'text/javascript' ) ) ;
}
} ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
self . parser . addNodeFilter ( '#cdata' , function ( nodes , name ) {
2012-03-21 04:47:31 +01:00
var i = nodes . length , node ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
while ( i -- ) {
node = nodes [ i ] ;
node . type = 8 ;
node . name = '#comment' ;
node . value = '[CDATA[' + node . value + ']]' ;
}
} ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
self . parser . addNodeFilter ( 'p,h1,h2,h3,h4,h5,h6,div' , function ( nodes , name ) {
var i = nodes . length , node , nonEmptyElements = self . schema . getNonEmptyElements ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
while ( i -- ) {
node = nodes [ i ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( node . isEmpty ( nonEmptyElements ) )
node . empty ( ) . append ( new tinymce . html . Node ( 'br' , 1 ) ) . shortEnded = true ;
}
} ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
self . serializer = new tinymce . dom . Serializer ( settings , self . dom , self . schema ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
self . selection = new tinymce . dom . Selection ( self . dom , self . getWin ( ) , self . serializer ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
self . formatter = new tinymce . Formatter ( self ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
self . undoManager = new tinymce . UndoManager ( self ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
self . forceBlocks = new tinymce . ForceBlocks ( self ) ;
self . enterKey = new tinymce . EnterKey ( self ) ;
self . editorCommands = new tinymce . EditorCommands ( self ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Pass through
2012-05-16 02:18:46 +02:00
self . serializer . onPreProcess . add ( function ( se , o ) {
return self . onPreProcess . dispatch ( self , o , se ) ;
2010-07-02 01:48:07 +02:00
} ) ;
2012-05-16 02:18:46 +02:00
self . serializer . onPostProcess . add ( function ( se , o ) {
return self . onPostProcess . dispatch ( self , o , se ) ;
2012-03-21 04:47:31 +01:00
} ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
self . onPreInit . dispatch ( self ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( ! settings . gecko _spellcheck )
doc . body . spellcheck = false ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( ! settings . readonly ) {
self . bindNativeEvents ( ) ;
2010-07-02 01:48:07 +02:00
}
2012-05-16 02:18:46 +02:00
self . controlManager . onPostRender . dispatch ( self , self . controlManager ) ;
self . onPostRender . dispatch ( self ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
self . quirks = tinymce . util . Quirks ( self ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( settings . directionality )
body . dir = settings . directionality ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( settings . nowrap )
body . style . whiteSpace = "nowrap" ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( settings . protect ) {
self . onBeforeSetContent . add ( function ( ed , o ) {
each ( settings . protect , function ( pattern ) {
o . content = o . content . replace ( pattern , function ( str ) {
return '<!--mce:protected ' + escape ( str ) + '-->' ;
} ) ;
} ) ;
2012-03-21 04:47:31 +01:00
} ) ;
}
// Add visual aids when new contents is added
2012-05-16 02:18:46 +02:00
self . onSetContent . add ( function ( ) {
self . addVisual ( self . getBody ( ) ) ;
2012-03-21 04:47:31 +01:00
} ) ;
// Remove empty contents
2012-05-16 02:18:46 +02:00
if ( settings . padd _empty _editor ) {
self . onPostProcess . add ( function ( ed , o ) {
2012-03-21 04:47:31 +01:00
o . content = o . content . replace ( /^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/ , '' ) ;
} ) ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
self . load ( { initial : true , format : 'html' } ) ;
self . startContent = self . getContent ( { format : 'raw' } ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
self . initialized = true ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
self . onInit . dispatch ( self ) ;
self . execCallback ( 'setupcontent_callback' , self . id , body , doc ) ;
self . execCallback ( 'init_instance_callback' , self ) ;
self . focus ( true ) ;
self . nodeChanged ( { initial : true } ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Load specified content CSS last
2012-05-16 02:18:46 +02:00
each ( self . contentCSS , function ( url ) {
self . dom . loadCSS ( url ) ;
2012-03-21 04:47:31 +01:00
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Handle auto focus
2012-05-16 02:18:46 +02:00
if ( settings . auto _focus ) {
2012-03-21 04:47:31 +01:00
setTimeout ( function ( ) {
2012-05-16 02:18:46 +02:00
var ed = tinymce . get ( settings . auto _focus ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
ed . selection . select ( ed . getBody ( ) , 1 ) ;
ed . selection . collapse ( 1 ) ;
ed . getBody ( ) . focus ( ) ;
ed . getWin ( ) . focus ( ) ;
} , 100 ) ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Clean up references for IE
targetElm = doc = body = null ;
2012-03-21 04:47:31 +01:00
} ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
focus : function ( skip _focus ) {
var oed , self = this , selection = self . selection , contentEditable = self . settings . content _editable , ieRng , controlElm , doc = self . getDoc ( ) , body ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( ! skip _focus ) {
2012-03-21 04:47:31 +01:00
// Get selected control element
ieRng = selection . getRng ( ) ;
if ( ieRng . item ) {
controlElm = ieRng . item ( 0 ) ;
2010-07-02 01:48:07 +02:00
}
2012-05-16 02:18:46 +02:00
self . _refreshContentEditable ( ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Focus the window iframe
if ( ! contentEditable ) {
self . getWin ( ) . focus ( ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Focus the body as well since it's contentEditable
2012-05-16 02:18:46 +02:00
if ( tinymce . isGecko || contentEditable ) {
body = self . getBody ( ) ;
// Check for setActive since it doesn't scroll to the element
if ( body . setActive ) {
body . setActive ( ) ;
} else {
body . focus ( ) ;
}
if ( contentEditable ) {
selection . normalize ( ) ;
}
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Restore selected control element
// This is needed when for example an image is selected within a
// layer a call to focus will then remove the control selection
if ( controlElm && controlElm . ownerDocument == doc ) {
ieRng = doc . body . createControlRange ( ) ;
ieRng . addElement ( controlElm ) ;
ieRng . select ( ) ;
}
2010-07-02 01:48:07 +02:00
}
2012-05-16 02:18:46 +02:00
if ( tinymce . activeEditor != self ) {
2012-03-21 04:47:31 +01:00
if ( ( oed = tinymce . activeEditor ) != null )
2012-05-16 02:18:46 +02:00
oed . onDeactivate . dispatch ( oed , self ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
self . onActivate . dispatch ( self , oed ) ;
2010-07-02 01:48:07 +02:00
}
2012-05-16 02:18:46 +02:00
tinymce . _setActive ( self ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
execCallback : function ( n ) {
var t = this , f = t . settings [ n ] , s ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! f )
return ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Look through lookup
if ( t . callbackLookup && ( s = t . callbackLookup [ n ] ) ) {
f = s . func ;
s = s . scope ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( is ( f , 'string' ) ) {
s = f . replace ( /\.\w+$/ , '' ) ;
s = s ? tinymce . resolve ( s ) : 0 ;
f = tinymce . resolve ( f ) ;
t . callbackLookup = t . callbackLookup || { } ;
t . callbackLookup [ n ] = { func : f , scope : s } ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return f . apply ( s || t , Array . prototype . slice . call ( arguments , 1 ) ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
translate : function ( s ) {
var c = this . settings . language || 'en' , i18n = tinymce . i18n ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! s )
return '' ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
return i18n [ c + '.' + s ] || s . replace ( /\{\#([^\}]+)\}/g , function ( a , b ) {
2012-03-21 04:47:31 +01:00
return i18n [ c + '.' + b ] || '{#' + b + '}' ;
} ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
getLang : function ( n , dv ) {
return tinymce . i18n [ ( this . settings . language || 'en' ) + '.' + n ] || ( is ( dv ) ? dv : '{#' + n + '}' ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
getParam : function ( n , dv , ty ) {
var tr = tinymce . trim , v = is ( this . settings [ n ] ) ? this . settings [ n ] : dv , o ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ty === 'hash' ) {
o = { } ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( is ( v , 'string' ) ) {
each ( v . indexOf ( '=' ) > 0 ? v . split ( /[;,](?![^=;,]*(?:[;,]|$))/ ) : v . split ( ',' ) , function ( v ) {
v = v . split ( '=' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( v . length > 1 )
o [ tr ( v [ 0 ] ) ] = tr ( v [ 1 ] ) ;
else
o [ tr ( v [ 0 ] ) ] = tr ( v ) ;
} ) ;
} else
o = v ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return o ;
}
return v ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
nodeChanged : function ( o ) {
2012-05-16 02:18:46 +02:00
var self = this , selection = self . selection , node ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Fix for bug #1896577 it seems that this can not be fired while the editor is loading
2012-05-16 02:18:46 +02:00
if ( self . initialized ) {
2012-03-21 04:47:31 +01:00
o = o || { } ;
2012-05-16 02:18:46 +02:00
// Get start node
node = selection . getStart ( ) || self . getBody ( ) ;
node = isIE && node . ownerDocument != self . getDoc ( ) ? self . getBody ( ) : node ; // Fix for IE initial state
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Get parents and add them to object
o . parents = [ ] ;
2012-05-16 02:18:46 +02:00
self . dom . getParent ( node , function ( node ) {
2012-03-21 04:47:31 +01:00
if ( node . nodeName == 'BODY' )
return true ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
o . parents . push ( node ) ;
} ) ;
2012-05-16 02:18:46 +02:00
self . onNodeChange . dispatch (
self ,
o ? o . controlManager || self . controlManager : self . controlManager ,
node ,
selection . isCollapsed ( ) ,
2012-03-21 04:47:31 +01:00
o
) ;
}
2010-07-02 01:48:07 +02:00
} ,
2012-05-16 02:18:46 +02:00
addButton : function ( name , settings ) {
var self = this ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
self . buttons = self . buttons || { } ;
self . buttons [ name ] = settings ;
2012-03-21 04:47:31 +01:00
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
addCommand : function ( name , callback , scope ) {
this . execCommands [ name ] = { func : callback , scope : scope || this } ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
addQueryStateHandler : function ( name , callback , scope ) {
this . queryStateCommands [ name ] = { func : callback , scope : scope || this } ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
addQueryValueHandler : function ( name , callback , scope ) {
this . queryValueCommands [ name ] = { func : callback , scope : scope || this } ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
addShortcut : function ( pa , desc , cmd _func , sc ) {
var t = this , c ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( t . settings . custom _shortcuts === false )
2012-03-21 04:47:31 +01:00
return false ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . shortcuts = t . shortcuts || { } ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( is ( cmd _func , 'string' ) ) {
c = cmd _func ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
cmd _func = function ( ) {
t . execCommand ( c , false , null ) ;
} ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( is ( cmd _func , 'object' ) ) {
c = cmd _func ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
cmd _func = function ( ) {
t . execCommand ( c [ 0 ] , c [ 1 ] , c [ 2 ] ) ;
} ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
each ( explode ( pa ) , function ( pa ) {
var o = {
func : cmd _func ,
scope : sc || this ,
2012-05-16 02:18:46 +02:00
desc : t . translate ( desc ) ,
2012-03-21 04:47:31 +01:00
alt : false ,
ctrl : false ,
shift : false
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
each ( explode ( pa , '+' ) , function ( v ) {
switch ( v ) {
case 'alt' :
case 'ctrl' :
case 'shift' :
o [ v ] = true ;
break ;
default :
o . charCode = v . charCodeAt ( 0 ) ;
o . keyCode = v . toUpperCase ( ) . charCodeAt ( 0 ) ;
}
} ) ;
t . shortcuts [ ( o . ctrl ? 'ctrl' : '' ) + ',' + ( o . alt ? 'alt' : '' ) + ',' + ( o . shift ? 'shift' : '' ) + ',' + o . keyCode ] = o ;
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return true ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
execCommand : function ( cmd , ui , val , a ) {
var t = this , s = 0 , o , st ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! /^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/ . test ( cmd ) && ( ! a || ! a . skip _focus ) )
t . focus ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
a = extend ( { } , a ) ;
t . onBeforeExecCommand . dispatch ( t , cmd , ui , val , a ) ;
if ( a . terminate )
return false ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Command callback
if ( t . execCallback ( 'execcommand_callback' , t . id , t . selection . getNode ( ) , cmd , ui , val ) ) {
t . onExecCommand . dispatch ( t , cmd , ui , val , a ) ;
return true ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Registred commands
if ( o = t . execCommands [ cmd ] ) {
st = o . func . call ( o . scope , ui , val ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Fall through on true
if ( st !== true ) {
t . onExecCommand . dispatch ( t , cmd , ui , val , a ) ;
return st ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Plugin commands
each ( t . plugins , function ( p ) {
if ( p . execCommand && p . execCommand ( cmd , ui , val ) ) {
t . onExecCommand . dispatch ( t , cmd , ui , val , a ) ;
s = 1 ;
return false ;
}
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( s )
return true ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Theme commands
if ( t . theme && t . theme . execCommand && t . theme . execCommand ( cmd , ui , val ) ) {
t . onExecCommand . dispatch ( t , cmd , ui , val , a ) ;
return true ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Editor commands
if ( t . editorCommands . execCommand ( cmd , ui , val ) ) {
t . onExecCommand . dispatch ( t , cmd , ui , val , a ) ;
return true ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Browser commands
t . getDoc ( ) . execCommand ( cmd , ui , val ) ;
t . onExecCommand . dispatch ( t , cmd , ui , val , a ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
queryCommandState : function ( cmd ) {
var t = this , o , s ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Is hidden then return undefined
if ( t . _isHidden ( ) )
2010-07-02 01:48:07 +02:00
return ;
2012-03-21 04:47:31 +01:00
// Registred commands
if ( o = t . queryStateCommands [ cmd ] ) {
s = o . func . call ( o . scope ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Fall though on true
if ( s !== true )
return s ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Registred commands
o = t . editorCommands . queryCommandState ( cmd ) ;
if ( o !== - 1 )
return o ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Browser commands
try {
return this . getDoc ( ) . queryCommandState ( cmd ) ;
} catch ( ex ) {
// Fails sometimes see bug: 1896577
}
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
queryCommandValue : function ( c ) {
var t = this , o , s ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Is hidden then return undefined
if ( t . _isHidden ( ) )
return ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Registred commands
if ( o = t . queryValueCommands [ c ] ) {
s = o . func . call ( o . scope ) ;
// Fall though on true
if ( s !== true )
return s ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
// Registred commands
o = t . editorCommands . queryCommandValue ( c ) ;
if ( is ( o ) )
return o ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Browser commands
try {
return this . getDoc ( ) . queryCommandValue ( c ) ;
} catch ( ex ) {
// Fails sometimes see bug: 1896577
}
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
show : function ( ) {
2012-05-16 02:18:46 +02:00
var self = this ;
2012-03-21 04:47:31 +01:00
2012-05-16 02:18:46 +02:00
DOM . show ( self . getContainer ( ) ) ;
DOM . hide ( self . id ) ;
self . load ( ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
hide : function ( ) {
2012-05-16 02:18:46 +02:00
var self = this , doc = self . getDoc ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Fixed bug where IE has a blinking cursor left from the editor
2012-05-16 02:18:46 +02:00
if ( isIE && doc )
doc . execCommand ( 'SelectAll' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// We must save before we hide so Safari doesn't crash
2012-05-16 02:18:46 +02:00
self . save ( ) ;
DOM . hide ( self . getContainer ( ) ) ;
DOM . setStyle ( self . id , 'display' , self . orgDisplay ) ;
2012-03-21 04:47:31 +01:00
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
isHidden : function ( ) {
return ! DOM . isHidden ( this . id ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
setProgressState : function ( b , ti , o ) {
this . onSetProgressState . dispatch ( this , b , ti , o ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return b ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
load : function ( o ) {
var t = this , e = t . getElement ( ) , h ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( e ) {
o = o || { } ;
o . load = true ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Double encode existing entities in the value
h = t . setContent ( is ( e . value ) ? e . value : e . innerHTML , o ) ;
o . element = e ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! o . no _events )
t . onLoadContent . dispatch ( t , o ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
o . element = e = null ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return h ;
}
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
save : function ( o ) {
var t = this , e = t . getElement ( ) , h , f ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! e || ! t . initialized )
return ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
o = o || { } ;
o . save = true ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
o . element = e ;
h = o . content = t . getContent ( o ) ;
if ( ! o . no _events )
t . onSaveContent . dispatch ( t , o ) ;
h = o . content ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! /TEXTAREA|INPUT/i . test ( e . nodeName ) ) {
e . innerHTML = h ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Update hidden form element
if ( f = DOM . getParent ( t . id , 'form' ) ) {
each ( f . elements , function ( e ) {
if ( e . name == t . id ) {
e . value = h ;
return false ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
} ) ;
}
} else
e . value = h ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
o . element = e = null ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return h ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
setContent : function ( content , args ) {
var self = this , rootNode , body = self . getBody ( ) , forcedRootBlockName ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Setup args object
args = args || { } ;
args . format = args . format || 'html' ;
args . set = true ;
args . content = content ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Do preprocessing
if ( ! args . no _events )
self . onBeforeSetContent . dispatch ( self , args ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
content = args . content ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
// It will also be impossible to place the caret in the editor unless there is a BR element present
if ( ! tinymce . isIE && ( content . length === 0 || /^\s+$/ . test ( content ) ) ) {
forcedRootBlockName = self . settings . forced _root _block ;
if ( forcedRootBlockName )
content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>' ;
else
content = '<br data-mce-bogus="1">' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
body . innerHTML = content ;
self . selection . select ( body , true ) ;
self . selection . collapse ( true ) ;
return ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Parse and serialize the html
if ( args . format !== 'raw' ) {
content = new tinymce . html . Serializer ( { } , self . schema ) . serialize (
self . parser . parse ( content )
) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Set the new cleaned contents to the editor
args . content = tinymce . trim ( content ) ;
self . dom . setHTML ( body , args . content ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Do post processing
if ( ! args . no _events )
self . onSetContent . dispatch ( self , args ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . selection . normalize ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return args . content ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
getContent : function ( args ) {
var self = this , content ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Setup args object
args = args || { } ;
args . format = args . format || 'html' ;
args . get = true ;
2012-05-16 02:18:46 +02:00
args . getInner = true ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Do preprocessing
if ( ! args . no _events )
self . onBeforeGetContent . dispatch ( self , args ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Get raw contents or by default the cleaned contents
if ( args . format == 'raw' )
content = self . getBody ( ) . innerHTML ;
else
content = self . serializer . serialize ( self . getBody ( ) , args ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
args . content = tinymce . trim ( content ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Do post processing
if ( ! args . no _events )
self . onGetContent . dispatch ( self , args ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return args . content ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
isDirty : function ( ) {
var self = this ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return tinymce . trim ( self . startContent ) != tinymce . trim ( self . getContent ( { format : 'raw' , no _events : 1 } ) ) && ! self . isNotDirty ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
getContainer : function ( ) {
2012-05-16 02:18:46 +02:00
var self = this ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( ! self . container )
self . container = DOM . get ( self . editorContainer || self . id + '_parent' ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
return self . container ;
2012-03-21 04:47:31 +01:00
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
getContentAreaContainer : function ( ) {
return this . contentAreaContainer ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
getElement : function ( ) {
return DOM . get ( this . settings . content _element || this . id ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
getWin : function ( ) {
2012-05-16 02:18:46 +02:00
var self = this , elm ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( ! self . contentWindow ) {
elm = DOM . get ( self . id + "_ifr" ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( elm )
self . contentWindow = elm . contentWindow ;
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
return self . contentWindow ;
2012-03-21 04:47:31 +01:00
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
getDoc : function ( ) {
2012-05-16 02:18:46 +02:00
var self = this , win ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( ! self . contentDocument ) {
win = self . getWin ( ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( win )
self . contentDocument = win . document ;
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
return self . contentDocument ;
2012-03-21 04:47:31 +01:00
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
getBody : function ( ) {
return this . bodyElement || this . getDoc ( ) . body ;
} ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
convertURL : function ( url , name , elm ) {
var self = this , settings = self . settings ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Use callback instead
2012-05-16 02:18:46 +02:00
if ( settings . urlconverter _callback )
return self . execCallback ( 'urlconverter_callback' , url , elm , true , name ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
2012-05-16 02:18:46 +02:00
if ( ! settings . convert _urls || ( elm && elm . nodeName == 'LINK' ) || url . indexOf ( 'file:' ) === 0 )
return url ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Convert to relative
2012-05-16 02:18:46 +02:00
if ( settings . relative _urls )
return self . documentBaseURI . toRelative ( url ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Convert to absolute
2012-05-16 02:18:46 +02:00
url = self . documentBaseURI . toAbsolute ( url , settings . remove _script _host ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
return url ;
2012-03-21 04:47:31 +01:00
} ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
addVisual : function ( elm ) {
var self = this , settings = self . settings , dom = self . dom , cls ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
elm = elm || self . getBody ( ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( ! is ( self . hasVisual ) )
self . hasVisual = settings . visual ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
each ( dom . select ( 'table,a' , elm ) , function ( elm ) {
var value ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
switch ( elm . nodeName ) {
2012-03-21 04:47:31 +01:00
case 'TABLE' :
2012-05-16 02:18:46 +02:00
cls = settings . visual _table _class || 'mceItemTable' ;
value = dom . getAttrib ( elm , 'border' ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( ! value || value == '0' ) {
if ( self . hasVisual )
dom . addClass ( elm , cls ) ;
2012-03-21 04:47:31 +01:00
else
2012-05-16 02:18:46 +02:00
dom . removeClass ( elm , cls ) ;
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
return ;
2012-03-21 04:47:31 +01:00
case 'A' :
2012-05-16 02:18:46 +02:00
value = dom . getAttrib ( elm , 'name' ) ;
cls = 'mceItemAnchor' ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( value ) {
if ( self . hasVisual )
dom . addClass ( elm , cls ) ;
2012-03-21 04:47:31 +01:00
else
2012-05-16 02:18:46 +02:00
dom . removeClass ( elm , cls ) ;
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return ;
}
} ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
self . onVisualAid . dispatch ( self , elm , self . hasVisual ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
remove : function ( ) {
2012-05-16 02:18:46 +02:00
var self = this , elm = self . getContainer ( ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( ! self . removed ) {
self . removed = 1 ; // Cancels post remove event execution
self . hide ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Don't clear the window or document if content editable
// is enabled since other instances might still be present
2012-05-16 02:18:46 +02:00
if ( ! self . settings . content _editable ) {
Event . clear ( self . getWin ( ) ) ;
Event . clear ( self . getDoc ( ) ) ;
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
Event . clear ( self . getBody ( ) ) ;
Event . clear ( self . formElement ) ;
Event . unbind ( elm ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
self . execCallback ( 'remove_instance_callback' , self ) ;
self . onRemove . dispatch ( self ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
2012-05-16 02:18:46 +02:00
self . onExecCommand . listeners = [ ] ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
tinymce . remove ( self ) ;
DOM . remove ( elm ) ;
2012-03-21 04:47:31 +01:00
}
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
destroy : function ( s ) {
var t = this ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// One time is enough
if ( t . destroyed )
return ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// We must unbind on Gecko since it would otherwise produce the pesky "attempt to run compile-and-go script on a cleared scope" message
if ( isGecko ) {
Event . unbind ( t . getDoc ( ) ) ;
Event . unbind ( t . getWin ( ) ) ;
Event . unbind ( t . getBody ( ) ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! s ) {
tinymce . removeUnload ( t . destroy ) ;
tinyMCE . onBeforeUnload . remove ( t . _beforeUnload ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Manual destroy
if ( t . theme && t . theme . destroy )
t . theme . destroy ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Destroy controls, selection and dom
t . controlManager . destroy ( ) ;
t . selection . destroy ( ) ;
t . dom . destroy ( ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
if ( t . formElement ) {
t . formElement . submit = t . formElement . _mceOldSubmit ;
t . formElement . _mceOldSubmit = null ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . contentAreaContainer = t . formElement = t . container = t . settings . content _element = t . bodyElement = t . contentDocument = t . contentWindow = null ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( t . selection )
t . selection = t . selection . win = t . selection . dom = t . selection . dom . doc = null ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
t . destroyed = 1 ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Internal functions
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
_refreshContentEditable : function ( ) {
var self = this , body , parent ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again
if ( self . _isHidden ( ) ) {
body = self . getBody ( ) ;
parent = body . parentNode ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
parent . removeChild ( body ) ;
parent . appendChild ( body ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
body . focus ( ) ;
}
} ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
_isHidden : function ( ) {
var s ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( ! isGecko )
return 0 ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Weird, wheres that cursor selection?
s = this . selection . getSel ( ) ;
return ( ! s || ! s . rangeCount || s . rangeCount === 0 ) ;
}
} ) ;
} ) ( tinymce ) ;
( function ( tinymce ) {
var each = tinymce . each ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
tinymce . Editor . prototype . setupEvents = function ( ) {
var self = this , settings = self . settings ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Add events to the editor
each ( [
'onPreInit' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onBeforeRenderUI' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onPostRender' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onLoad' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onInit' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onRemove' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onActivate' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onDeactivate' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onClick' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onEvent' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onMouseUp' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onMouseDown' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onDblClick' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onKeyDown' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onKeyUp' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onKeyPress' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onContextMenu' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onSubmit' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onReset' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onPaste' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onPreProcess' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onPostProcess' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onBeforeSetContent' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onBeforeGetContent' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onSetContent' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onGetContent' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onLoadContent' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onSaveContent' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onNodeChange' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onChange' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onBeforeExecCommand' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onExecCommand' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onUndo' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onRedo' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onVisualAid' ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
'onSetProgressState' ,
'onSetAttrib'
] , function ( name ) {
self [ name ] = new tinymce . util . Dispatcher ( self ) ;
} ) ;
// Handle legacy cleanup_callback option
if ( settings . cleanup _callback ) {
self . onBeforeSetContent . add ( function ( ed , o ) {
o . content = ed . execCallback ( 'cleanup_callback' , 'insert_to_editor' , o . content , o ) ;
} ) ;
self . onPreProcess . add ( function ( ed , o ) {
if ( o . set )
ed . execCallback ( 'cleanup_callback' , 'insert_to_editor_dom' , o . node , o ) ;
if ( o . get )
ed . execCallback ( 'cleanup_callback' , 'get_from_editor_dom' , o . node , o ) ;
} ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
self . onPostProcess . add ( function ( ed , o ) {
if ( o . set )
o . content = ed . execCallback ( 'cleanup_callback' , 'insert_to_editor' , o . content , o ) ;
if ( o . get )
o . content = ed . execCallback ( 'cleanup_callback' , 'get_from_editor' , o . content , o ) ;
} ) ;
}
// Handle legacy save_callback option
if ( settings . save _callback ) {
self . onGetContent . add ( function ( ed , o ) {
if ( o . save )
o . content = ed . execCallback ( 'save_callback' , ed . id , o . content , ed . getBody ( ) ) ;
} ) ;
}
// Handle legacy handle_event_callback option
if ( settings . handle _event _callback ) {
self . onEvent . add ( function ( ed , e , o ) {
if ( self . execCallback ( 'handle_event_callback' , e , ed , o ) === false )
Event . cancel ( e ) ;
} ) ;
}
// Handle legacy handle_node_change_callback option
if ( settings . handle _node _change _callback ) {
self . onNodeChange . add ( function ( ed , cm , n ) {
ed . execCallback ( 'handle_node_change_callback' , ed . id , n , - 1 , - 1 , true , ed . selection . isCollapsed ( ) ) ;
} ) ;
}
// Handle legacy save_callback option
if ( settings . save _callback ) {
self . onSaveContent . add ( function ( ed , o ) {
var h = ed . execCallback ( 'save_callback' , ed . id , o . content , ed . getBody ( ) ) ;
if ( h )
o . content = h ;
} ) ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Handle legacy onchange_callback option
if ( settings . onchange _callback ) {
self . onChange . add ( function ( ed , l ) {
ed . execCallback ( 'onchange_callback' , ed , l ) ;
} ) ;
}
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
tinymce . Editor . prototype . bindNativeEvents = function ( ) {
// 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
var self = this , i , settings = self . settings , dom = self . dom , nativeToDispatcherMap ;
nativeToDispatcherMap = {
mouseup : 'onMouseUp' ,
mousedown : 'onMouseDown' ,
click : 'onClick' ,
keyup : 'onKeyUp' ,
keydown : 'onKeyDown' ,
keypress : 'onKeyPress' ,
submit : 'onSubmit' ,
reset : 'onReset' ,
contextmenu : 'onContextMenu' ,
dblclick : 'onDblClick' ,
paste : 'onPaste' // Doesn't work in all browsers yet
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Handler that takes a native event and sends it out to a dispatcher like onKeyDown
function eventHandler ( evt , args ) {
var type = evt . type ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Don't fire events when it's removed
if ( self . removed )
return ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Sends the native event out to a global dispatcher then to the specific event dispatcher
if ( self . onEvent . dispatch ( self , evt , args ) !== false ) {
self [ nativeToDispatcherMap [ evt . fakeType || evt . type ] ] . dispatch ( self , evt , args ) ;
}
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Opera doesn't support focus event for contentEditable elements so we need to fake it
function doOperaFocus ( e ) {
self . focus ( true ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
function nodeChanged ( ) {
// Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i>
self . selection . normalize ( ) ;
self . nodeChanged ( ) ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Add DOM events
each ( nativeToDispatcherMap , function ( dispatcherName , nativeName ) {
var root = settings . content _editable ? self . getBody ( ) : self . getDoc ( ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
switch ( nativeName ) {
case 'contextmenu' :
dom . bind ( root , nativeName , eventHandler ) ;
break ;
2012-03-21 04:47:31 +01:00
2012-05-16 02:18:46 +02:00
case 'paste' :
dom . bind ( self . getBody ( ) , nativeName , eventHandler ) ;
break ;
2012-03-21 04:47:31 +01:00
2012-05-16 02:18:46 +02:00
case 'submit' :
case 'reset' :
dom . bind ( self . getElement ( ) . form || tinymce . DOM . getParent ( self . id , 'form' ) , nativeName , eventHandler ) ;
break ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
default :
dom . bind ( root , nativeName , eventHandler ) ;
2012-03-21 04:47:31 +01:00
}
2012-05-16 02:18:46 +02:00
} ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Set the editor as active when focused
dom . bind ( settings . content _editable ? self . getBody ( ) : ( tinymce . isGecko ? self . getDoc ( ) : self . getWin ( ) ) , 'focus' , function ( e ) {
self . focus ( true ) ;
} ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( settings . content _editable && tinymce . isOpera ) {
dom . bind ( self . getBody ( ) , 'click' , doOperaFocus ) ;
dom . bind ( self . getBody ( ) , 'keydown' , doOperaFocus ) ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Add node change handler
self . onMouseUp . add ( nodeChanged ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
self . onKeyUp . add ( function ( ed , e ) {
var keyCode = e . keyCode ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( ( keyCode >= 33 && keyCode <= 36 ) || ( keyCode >= 37 && keyCode <= 40 ) || keyCode == 13 || keyCode == 45 || keyCode == 46 || keyCode == 8 || ( tinymce . isMac && ( keyCode == 91 || keyCode == 93 ) ) || e . ctrlKey )
nodeChanged ( ) ;
} ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Add reset handler
self . onReset . add ( function ( ) {
self . setContent ( self . startContent , { format : 'raw' } ) ;
} ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Add shortcuts
function handleShortcut ( e , execute ) {
if ( e . altKey || e . ctrlKey || e . metaKey ) {
each ( self . shortcuts , function ( shortcut ) {
var ctrlState = tinymce . isMac ? e . metaKey : e . ctrlKey ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( shortcut . ctrl != ctrlState || shortcut . alt != e . altKey || shortcut . shift != e . shiftKey )
return ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( e . keyCode == shortcut . keyCode || ( e . charCode && e . charCode == shortcut . charCode ) ) {
e . preventDefault ( ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( execute ) {
shortcut . func . call ( shortcut . scope ) ;
2012-03-21 04:47:31 +01:00
}
2012-05-16 02:18:46 +02:00
return true ;
2012-03-21 04:47:31 +01:00
}
} ) ;
2010-07-02 01:48:07 +02:00
}
2012-05-16 02:18:46 +02:00
} ;
2012-03-21 04:47:31 +01:00
2012-05-16 02:18:46 +02:00
self . onKeyUp . add ( function ( ed , e ) {
handleShortcut ( e ) ;
} ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
self . onKeyPress . add ( function ( ed , e ) {
handleShortcut ( e ) ;
} ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
self . onKeyDown . add ( function ( ed , e ) {
handleShortcut ( e , true ) ;
} ) ;
2012-03-21 04:47:31 +01:00
2012-05-16 02:18:46 +02:00
if ( tinymce . isOpera ) {
self . onClick . add ( function ( ed , e ) {
e . preventDefault ( ) ;
} ) ;
2010-07-02 01:48:07 +02:00
}
2012-05-16 02:18:46 +02:00
} ;
2010-07-02 01:48:07 +02:00
} ) ( tinymce ) ;
( function ( tinymce ) {
2012-03-21 04:47:31 +01:00
// Added for compression purposes
2012-05-16 02:18:46 +02:00
var each = tinymce . each , undef , TRUE = true , FALSE = false ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinymce . EditorCommands = function ( editor ) {
var dom = editor . dom ,
selection = editor . selection ,
commands = { state : { } , exec : { } , value : { } } ,
settings = editor . settings ,
formatter = editor . formatter ,
bookmark ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function execCommand ( command , ui , value ) {
var func ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
command = command . toLowerCase ( ) ;
if ( func = commands . exec [ command ] ) {
func ( command , ui , value ) ;
return TRUE ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return FALSE ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function queryCommandState ( command ) {
var func ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
command = command . toLowerCase ( ) ;
if ( func = commands . state [ command ] )
return func ( command ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return - 1 ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function queryCommandValue ( command ) {
var func ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
command = command . toLowerCase ( ) ;
if ( func = commands . value [ command ] )
return func ( command ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return FALSE ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function addCommands ( command _list , type ) {
type = type || 'exec' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
each ( command _list , function ( callback , command ) {
each ( command . toLowerCase ( ) . split ( ',' ) , function ( command ) {
commands [ type ] [ command ] = callback ;
} ) ;
} ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Expose public methods
tinymce . extend ( this , {
execCommand : execCommand ,
queryCommandState : queryCommandState ,
queryCommandValue : queryCommandValue ,
addCommands : addCommands
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Private methods
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function execNativeCommand ( command , ui , value ) {
2012-05-16 02:18:46 +02:00
if ( ui === undef )
2012-03-21 04:47:31 +01:00
ui = FALSE ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( value === undef )
2012-03-21 04:47:31 +01:00
value = null ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return editor . getDoc ( ) . execCommand ( command , ui , value ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function isFormatMatch ( name ) {
return formatter . match ( name ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function toggleFormat ( name , value ) {
2012-05-16 02:18:46 +02:00
formatter . toggle ( name , value ? { value : value } : undef ) ;
2012-03-21 04:47:31 +01:00
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function storeSelection ( type ) {
bookmark = selection . getBookmark ( type ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function restoreSelection ( ) {
selection . moveToBookmark ( bookmark ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Add execCommand overrides
addCommands ( {
// Ignore these, added for compatibility
'mceResetDesignMode,mceBeginUndoLevel' : function ( ) { } ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Add undo manager logic
'mceEndUndoLevel,mceAddUndoLevel' : function ( ) {
editor . undoManager . add ( ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
'Cut,Copy,Paste' : function ( command ) {
var doc = editor . getDoc ( ) , failed ;
// Try executing the native command
try {
execNativeCommand ( command ) ;
} catch ( ex ) {
// Command failed
failed = TRUE ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
// Present alert message about clipboard access not being available
if ( failed || ! doc . queryCommandSupported ( command ) ) {
if ( tinymce . isGecko ) {
editor . windowManager . confirm ( editor . getLang ( 'clipboard_msg' ) , function ( state ) {
if ( state )
open ( 'http://www.mozilla.org/editor/midasdemo/securityprefs.html' , '_blank' ) ;
} ) ;
} else
editor . windowManager . alert ( editor . getLang ( 'clipboard_no_support' ) ) ;
}
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
// Override unlink command
unlink : function ( command ) {
if ( selection . isCollapsed ( ) )
selection . select ( selection . getNode ( ) ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
execNativeCommand ( command ) ;
selection . collapse ( FALSE ) ;
2010-07-02 01:48:07 +02:00
} ,
2012-03-21 04:47:31 +01:00
// Override justify commands to use the text formatter engine
'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function ( command ) {
var align = command . substring ( 7 ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Remove all other alignments first
each ( 'left,center,right,full' . split ( ',' ) , function ( name ) {
if ( align != name )
formatter . remove ( 'align' + name ) ;
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
toggleFormat ( 'align' + align ) ;
execCommand ( 'mceRepaint' ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Override list commands to fix WebKit bug
'InsertUnorderedList,InsertOrderedList' : function ( command ) {
var listElm , listParent ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
execNativeCommand ( command ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// WebKit produces lists within block elements so we need to split them
// we will replace the native list creation logic to custom logic later on
// TODO: Remove this when the list creation logic is removed
listElm = dom . getParent ( selection . getNode ( ) , 'ol,ul' ) ;
if ( listElm ) {
listParent = listElm . parentNode ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// If list is within a text block then split that block
if ( /^(H[1-6]|P|ADDRESS|PRE)$/ . test ( listParent . nodeName ) ) {
storeSelection ( ) ;
dom . split ( listParent , listElm ) ;
restoreSelection ( ) ;
}
}
} ,
// Override commands to use the text formatter engine
'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function ( command ) {
toggleFormat ( command ) ;
} ,
// Override commands to use the text formatter engine
'ForeColor,HiliteColor,FontName' : function ( command , ui , value ) {
toggleFormat ( command , value ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
FontSize : function ( command , ui , value ) {
var fontClasses , fontSizes ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Convert font size 1-7 to styles
if ( value >= 1 && value <= 7 ) {
fontSizes = tinymce . explode ( settings . font _size _style _values ) ;
fontClasses = tinymce . explode ( settings . font _size _classes ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( fontClasses )
value = fontClasses [ value - 1 ] || value ;
else
value = fontSizes [ value - 1 ] || value ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
toggleFormat ( command , value ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
RemoveFormat : function ( command ) {
formatter . remove ( command ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
mceBlockQuote : function ( command ) {
toggleFormat ( 'blockquote' ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
FormatBlock : function ( command , ui , value ) {
return toggleFormat ( value || 'p' ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
mceCleanup : function ( ) {
var bookmark = selection . getBookmark ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
editor . setContent ( editor . getContent ( { cleanup : TRUE } ) , { cleanup : TRUE } ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
selection . moveToBookmark ( bookmark ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
mceRemoveNode : function ( command , ui , value ) {
var node = value || selection . getNode ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Make sure that the body node isn't removed
if ( node != editor . getBody ( ) ) {
storeSelection ( ) ;
editor . dom . remove ( node , TRUE ) ;
restoreSelection ( ) ;
}
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
mceSelectNodeDepth : function ( command , ui , value ) {
var counter = 0 ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
dom . getParent ( selection . getNode ( ) , function ( node ) {
if ( node . nodeType == 1 && counter ++ == value ) {
selection . select ( node ) ;
return FALSE ;
}
} , editor . getBody ( ) ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
mceSelectNode : function ( command , ui , value ) {
selection . select ( value ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
mceInsertContent : function ( command , ui , value ) {
var parser , serializer , parentNode , rootNode , fragment , args ,
marker , nodeRect , viewPortRect , rng , node , node2 , bookmarkHtml , viewportBodyElement ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
//selection.normalize();
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Setup parser and serializer
parser = editor . parser ;
serializer = new tinymce . html . Serializer ( { } , editor . schema ) ;
bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Run beforeSetContent handlers on the HTML to be inserted
args = { content : value , format : 'html' } ;
selection . onBeforeSetContent . dispatch ( selection , args ) ;
value = args . content ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Add caret at end of contents if it's missing
if ( value . indexOf ( '{$caret}' ) == - 1 )
value += '{$caret}' ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Replace the caret marker with a span bookmark element
value = value . replace ( /\{\$caret\}/ , bookmarkHtml ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Insert node maker where we will insert the new HTML and get it's parent
if ( ! selection . isCollapsed ( ) )
editor . getDoc ( ) . execCommand ( 'Delete' , false , null ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
parentNode = selection . getNode ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Parse the fragment within the context of the parent node
args = { context : parentNode . nodeName . toLowerCase ( ) } ;
fragment = parser . parse ( value , args ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Move the caret to a more suitable location
node = fragment . lastChild ;
if ( node . attr ( 'id' ) == 'mce_marker' ) {
marker = node ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
for ( node = node . prev ; node ; node = node . walk ( true ) ) {
if ( node . type == 3 || ! dom . isBlock ( node . name ) ) {
node . parent . insert ( marker , node , node . name === 'br' ) ;
break ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
// If parser says valid we can insert the contents into that parent
if ( ! args . invalid ) {
value = serializer . serialize ( fragment ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Check if parent is empty or only has one BR element then set the innerHTML of that parent
node = parentNode . firstChild ;
node2 = parentNode . lastChild ;
if ( ! node || ( node === node2 && node . nodeName === 'BR' ) )
dom . setHTML ( parentNode , value ) ;
else
selection . setContent ( value ) ;
} else {
// If the fragment was invalid within that context then we need
// to parse and process the parent it's inserted into
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Insert bookmark node and get the parent
selection . setContent ( bookmarkHtml ) ;
parentNode = editor . selection . getNode ( ) ;
rootNode = editor . getBody ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Opera will return the document node when selection is in root
if ( parentNode . nodeType == 9 )
parentNode = node = rootNode ;
else
node = parentNode ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Find the ancestor just before the root element
while ( node !== rootNode ) {
parentNode = node ;
node = node . parentNode ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
// Get the outer/inner HTML depending on if we are in the root and parser and serialize that
value = parentNode == rootNode ? rootNode . innerHTML : dom . getOuterHTML ( parentNode ) ;
value = serializer . serialize (
parser . parse (
// Need to replace by using a function since $ in the contents would otherwise be a problem
value . replace ( /<span (id="mce_marker"|id=mce_marker).+?<\/span>/i , function ( ) {
return serializer . serialize ( fragment ) ;
} )
)
) ;
// Set the inner/outer HTML depending on if we are in the root or not
if ( parentNode == rootNode )
dom . setHTML ( rootNode , value ) ;
else
dom . setOuterHTML ( parentNode , value ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
marker = dom . get ( 'mce_marker' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
nodeRect = dom . getRect ( marker ) ;
viewPortRect = dom . getViewPort ( editor . getWin ( ) ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Check if node is out side the viewport if it is then scroll to it
if ( ( nodeRect . y + nodeRect . h > viewPortRect . y + viewPortRect . h || nodeRect . y < viewPortRect . y ) ||
( nodeRect . x > viewPortRect . x + viewPortRect . w || nodeRect . x < viewPortRect . x ) ) {
viewportBodyElement = tinymce . isIE ? editor . getDoc ( ) . documentElement : editor . getBody ( ) ;
viewportBodyElement . scrollLeft = nodeRect . x ;
viewportBodyElement . scrollTop = nodeRect . y - viewPortRect . h + 25 ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Move selection before marker and remove it
rng = dom . createRng ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// If previous sibling is a text node set the selection to the end of that node
node = marker . previousSibling ;
if ( node && node . nodeType == 3 ) {
rng . setStart ( node , node . nodeValue . length ) ;
} else {
// If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
rng . setStartBefore ( marker ) ;
rng . setEndBefore ( marker ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Remove the marker node and set the new range
dom . remove ( marker ) ;
selection . setRng ( rng ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Dispatch after event and add any visual elements needed
selection . onSetContent . dispatch ( selection , args ) ;
editor . addVisual ( ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
mceInsertRawHTML : function ( command , ui , value ) {
selection . setContent ( 'tiny_mce_marker' ) ;
editor . setContent ( editor . getContent ( ) . replace ( /tiny_mce_marker/g , function ( ) { return value } ) ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
mceSetContent : function ( command , ui , value ) {
editor . setContent ( value ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
'Indent,Outdent' : function ( command ) {
var intentValue , indentUnit , value ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Setup indent level
intentValue = settings . indentation ;
indentUnit = /[a-z%]+$/i . exec ( intentValue ) ;
intentValue = parseInt ( intentValue ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ! queryCommandState ( 'InsertUnorderedList' ) && ! queryCommandState ( 'InsertOrderedList' ) ) {
// If forced_root_blocks is set to false we don't have a block to indent so lets create a div
if ( ! settings . forced _root _block && ! dom . getParent ( selection . getNode ( ) , dom . isBlock ) ) {
formatter . apply ( 'div' ) ;
}
each ( selection . getSelectedBlocks ( ) , function ( element ) {
if ( command == 'outdent' ) {
value = Math . max ( 0 , parseInt ( element . style . paddingLeft || 0 ) - intentValue ) ;
dom . setStyle ( element , 'paddingLeft' , value ? value + indentUnit : '' ) ;
} else
dom . setStyle ( element , 'paddingLeft' , ( parseInt ( element . style . paddingLeft || 0 ) + intentValue ) + indentUnit ) ;
} ) ;
} else
execNativeCommand ( command ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
mceRepaint : function ( ) {
var bookmark ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( tinymce . isGecko ) {
try {
storeSelection ( TRUE ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( selection . getSel ( ) )
selection . getSel ( ) . selectAllChildren ( editor . getBody ( ) ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
selection . collapse ( TRUE ) ;
restoreSelection ( ) ;
} catch ( ex ) {
// Ignore
}
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
mceToggleFormat : function ( command , ui , value ) {
formatter . toggle ( value ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
InsertHorizontalRule : function ( ) {
editor . execCommand ( 'mceInsertContent' , false , '<hr />' ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
mceToggleVisualAid : function ( ) {
editor . hasVisual = ! editor . hasVisual ;
editor . addVisual ( ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
mceReplaceContent : function ( command , ui , value ) {
editor . execCommand ( 'mceInsertContent' , false , value . replace ( /\{\$selection\}/g , selection . getContent ( { format : 'text' } ) ) ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
mceInsertLink : function ( command , ui , value ) {
var anchor ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( typeof ( value ) == 'string' )
value = { href : value } ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
anchor = dom . getParent ( selection . getNode ( ) , 'a' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
value . href = value . href . replace ( ' ' , '%20' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Remove existing links if there could be child links or that the href isn't specified
if ( ! anchor || ! value . href ) {
formatter . remove ( 'link' ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Apply new link to selection
if ( value . href ) {
formatter . apply ( 'link' , value , anchor ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
selectAll : function ( ) {
var root = dom . getRoot ( ) , rng = dom . createRng ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
rng . setStart ( root , 0 ) ;
rng . setEnd ( root , root . childNodes . length ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
editor . selection . setRng ( rng ) ;
}
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Add queryCommandState overrides
addCommands ( {
// Override justify commands
'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function ( command ) {
var name = 'align' + command . substring ( 7 ) ;
2012-05-16 02:18:46 +02:00
var nodes = selection . isCollapsed ( ) ? [ dom . getParent ( selection . getNode ( ) , dom . isBlock ) ] : selection . getSelectedBlocks ( ) ;
2012-03-21 04:47:31 +01:00
var matches = tinymce . map ( nodes , function ( node ) {
return ! ! formatter . matchNode ( node , name ) ;
} ) ;
return tinymce . inArray ( matches , TRUE ) !== - 1 ;
} ,
'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function ( command ) {
return isFormatMatch ( command ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
mceBlockQuote : function ( ) {
return isFormatMatch ( 'blockquote' ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
Outdent : function ( ) {
var node ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( settings . inline _styles ) {
if ( ( node = dom . getParent ( selection . getStart ( ) , dom . isBlock ) ) && parseInt ( node . style . paddingLeft ) > 0 )
return TRUE ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( ( node = dom . getParent ( selection . getEnd ( ) , dom . isBlock ) ) && parseInt ( node . style . paddingLeft ) > 0 )
return TRUE ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return queryCommandState ( 'InsertUnorderedList' ) || queryCommandState ( 'InsertOrderedList' ) || ( ! settings . inline _styles && ! ! dom . getParent ( selection . getNode ( ) , 'BLOCKQUOTE' ) ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
'InsertUnorderedList,InsertOrderedList' : function ( command ) {
return dom . getParent ( selection . getNode ( ) , command == 'insertunorderedlist' ? 'UL' : 'OL' ) ;
}
} , 'state' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Add queryCommandValue overrides
addCommands ( {
'FontSize,FontName' : function ( command ) {
var value = 0 , parent ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( parent = dom . getParent ( selection . getNode ( ) , 'span' ) ) {
if ( command == 'fontsize' )
value = parent . style . fontSize ;
else
value = parent . style . fontFamily . replace ( /, /g , ',' ) . replace ( /[\'\"]/g , '' ) . toLowerCase ( ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
return value ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
} , 'value' ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Add undo manager logic
2012-05-16 02:18:46 +02:00
addCommands ( {
Undo : function ( ) {
editor . undoManager . undo ( ) ;
} ,
2012-03-21 04:47:31 +01:00
2012-05-16 02:18:46 +02:00
Redo : function ( ) {
editor . undoManager . redo ( ) ;
}
} ) ;
2012-03-21 04:47:31 +01:00
} ;
} ) ( tinymce ) ;
( function ( tinymce ) {
var Dispatcher = tinymce . util . Dispatcher ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinymce . UndoManager = function ( editor ) {
2012-05-16 02:18:46 +02:00
var self , index = 0 , data = [ ] , beforeBookmark , onAdd , onUndo , onRedo ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function getContent ( ) {
// Remove whitespace before/after and remove pure bogus nodes
return tinymce . trim ( editor . getContent ( { format : 'raw' , no _events : 1 } ) . replace ( /<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>/g , '' ) ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
function addNonTypingUndoLevel ( ) {
self . typing = false ;
self . add ( ) ;
} ;
// Create event instances
onAdd = new Dispatcher ( self ) ;
onUndo = new Dispatcher ( self ) ;
onRedo = new Dispatcher ( self ) ;
// Pass though onAdd event from UndoManager to Editor as onChange
onAdd . add ( function ( undoman , level ) {
if ( undoman . hasUndo ( ) )
return editor . onChange . dispatch ( editor , level , undoman ) ;
} ) ;
// Pass though onUndo event from UndoManager to Editor
onUndo . add ( function ( undoman , level ) {
return editor . onUndo . dispatch ( editor , level , undoman ) ;
} ) ;
// Pass though onRedo event from UndoManager to Editor
onRedo . add ( function ( undoman , level ) {
return editor . onRedo . dispatch ( editor , level , undoman ) ;
} ) ;
// Add initial undo level when the editor is initialized
editor . onInit . add ( function ( ) {
self . add ( ) ;
} ) ;
// Get position before an execCommand is processed
editor . onBeforeExecCommand . add ( function ( ed , cmd , ui , val , args ) {
if ( cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && ( ! args || ! args . skip _undo ) ) {
self . beforeChange ( ) ;
}
} ) ;
// Add undo level after an execCommand call was made
editor . onExecCommand . add ( function ( ed , cmd , ui , val , args ) {
if ( cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && ( ! args || ! args . skip _undo ) ) {
self . add ( ) ;
}
} ) ;
// Add undo level on save contents, drag end and blur/focusout
editor . onSaveContent . add ( addNonTypingUndoLevel ) ;
editor . dom . bind ( editor . dom . getRoot ( ) , 'dragend' , addNonTypingUndoLevel ) ;
editor . dom . bind ( editor . getDoc ( ) , tinymce . isGecko ? 'blur' : 'focusout' , function ( e ) {
if ( ! editor . removed && self . typing ) {
addNonTypingUndoLevel ( ) ;
}
} ) ;
editor . onKeyUp . add ( function ( editor , e ) {
var keyCode = e . keyCode ;
if ( ( keyCode >= 33 && keyCode <= 36 ) || ( keyCode >= 37 && keyCode <= 40 ) || keyCode == 45 || keyCode == 13 || e . ctrlKey ) {
addNonTypingUndoLevel ( ) ;
}
} ) ;
editor . onKeyDown . add ( function ( editor , e ) {
var keyCode = e . keyCode ;
// Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
if ( ( keyCode >= 33 && keyCode <= 36 ) || ( keyCode >= 37 && keyCode <= 40 ) || keyCode == 45 ) {
if ( self . typing ) {
addNonTypingUndoLevel ( ) ;
}
return ;
}
// If key isn't shift,ctrl,alt,capslock,metakey
if ( ( keyCode < 16 || keyCode > 20 ) && keyCode != 224 && keyCode != 91 && ! self . typing ) {
self . beforeChange ( ) ;
self . typing = true ;
self . add ( ) ;
}
} ) ;
editor . onMouseDown . add ( function ( editor , e ) {
if ( self . typing ) {
addNonTypingUndoLevel ( ) ;
}
} ) ;
// Add keyboard shortcuts for undo/redo keys
editor . addShortcut ( 'ctrl+z' , 'undo_desc' , 'Undo' ) ;
editor . addShortcut ( 'ctrl+y' , 'redo_desc' , 'Redo' ) ;
self = {
// Explose for debugging reasons
data : data ,
2012-03-21 04:47:31 +01:00
typing : false ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
onAdd : onAdd ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
onUndo : onUndo ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
onRedo : onRedo ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
beforeChange : function ( ) {
beforeBookmark = editor . selection . getBookmark ( 2 , true ) ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
add : function ( level ) {
var i , settings = editor . settings , lastLevel ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
level = level || { } ;
level . content = getContent ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Add undo level if needed
lastLevel = data [ index ] ;
if ( lastLevel && lastLevel . content == level . content )
return null ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Set before bookmark on previous level
if ( data [ index ] )
data [ index ] . beforeBookmark = beforeBookmark ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Time to compress
if ( settings . custom _undo _redo _levels ) {
if ( data . length > settings . custom _undo _redo _levels ) {
for ( i = 0 ; i < data . length - 1 ; i ++ )
data [ i ] = data [ i + 1 ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
data . length -- ;
index = data . length ;
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Get a non intrusive normalized bookmark
level . bookmark = editor . selection . getBookmark ( 2 , true ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Crop array if needed
if ( index < data . length - 1 )
data . length = index + 1 ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
data . push ( level ) ;
index = data . length - 1 ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . onAdd . dispatch ( self , level ) ;
editor . isNotDirty = 0 ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return level ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
undo : function ( ) {
var level , i ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( self . typing ) {
self . add ( ) ;
self . typing = false ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( index > 0 ) {
level = data [ -- index ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
editor . setContent ( level . content , { format : 'raw' } ) ;
editor . selection . moveToBookmark ( level . beforeBookmark ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . onUndo . dispatch ( self , level ) ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return level ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
redo : function ( ) {
var level ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( index < data . length - 1 ) {
level = data [ ++ index ] ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
editor . setContent ( level . content , { format : 'raw' } ) ;
editor . selection . moveToBookmark ( level . bookmark ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
self . onRedo . dispatch ( self , level ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
return level ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
clear : function ( ) {
data = [ ] ;
index = 0 ;
self . typing = false ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
hasUndo : function ( ) {
return index > 0 || this . typing ;
} ,
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
hasRedo : function ( ) {
return index < data . length - 1 && ! this . typing ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
} ;
2012-05-16 02:18:46 +02:00
return self ;
2012-03-21 04:47:31 +01:00
} ;
} ) ( tinymce ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
tinymce . ForceBlocks = function ( editor ) {
var settings = editor . settings , dom = editor . dom , selection = editor . selection , blockElements = editor . schema . getBlockElements ( ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
function addRootBlocks ( ) {
var node = selection . getStart ( ) , rootNode = editor . getBody ( ) , rng , startContainer , startOffset , endContainer , endOffset , rootBlockNode , tempNode , offset = - 0xFFFFFF , wrapped ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( ! node || node . nodeType !== 1 || ! settings . forced _root _block )
return ;
// Check if node is wrapped in block
while ( node && node != rootNode ) {
if ( blockElements [ node . nodeName ] )
2012-03-21 04:47:31 +01:00
return ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
node = node . parentNode ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Get current selection
rng = selection . getRng ( ) ;
if ( rng . setStart ) {
startContainer = rng . startContainer ;
startOffset = rng . startOffset ;
endContainer = rng . endContainer ;
endOffset = rng . endOffset ;
} else {
// Force control range into text range
if ( rng . item ) {
node = rng . item ( 0 ) ;
rng = editor . getDoc ( ) . body . createTextRange ( ) ;
rng . moveToElementText ( node ) ;
2010-07-02 01:48:07 +02:00
}
2012-05-16 02:18:46 +02:00
tmpRng = rng . duplicate ( ) ;
tmpRng . collapse ( true ) ;
startOffset = tmpRng . move ( 'character' , offset ) * - 1 ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( ! tmpRng . collapsed ) {
2012-03-21 04:47:31 +01:00
tmpRng = rng . duplicate ( ) ;
2012-05-16 02:18:46 +02:00
tmpRng . collapse ( false ) ;
endOffset = ( tmpRng . move ( 'character' , offset ) * - 1 ) - startOffset ;
2010-07-02 01:48:07 +02:00
}
2012-05-16 02:18:46 +02:00
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// Wrap non block elements and text nodes
node = rootNode . firstChild ;
while ( node ) {
if ( node . nodeType === 3 || ( node . nodeType == 1 && ! blockElements [ node . nodeName ] ) ) {
if ( ! rootBlockNode ) {
rootBlockNode = dom . create ( settings . forced _root _block ) ;
node . parentNode . insertBefore ( rootBlockNode , node ) ;
wrapped = true ;
2012-03-21 04:47:31 +01:00
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
tempNode = node ;
node = node . nextSibling ;
rootBlockNode . appendChild ( tempNode ) ;
2012-03-21 04:47:31 +01:00
} else {
2012-05-16 02:18:46 +02:00
rootBlockNode = null ;
node = node . nextSibling ;
}
}
if ( rng . setStart ) {
rng . setStart ( startContainer , startOffset ) ;
rng . setEnd ( endContainer , endOffset ) ;
selection . setRng ( rng ) ;
} else {
try {
rng = editor . getDoc ( ) . body . createTextRange ( ) ;
rng . moveToElementText ( rootNode ) ;
rng . collapse ( true ) ;
rng . moveStart ( 'character' , startOffset ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( endOffset > 0 )
rng . moveEnd ( 'character' , endOffset ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
rng . select ( ) ;
} catch ( ex ) {
// Ignore
2010-07-02 01:48:07 +02:00
}
2012-05-16 02:18:46 +02:00
}
2012-03-21 04:47:31 +01:00
2012-05-16 02:18:46 +02:00
// Only trigger nodeChange when we wrapped nodes to prevent a forever loop
if ( wrapped ) {
2012-03-21 04:47:31 +01:00
editor . nodeChanged ( ) ;
2012-05-16 02:18:46 +02:00
}
} ;
2012-03-21 04:47:31 +01:00
2012-05-16 02:18:46 +02:00
// Force root blocks
if ( settings . forced _root _block ) {
2012-03-21 04:47:31 +01:00
editor . onKeyUp . add ( addRootBlocks ) ;
2012-05-16 02:18:46 +02:00
editor . onNodeChange . add ( addRootBlocks ) ;
2012-03-21 04:47:31 +01:00
}
} ;
2010-07-02 01:48:07 +02:00
( function ( tinymce ) {
// Shorten names
var DOM = tinymce . DOM , Event = tinymce . dom . Event , each = tinymce . each , extend = tinymce . extend ;
tinymce . create ( 'tinymce.ControlManager' , {
ControlManager : function ( ed , s ) {
var t = this , i ;
s = s || { } ;
t . editor = ed ;
t . controls = { } ;
t . onAdd = new tinymce . util . Dispatcher ( t ) ;
t . onPostRender = new tinymce . util . Dispatcher ( t ) ;
t . prefix = s . prefix || ed . id + '_' ;
t . _cls = { } ;
t . onPostRender . add ( function ( ) {
each ( t . controls , function ( c ) {
c . postRender ( ) ;
} ) ;
} ) ;
} ,
get : function ( id ) {
return this . controls [ this . prefix + id ] || this . controls [ id ] ;
} ,
setActive : function ( id , s ) {
var c = null ;
if ( c = this . get ( id ) )
c . setActive ( s ) ;
return c ;
} ,
setDisabled : function ( id , s ) {
var c = null ;
if ( c = this . get ( id ) )
c . setDisabled ( s ) ;
return c ;
} ,
add : function ( c ) {
var t = this ;
if ( c ) {
t . controls [ c . id ] = c ;
t . onAdd . dispatch ( c , t ) ;
}
return c ;
} ,
createControl : function ( n ) {
var c , t = this , ed = t . editor ;
each ( ed . plugins , function ( p ) {
if ( p . createControl ) {
c = p . createControl ( n , t ) ;
if ( c )
return false ;
}
} ) ;
switch ( n ) {
case "|" :
case "separator" :
return t . createSeparator ( ) ;
}
if ( ! c && ed . buttons && ( c = ed . buttons [ n ] ) )
return t . createButton ( n , c ) ;
return t . add ( c ) ;
} ,
createDropMenu : function ( id , s , cc ) {
var t = this , ed = t . editor , c , bm , v , cls ;
s = extend ( {
'class' : 'mceDropDown' ,
constrain : ed . settings . constrain _menus
} , s ) ;
s [ 'class' ] = s [ 'class' ] + ' ' + ed . getParam ( 'skin' ) + 'Skin' ;
if ( v = ed . getParam ( 'skin_variant' ) )
s [ 'class' ] += ' ' + ed . getParam ( 'skin' ) + 'Skin' + v . substring ( 0 , 1 ) . toUpperCase ( ) + v . substring ( 1 ) ;
2012-05-16 02:18:46 +02:00
s [ 'class' ] += ed . settings . directionality == "rtl" ? ' mceRtl' : '' ;
2010-07-02 01:48:07 +02:00
id = t . prefix + id ;
cls = cc || t . _cls . dropmenu || tinymce . ui . DropMenu ;
c = t . controls [ id ] = new cls ( id , s ) ;
c . onAddItem . add ( function ( c , o ) {
var s = o . settings ;
s . title = ed . getLang ( s . title , s . title ) ;
if ( ! s . onclick ) {
s . onclick = function ( v ) {
if ( s . cmd )
ed . execCommand ( s . cmd , s . ui || false , s . value ) ;
} ;
}
} ) ;
ed . onRemove . add ( function ( ) {
c . destroy ( ) ;
} ) ;
// Fix for bug #1897785, #1898007
if ( tinymce . isIE ) {
c . onShowMenu . add ( function ( ) {
// IE 8 needs focus in order to store away a range with the current collapsed caret location
ed . focus ( ) ;
bm = ed . selection . getBookmark ( 1 ) ;
} ) ;
c . onHideMenu . add ( function ( ) {
if ( bm ) {
ed . selection . moveToBookmark ( bm ) ;
bm = 0 ;
}
} ) ;
}
return t . add ( c ) ;
} ,
createListBox : function ( id , s , cc ) {
var t = this , ed = t . editor , cmd , c , cls ;
if ( t . get ( id ) )
return null ;
s . title = ed . translate ( s . title ) ;
s . scope = s . scope || ed ;
if ( ! s . onselect ) {
s . onselect = function ( v ) {
ed . execCommand ( s . cmd , s . ui || false , v || s . value ) ;
} ;
}
s = extend ( {
title : s . title ,
'class' : 'mce_' + id ,
scope : s . scope ,
control _manager : t
} , s ) ;
id = t . prefix + id ;
2012-03-21 04:47:31 +01:00
function useNativeListForAccessibility ( ed ) {
return ed . settings . use _accessible _selects && ! tinymce . isGecko
}
if ( ed . settings . use _native _selects || useNativeListForAccessibility ( ed ) )
2010-07-02 01:48:07 +02:00
c = new tinymce . ui . NativeListBox ( id , s ) ;
else {
cls = cc || t . _cls . listbox || tinymce . ui . ListBox ;
2012-03-21 04:47:31 +01:00
c = new cls ( id , s , ed ) ;
2010-07-02 01:48:07 +02:00
}
t . controls [ id ] = c ;
// Fix focus problem in Safari
if ( tinymce . isWebKit ) {
c . onPostRender . add ( function ( c , n ) {
// Store bookmark on mousedown
Event . add ( n , 'mousedown' , function ( ) {
ed . bookmark = ed . selection . getBookmark ( 1 ) ;
} ) ;
// Restore on focus, since it might be lost
Event . add ( n , 'focus' , function ( ) {
ed . selection . moveToBookmark ( ed . bookmark ) ;
ed . bookmark = null ;
} ) ;
} ) ;
}
if ( c . hideMenu )
ed . onMouseDown . add ( c . hideMenu , c ) ;
return t . add ( c ) ;
} ,
createButton : function ( id , s , cc ) {
var t = this , ed = t . editor , o , c , cls ;
if ( t . get ( id ) )
return null ;
s . title = ed . translate ( s . title ) ;
s . label = ed . translate ( s . label ) ;
s . scope = s . scope || ed ;
if ( ! s . onclick && ! s . menu _button ) {
s . onclick = function ( ) {
ed . execCommand ( s . cmd , s . ui || false , s . value ) ;
} ;
}
s = extend ( {
title : s . title ,
'class' : 'mce_' + id ,
unavailable _prefix : ed . getLang ( 'unavailable' , '' ) ,
scope : s . scope ,
control _manager : t
} , s ) ;
id = t . prefix + id ;
if ( s . menu _button ) {
cls = cc || t . _cls . menubutton || tinymce . ui . MenuButton ;
2012-03-21 04:47:31 +01:00
c = new cls ( id , s , ed ) ;
2010-07-02 01:48:07 +02:00
ed . onMouseDown . add ( c . hideMenu , c ) ;
} else {
cls = t . _cls . button || tinymce . ui . Button ;
2012-03-21 04:47:31 +01:00
c = new cls ( id , s , ed ) ;
2010-07-02 01:48:07 +02:00
}
return t . add ( c ) ;
} ,
createMenuButton : function ( id , s , cc ) {
s = s || { } ;
s . menu _button = 1 ;
return this . createButton ( id , s , cc ) ;
} ,
createSplitButton : function ( id , s , cc ) {
var t = this , ed = t . editor , cmd , c , cls ;
if ( t . get ( id ) )
return null ;
s . title = ed . translate ( s . title ) ;
s . scope = s . scope || ed ;
if ( ! s . onclick ) {
s . onclick = function ( v ) {
ed . execCommand ( s . cmd , s . ui || false , v || s . value ) ;
} ;
}
if ( ! s . onselect ) {
s . onselect = function ( v ) {
ed . execCommand ( s . cmd , s . ui || false , v || s . value ) ;
} ;
}
s = extend ( {
title : s . title ,
'class' : 'mce_' + id ,
scope : s . scope ,
control _manager : t
} , s ) ;
id = t . prefix + id ;
cls = cc || t . _cls . splitbutton || tinymce . ui . SplitButton ;
2012-03-21 04:47:31 +01:00
c = t . add ( new cls ( id , s , ed ) ) ;
2010-07-02 01:48:07 +02:00
ed . onMouseDown . add ( c . hideMenu , c ) ;
return c ;
} ,
createColorSplitButton : function ( id , s , cc ) {
var t = this , ed = t . editor , cmd , c , cls , bm ;
if ( t . get ( id ) )
return null ;
s . title = ed . translate ( s . title ) ;
s . scope = s . scope || ed ;
if ( ! s . onclick ) {
s . onclick = function ( v ) {
if ( tinymce . isIE )
bm = ed . selection . getBookmark ( 1 ) ;
ed . execCommand ( s . cmd , s . ui || false , v || s . value ) ;
} ;
}
if ( ! s . onselect ) {
s . onselect = function ( v ) {
ed . execCommand ( s . cmd , s . ui || false , v || s . value ) ;
} ;
}
s = extend ( {
title : s . title ,
'class' : 'mce_' + id ,
'menu_class' : ed . getParam ( 'skin' ) + 'Skin' ,
scope : s . scope ,
more _colors _title : ed . getLang ( 'more_colors' )
} , s ) ;
id = t . prefix + id ;
cls = cc || t . _cls . colorsplitbutton || tinymce . ui . ColorSplitButton ;
2012-03-21 04:47:31 +01:00
c = new cls ( id , s , ed ) ;
2010-07-02 01:48:07 +02:00
ed . onMouseDown . add ( c . hideMenu , c ) ;
// Remove the menu element when the editor is removed
ed . onRemove . add ( function ( ) {
c . destroy ( ) ;
} ) ;
// Fix for bug #1897785, #1898007
if ( tinymce . isIE ) {
c . onShowMenu . add ( function ( ) {
// IE 8 needs focus in order to store away a range with the current collapsed caret location
ed . focus ( ) ;
bm = ed . selection . getBookmark ( 1 ) ;
} ) ;
c . onHideMenu . add ( function ( ) {
if ( bm ) {
ed . selection . moveToBookmark ( bm ) ;
bm = 0 ;
}
} ) ;
}
return t . add ( c ) ;
} ,
createToolbar : function ( id , s , cc ) {
var c , t = this , cls ;
id = t . prefix + id ;
cls = cc || t . _cls . toolbar || tinymce . ui . Toolbar ;
2012-03-21 04:47:31 +01:00
c = new cls ( id , s , t . editor ) ;
2010-07-02 01:48:07 +02:00
if ( t . get ( id ) )
return null ;
return t . add ( c ) ;
} ,
2012-03-21 04:47:31 +01:00
createToolbarGroup : function ( id , s , cc ) {
var c , t = this , cls ;
id = t . prefix + id ;
cls = cc || this . _cls . toolbarGroup || tinymce . ui . ToolbarGroup ;
c = new cls ( id , s , t . editor ) ;
if ( t . get ( id ) )
return null ;
return t . add ( c ) ;
} ,
2010-07-02 01:48:07 +02:00
createSeparator : function ( cc ) {
var cls = cc || this . _cls . separator || tinymce . ui . Separator ;
return new cls ( ) ;
} ,
setControlType : function ( n , c ) {
return this . _cls [ n . toLowerCase ( ) ] = c ;
} ,
destroy : function ( ) {
each ( this . controls , function ( c ) {
c . destroy ( ) ;
} ) ;
this . controls = null ;
}
} ) ;
} ) ( tinymce ) ;
( function ( tinymce ) {
var Dispatcher = tinymce . util . Dispatcher , each = tinymce . each , isIE = tinymce . isIE , isOpera = tinymce . isOpera ;
tinymce . create ( 'tinymce.WindowManager' , {
WindowManager : function ( ed ) {
var t = this ;
t . editor = ed ;
t . onOpen = new Dispatcher ( t ) ;
t . onClose = new Dispatcher ( t ) ;
t . params = { } ;
t . features = { } ;
} ,
open : function ( s , p ) {
var t = this , f = '' , x , y , mo = t . editor . settings . dialog _type == 'modal' , w , sw , sh , vp = tinymce . DOM . getViewPort ( ) , u ;
// Default some options
s = s || { } ;
p = p || { } ;
sw = isOpera ? vp . w : screen . width ; // Opera uses windows inside the Opera window
sh = isOpera ? vp . h : screen . height ;
s . name = s . name || 'mc_' + new Date ( ) . getTime ( ) ;
s . width = parseInt ( s . width || 320 ) ;
s . height = parseInt ( s . height || 240 ) ;
s . resizable = true ;
s . left = s . left || parseInt ( sw / 2.0 ) - ( s . width / 2.0 ) ;
s . top = s . top || parseInt ( sh / 2.0 ) - ( s . height / 2.0 ) ;
p . inline = false ;
p . mce _width = s . width ;
p . mce _height = s . height ;
p . mce _auto _focus = s . auto _focus ;
if ( mo ) {
if ( isIE ) {
s . center = true ;
s . help = false ;
s . dialogWidth = s . width + 'px' ;
s . dialogHeight = s . height + 'px' ;
s . scroll = s . scrollbars || false ;
}
}
// Build features string
each ( s , function ( v , k ) {
if ( tinymce . is ( v , 'boolean' ) )
v = v ? 'yes' : 'no' ;
if ( ! /^(name|url)$/ . test ( k ) ) {
if ( isIE && mo )
f += ( f ? ';' : '' ) + k + ':' + v ;
else
f += ( f ? ',' : '' ) + k + '=' + v ;
}
} ) ;
t . features = s ;
t . params = p ;
t . onOpen . dispatch ( t , s , p ) ;
u = s . url || s . file ;
u = tinymce . _addVer ( u ) ;
try {
if ( isIE && mo ) {
w = 1 ;
window . showModalDialog ( u , window , f ) ;
} else
w = window . open ( u , s . name , f ) ;
} catch ( ex ) {
// Ignore
}
if ( ! w )
alert ( t . editor . getLang ( 'popup_blocked' ) ) ;
} ,
close : function ( w ) {
w . close ( ) ;
this . onClose . dispatch ( this ) ;
} ,
createInstance : function ( cl , a , b , c , d , e ) {
var f = tinymce . resolve ( cl ) ;
return new f ( a , b , c , d , e ) ;
} ,
confirm : function ( t , cb , s , w ) {
w = w || window ;
cb . call ( s || this , w . confirm ( this . _decode ( this . editor . getLang ( t , t ) ) ) ) ;
} ,
alert : function ( tx , cb , s , w ) {
var t = this ;
w = w || window ;
w . alert ( t . _decode ( t . editor . getLang ( tx , tx ) ) ) ;
if ( cb )
cb . call ( s || t ) ;
} ,
resizeBy : function ( dw , dh , win ) {
win . resizeBy ( dw , dh ) ;
} ,
2012-05-16 02:18:46 +02:00
// Internal functions
_decode : function ( s ) {
return tinymce . DOM . decode ( s ) . replace ( /\\n/g , '\n' ) ;
}
} ) ;
} ( tinymce ) ) ;
( function ( tinymce ) {
tinymce . Formatter = function ( ed ) {
var formats = { } ,
each = tinymce . each ,
dom = ed . dom ,
selection = ed . selection ,
TreeWalker = tinymce . dom . TreeWalker ,
rangeUtils = new tinymce . dom . RangeUtils ( dom ) ,
isValid = ed . schema . isValidChild ,
isBlock = dom . isBlock ,
forcedRootBlock = ed . settings . forced _root _block ,
nodeIndex = dom . nodeIndex ,
INVISIBLE _CHAR = tinymce . isGecko ? '\u200B' : '\uFEFF' ,
MCE _ATTR _RE = /^(src|href|style)$/ ,
FALSE = false ,
TRUE = true ,
undef ,
getContentEditable = dom . getContentEditable ;
function isArray ( obj ) {
return obj instanceof Array ;
} ;
function getParents ( node , selector ) {
return dom . getParents ( node , selector , dom . getRoot ( ) ) ;
} ;
function isCaretNode ( node ) {
return node . nodeType === 1 && node . id === '_mce_caret' ;
} ;
function defaultFormats ( ) {
register ( {
alignleft : [
{ selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li' , styles : { textAlign : 'left' } , defaultBlock : 'div' } ,
{ selector : 'img,table' , collapsed : false , styles : { 'float' : 'left' } }
] ,
aligncenter : [
{ selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li' , styles : { textAlign : 'center' } , defaultBlock : 'div' } ,
{ selector : 'img' , collapsed : false , styles : { display : 'block' , marginLeft : 'auto' , marginRight : 'auto' } } ,
{ selector : 'table' , collapsed : false , styles : { marginLeft : 'auto' , marginRight : 'auto' } }
] ,
alignright : [
{ selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li' , styles : { textAlign : 'right' } , defaultBlock : 'div' } ,
{ selector : 'img,table' , collapsed : false , styles : { 'float' : 'right' } }
] ,
alignfull : [
{ selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li' , styles : { textAlign : 'justify' } , defaultBlock : 'div' }
] ,
bold : [
{ inline : 'strong' , remove : 'all' } ,
{ inline : 'span' , styles : { fontWeight : 'bold' } } ,
{ inline : 'b' , remove : 'all' }
] ,
italic : [
{ inline : 'em' , remove : 'all' } ,
{ inline : 'span' , styles : { fontStyle : 'italic' } } ,
{ inline : 'i' , remove : 'all' }
] ,
underline : [
{ inline : 'span' , styles : { textDecoration : 'underline' } , exact : true } ,
{ inline : 'u' , remove : 'all' }
] ,
strikethrough : [
{ inline : 'span' , styles : { textDecoration : 'line-through' } , exact : true } ,
{ inline : 'strike' , remove : 'all' }
] ,
forecolor : { inline : 'span' , styles : { color : '%value' } , wrap _links : false } ,
hilitecolor : { inline : 'span' , styles : { backgroundColor : '%value' } , wrap _links : false } ,
fontname : { inline : 'span' , styles : { fontFamily : '%value' } } ,
fontsize : { inline : 'span' , styles : { fontSize : '%value' } } ,
fontsize _class : { inline : 'span' , attributes : { 'class' : '%value' } } ,
blockquote : { block : 'blockquote' , wrapper : 1 , remove : 'all' } ,
subscript : { inline : 'sub' } ,
superscript : { inline : 'sup' } ,
link : { inline : 'a' , selector : 'a' , remove : 'all' , split : true , deep : true ,
onmatch : function ( node ) {
return true ;
} ,
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
onformat : function ( elm , fmt , vars ) {
each ( vars , function ( value , key ) {
dom . setAttrib ( elm , key , value ) ;
} ) ;
}
} ,
2012-03-21 04:47:31 +01:00
2012-05-16 02:18:46 +02:00
removeformat : [
{ selector : 'b,strong,em,i,font,u,strike' , remove : 'all' , split : true , expand : false , block _expand : true , deep : true } ,
{ selector : 'span' , attributes : [ 'style' , 'class' ] , remove : 'empty' , split : true , expand : false , deep : true } ,
{ selector : '*' , attributes : [ 'style' , 'class' ] , split : false , expand : false , deep : true }
]
} ) ;
2012-03-21 04:47:31 +01:00
2012-05-16 02:18:46 +02:00
// Register default block formats
each ( 'p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp' . split ( /\s/ ) , function ( name ) {
register ( name , { block : name , remove : 'all' } ) ;
} ) ;
2012-03-21 04:47:31 +01:00
2012-05-16 02:18:46 +02:00
// Register user defined formats
register ( ed . settings . formats ) ;
2012-03-21 04:47:31 +01:00
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
function addKeyboardShortcuts ( ) {
// Add some inline shortcuts
ed . addShortcut ( 'ctrl+b' , 'bold_desc' , 'Bold' ) ;
ed . addShortcut ( 'ctrl+i' , 'italic_desc' , 'Italic' ) ;
ed . addShortcut ( 'ctrl+u' , 'underline_desc' , 'Underline' ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
// BlockFormat shortcuts keys
for ( var i = 1 ; i <= 6 ; i ++ ) {
ed . addShortcut ( 'ctrl+' + i , '' , [ 'FormatBlock' , false , 'h' + i ] ) ;
}
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
ed . addShortcut ( 'ctrl+7' , '' , [ 'FormatBlock' , false , 'p' ] ) ;
ed . addShortcut ( 'ctrl+8' , '' , [ 'FormatBlock' , false , 'div' ] ) ;
ed . addShortcut ( 'ctrl+9' , '' , [ 'FormatBlock' , false , 'address' ] ) ;
2010-07-02 01:48:07 +02:00
} ;
// Public functions
function get ( name ) {
return name ? formats [ name ] : formats ;
} ;
function register ( name , format ) {
if ( name ) {
if ( typeof ( name ) !== 'string' ) {
each ( name , function ( format , name ) {
register ( name , format ) ;
} ) ;
} else {
// Force format into array and add it to internal collection
format = format . length ? format : [ format ] ;
each ( format , function ( format ) {
// Set deep to false by default on selector formats this to avoid removing
// alignment on images inside paragraphs when alignment is changed on paragraphs
2012-05-16 02:18:46 +02:00
if ( format . deep === undef )
2010-07-02 01:48:07 +02:00
format . deep = ! format . selector ;
// Default to true
2012-05-16 02:18:46 +02:00
if ( format . split === undef )
2010-07-02 01:48:07 +02:00
format . split = ! format . selector || format . inline ;
// Default to true
2012-05-16 02:18:46 +02:00
if ( format . remove === undef && format . selector && ! format . inline )
2010-07-02 01:48:07 +02:00
format . remove = 'none' ;
// Mark format as a mixed format inline + block level
if ( format . selector && format . inline ) {
format . mixed = true ;
format . block _expand = true ;
}
// Split classes if needed
if ( typeof ( format . classes ) === 'string' )
format . classes = format . classes . split ( /\s+/ ) ;
} ) ;
formats [ name ] = format ;
}
}
} ;
2012-03-21 04:47:31 +01:00
var getTextDecoration = function ( node ) {
var decoration ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
ed . dom . getParent ( node , function ( n ) {
decoration = ed . dom . getStyle ( n , 'text-decoration' ) ;
return decoration && decoration !== 'none' ;
} ) ;
return decoration ;
} ;
var processUnderlineAndColor = function ( node ) {
var textDecoration ;
if ( node . nodeType === 1 && node . parentNode && node . parentNode . nodeType === 1 ) {
textDecoration = getTextDecoration ( node . parentNode ) ;
if ( ed . dom . getStyle ( node , 'color' ) && textDecoration ) {
ed . dom . setStyle ( node , 'text-decoration' , textDecoration ) ;
} else if ( ed . dom . getStyle ( node , 'textdecoration' ) === textDecoration ) {
ed . dom . setStyle ( node , 'text-decoration' , null ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
}
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function apply ( name , vars , node ) {
var formatList = get ( name ) , format = formatList [ 0 ] , bookmark , rng , i , isCollapsed = selection . isCollapsed ( ) ;
2010-07-02 01:48:07 +02:00
function setElementFormat ( elm , fmt ) {
fmt = fmt || format ;
if ( elm ) {
2012-03-21 04:47:31 +01:00
if ( fmt . onformat ) {
fmt . onformat ( elm , fmt , vars , node ) ;
}
2010-07-02 01:48:07 +02:00
each ( fmt . styles , function ( value , name ) {
dom . setStyle ( elm , name , replaceVars ( value , vars ) ) ;
} ) ;
each ( fmt . attributes , function ( value , name ) {
dom . setAttrib ( elm , name , replaceVars ( value , vars ) ) ;
} ) ;
each ( fmt . classes , function ( value ) {
value = replaceVars ( value , vars ) ;
if ( ! dom . hasClass ( elm , value ) )
dom . addClass ( elm , value ) ;
} ) ;
}
} ;
2012-03-21 04:47:31 +01:00
function adjustSelectionToVisibleSelection ( ) {
function findSelectionEnd ( start , end ) {
var walker = new TreeWalker ( end ) ;
for ( node = walker . current ( ) ; node ; node = walker . prev ( ) ) {
2012-05-16 02:18:46 +02:00
if ( node . childNodes . length > 1 || node == start || node . tagName == 'BR' ) {
2012-03-21 04:47:31 +01:00
return node ;
}
}
} ;
// Adjust selection so that a end container with a end offset of zero is not included in the selection
// as this isn't visible to the user.
var rng = ed . selection . getRng ( ) ;
var start = rng . startContainer ;
var end = rng . endContainer ;
2012-05-16 02:18:46 +02:00
if ( start != end && rng . endOffset === 0 ) {
2012-03-21 04:47:31 +01:00
var newEnd = findSelectionEnd ( start , end ) ;
var endOffset = newEnd . nodeType == 3 ? newEnd . length : newEnd . childNodes . length ;
rng . setEnd ( newEnd , endOffset ) ;
}
return rng ;
}
function applyStyleToList ( node , bookmark , wrapElm , newWrappers , process ) {
var nodes = [ ] , listIndex = - 1 , list , startIndex = - 1 , endIndex = - 1 , currentWrapElm ;
// find the index of the first child list.
each ( node . childNodes , function ( n , index ) {
if ( n . nodeName === "UL" || n . nodeName === "OL" ) {
listIndex = index ;
list = n ;
return false ;
}
} ) ;
// get the index of the bookmarks
each ( node . childNodes , function ( n , index ) {
if ( n . nodeName === "SPAN" && dom . getAttrib ( n , "data-mce-type" ) == "bookmark" ) {
if ( n . id == bookmark . id + "_start" ) {
startIndex = index ;
} else if ( n . id == bookmark . id + "_end" ) {
endIndex = index ;
}
}
} ) ;
// if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally
if ( listIndex <= 0 || ( startIndex < listIndex && endIndex > listIndex ) ) {
each ( tinymce . grep ( node . childNodes ) , process ) ;
return 0 ;
} else {
currentWrapElm = dom . clone ( wrapElm , FALSE ) ;
// create a list of the nodes on the same side of the list as the selection
each ( tinymce . grep ( node . childNodes ) , function ( n , index ) {
if ( ( startIndex < listIndex && index < listIndex ) || ( startIndex > listIndex && index > listIndex ) ) {
nodes . push ( n ) ;
n . parentNode . removeChild ( n ) ;
}
} ) ;
// insert the wrapping element either before or after the list.
if ( startIndex < listIndex ) {
node . insertBefore ( currentWrapElm , list ) ;
} else if ( startIndex > listIndex ) {
node . insertBefore ( currentWrapElm , list . nextSibling ) ;
}
// add the new nodes to the list.
newWrappers . push ( currentWrapElm ) ;
each ( nodes , function ( node ) {
currentWrapElm . appendChild ( node ) ;
} ) ;
return currentWrapElm ;
}
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function applyRngStyle ( rng , bookmark , node _specific ) {
var newWrappers = [ ] , wrapName , wrapElm , contentEditable = true ;
2010-07-02 01:48:07 +02:00
// Setup wrapper element
wrapName = format . inline || format . block ;
wrapElm = dom . create ( wrapName ) ;
setElementFormat ( wrapElm ) ;
rangeUtils . walk ( rng , function ( nodes ) {
var currentWrapElm ;
function process ( node ) {
2012-03-21 04:47:31 +01:00
var nodeName , parentName , found , hasContentEditableState , lastContentEditable ;
lastContentEditable = contentEditable ;
nodeName = node . nodeName . toLowerCase ( ) ;
parentName = node . parentNode . nodeName . toLowerCase ( ) ;
// Node has a contentEditable value
if ( node . nodeType === 1 && getContentEditable ( node ) ) {
lastContentEditable = contentEditable ;
contentEditable = getContentEditable ( node ) === "true" ;
hasContentEditableState = true ; // We don't want to wrap the container only it's children
}
2010-07-02 01:48:07 +02:00
// Stop wrapping on br elements
if ( isEq ( nodeName , 'br' ) ) {
currentWrapElm = 0 ;
// Remove any br elements when we wrap things
if ( format . block )
dom . remove ( node ) ;
return ;
}
// If node is wrapper type
if ( format . wrapper && matchNode ( node , name , vars ) ) {
currentWrapElm = 0 ;
return ;
}
// Can we rename the block
2012-03-21 04:47:31 +01:00
if ( contentEditable && ! hasContentEditableState && format . block && ! format . wrapper && isTextBlock ( nodeName ) ) {
2010-07-02 01:48:07 +02:00
node = dom . rename ( node , wrapName ) ;
setElementFormat ( node ) ;
newWrappers . push ( node ) ;
currentWrapElm = 0 ;
return ;
}
// Handle selector patterns
if ( format . selector ) {
// Look for matching formats
each ( formatList , function ( format ) {
2012-03-21 04:47:31 +01:00
// Check collapsed state if it exists
if ( 'collapsed' in format && format . collapsed !== isCollapsed ) {
return ;
}
2010-07-02 01:48:07 +02:00
if ( dom . is ( node , format . selector ) && ! isCaretNode ( node ) ) {
setElementFormat ( node , format ) ;
found = true ;
}
} ) ;
// Continue processing if a selector match wasn't found and a inline element is defined
if ( ! format . inline || found ) {
currentWrapElm = 0 ;
return ;
}
}
// Is it valid to wrap this item
2012-03-21 04:47:31 +01:00
if ( contentEditable && ! hasContentEditableState && isValid ( wrapName , nodeName ) && isValid ( parentName , wrapName ) &&
! ( ! node _specific && node . nodeType === 3 && node . nodeValue . length === 1 && node . nodeValue . charCodeAt ( 0 ) === 65279 ) && ! isCaretNode ( node ) ) {
2010-07-02 01:48:07 +02:00
// Start wrapping
if ( ! currentWrapElm ) {
// Wrap the node
2012-03-21 04:47:31 +01:00
currentWrapElm = dom . clone ( wrapElm , FALSE ) ;
2010-07-02 01:48:07 +02:00
node . parentNode . insertBefore ( currentWrapElm , node ) ;
newWrappers . push ( currentWrapElm ) ;
}
currentWrapElm . appendChild ( node ) ;
2012-03-21 04:47:31 +01:00
} else if ( nodeName == 'li' && bookmark ) {
// Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element.
currentWrapElm = applyStyleToList ( node , bookmark , wrapElm , newWrappers , process ) ;
2010-07-02 01:48:07 +02:00
} else {
// Start a new wrapper for possible children
currentWrapElm = 0 ;
2012-03-21 04:47:31 +01:00
2010-07-02 01:48:07 +02:00
each ( tinymce . grep ( node . childNodes ) , process ) ;
2012-03-21 04:47:31 +01:00
if ( hasContentEditableState ) {
contentEditable = lastContentEditable ; // Restore last contentEditable state from stack
}
2010-07-02 01:48:07 +02:00
// End the last wrapper
currentWrapElm = 0 ;
}
} ;
// Process siblings from range
each ( nodes , process ) ;
} ) ;
2012-03-21 04:47:31 +01:00
// Wrap links inside as well, for example color inside a link when the wrapper is around the link
if ( format . wrap _links === false ) {
each ( newWrappers , function ( node ) {
function process ( node ) {
var i , currentWrapElm , children ;
if ( node . nodeName === 'A' ) {
currentWrapElm = dom . clone ( wrapElm , FALSE ) ;
newWrappers . push ( currentWrapElm ) ;
children = tinymce . grep ( node . childNodes ) ;
for ( i = 0 ; i < children . length ; i ++ )
currentWrapElm . appendChild ( children [ i ] ) ;
node . appendChild ( currentWrapElm ) ;
}
each ( tinymce . grep ( node . childNodes ) , process ) ;
} ;
process ( node ) ;
} ) ;
}
2010-07-02 01:48:07 +02:00
// Cleanup
2012-03-21 04:47:31 +01:00
2010-07-02 01:48:07 +02:00
each ( newWrappers , function ( node ) {
var childCount ;
function getChildCount ( node ) {
var count = 0 ;
each ( node . childNodes , function ( node ) {
if ( ! isWhiteSpaceNode ( node ) && ! isBookmarkNode ( node ) )
count ++ ;
} ) ;
return count ;
} ;
function mergeStyles ( node ) {
var child , clone ;
each ( node . childNodes , function ( node ) {
if ( node . nodeType == 1 && ! isBookmarkNode ( node ) && ! isCaretNode ( node ) ) {
child = node ;
return FALSE ; // break loop
}
} ) ;
// If child was found and of the same type as the current node
if ( child && matchName ( child , format ) ) {
2012-03-21 04:47:31 +01:00
clone = dom . clone ( child , FALSE ) ;
2010-07-02 01:48:07 +02:00
setElementFormat ( clone ) ;
dom . replace ( clone , node , TRUE ) ;
dom . remove ( child , 1 ) ;
}
return clone || node ;
} ;
childCount = getChildCount ( node ) ;
2012-03-21 04:47:31 +01:00
// Remove empty nodes but only if there is multiple wrappers and they are not block
// elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at
if ( ( newWrappers . length > 1 || ! isBlock ( node ) ) && childCount === 0 ) {
2010-07-02 01:48:07 +02:00
dom . remove ( node , 1 ) ;
return ;
}
if ( format . inline || format . wrapper ) {
// Merges the current node with it's children of similar type to reduce the number of elements
if ( ! format . exact && childCount === 1 )
node = mergeStyles ( node ) ;
// Remove/merge children
each ( formatList , function ( format ) {
// Merge all children of similar type will move styles from child to parent
// this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
// will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
each ( dom . select ( format . inline , node ) , function ( child ) {
2012-03-21 04:47:31 +01:00
var parent ;
// When wrap_links is set to false we don't want
// to remove the format on children within links
if ( format . wrap _links === false ) {
parent = child . parentNode ;
do {
if ( parent . nodeName === 'A' )
return ;
} while ( parent = parent . parentNode ) ;
}
2010-07-02 01:48:07 +02:00
removeFormat ( format , vars , child , format . exact ? child : null ) ;
} ) ;
} ) ;
// Remove child if direct parent is of same type
if ( matchNode ( node . parentNode , name , vars ) ) {
dom . remove ( node , 1 ) ;
node = 0 ;
return TRUE ;
}
// Look for parent with similar style format
if ( format . merge _with _parents ) {
dom . getParent ( node . parentNode , function ( parent ) {
if ( matchNode ( parent , name , vars ) ) {
dom . remove ( node , 1 ) ;
node = 0 ;
return TRUE ;
}
} ) ;
}
// Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
2012-03-21 04:47:31 +01:00
if ( node && format . merge _siblings !== false ) {
2010-07-02 01:48:07 +02:00
node = mergeSiblings ( getNonWhiteSpaceSibling ( node ) , node ) ;
node = mergeSiblings ( node , getNonWhiteSpaceSibling ( node , TRUE ) ) ;
}
}
} ) ;
} ;
if ( format ) {
if ( node ) {
2012-03-21 04:47:31 +01:00
if ( node . nodeType ) {
rng = dom . createRng ( ) ;
rng . setStartBefore ( node ) ;
rng . setEndAfter ( node ) ;
applyRngStyle ( expandRng ( rng , formatList ) , null , true ) ;
} else {
applyRngStyle ( node , null , true ) ;
}
2010-07-02 01:48:07 +02:00
} else {
2012-03-21 04:47:31 +01:00
if ( ! isCollapsed || ! format . inline || dom . select ( 'td.mceSelected,th.mceSelected' ) . length ) {
// Obtain selection node before selection is unselected by applyRngStyle()
var curSelNode = ed . selection . getNode ( ) ;
// If the formats have a default block and we can't find a parent block then start wrapping it with a DIV this is for forced_root_blocks: false
// It's kind of a hack but people should be using the default block type P since all desktop editors work that way
if ( ! forcedRootBlock && formatList [ 0 ] . defaultBlock && ! dom . getParent ( curSelNode , dom . isBlock ) ) {
apply ( formatList [ 0 ] . defaultBlock ) ;
}
2010-07-02 01:48:07 +02:00
// Apply formatting to selection
2012-03-21 04:47:31 +01:00
ed . selection . setRng ( adjustSelectionToVisibleSelection ( ) ) ;
2010-07-02 01:48:07 +02:00
bookmark = selection . getBookmark ( ) ;
2012-03-21 04:47:31 +01:00
applyRngStyle ( expandRng ( selection . getRng ( TRUE ) , formatList ) , bookmark ) ;
// Colored nodes should be underlined so that the color of the underline matches the text color.
if ( format . styles && ( format . styles . color || format . styles . textDecoration ) ) {
tinymce . walk ( curSelNode , processUnderlineAndColor , 'childNodes' ) ;
processUnderlineAndColor ( curSelNode ) ;
}
2010-07-02 01:48:07 +02:00
selection . moveToBookmark ( bookmark ) ;
2012-03-21 04:47:31 +01:00
moveStart ( selection . getRng ( TRUE ) ) ;
2010-07-02 01:48:07 +02:00
ed . nodeChanged ( ) ;
} else
performCaretAction ( 'apply' , name , vars ) ;
}
}
} ;
function remove ( name , vars , node ) {
2012-03-21 04:47:31 +01:00
var formatList = get ( name ) , format = formatList [ 0 ] , bookmark , i , rng , contentEditable = true ;
2010-07-02 01:48:07 +02:00
// Merges the styles for each node
function process ( node ) {
2012-03-21 04:47:31 +01:00
var children , i , l , localContentEditable , lastContentEditable , hasContentEditableState ;
// Node has a contentEditable value
if ( node . nodeType === 1 && getContentEditable ( node ) ) {
lastContentEditable = contentEditable ;
contentEditable = getContentEditable ( node ) === "true" ;
hasContentEditableState = true ; // We don't want to wrap the container only it's children
}
2010-07-02 01:48:07 +02:00
// Grab the children first since the nodelist might be changed
children = tinymce . grep ( node . childNodes ) ;
// Process current node
2012-03-21 04:47:31 +01:00
if ( contentEditable && ! hasContentEditableState ) {
for ( i = 0 , l = formatList . length ; i < l ; i ++ ) {
if ( removeFormat ( formatList [ i ] , vars , node , node ) )
break ;
}
2010-07-02 01:48:07 +02:00
}
// Process the children
if ( format . deep ) {
2012-03-21 04:47:31 +01:00
if ( children . length ) {
for ( i = 0 , l = children . length ; i < l ; i ++ )
process ( children [ i ] ) ;
if ( hasContentEditableState ) {
contentEditable = lastContentEditable ; // Restore last contentEditable state from stack
}
}
2010-07-02 01:48:07 +02:00
}
} ;
function findFormatRoot ( container ) {
var formatRoot ;
// Find format root
each ( getParents ( container . parentNode ) . reverse ( ) , function ( parent ) {
var format ;
// Find format root element
if ( ! formatRoot && parent . id != '_start' && parent . id != '_end' ) {
// Is the node matching the format we are looking for
format = matchNode ( parent , name , vars ) ;
if ( format && format . split !== false )
formatRoot = parent ;
}
} ) ;
return formatRoot ;
} ;
function wrapAndSplit ( format _root , container , target , split ) {
var parent , clone , lastClone , firstClone , i , formatRootParent ;
// Format root found then clone formats and split it
if ( format _root ) {
formatRootParent = format _root . parentNode ;
for ( parent = container . parentNode ; parent && parent != formatRootParent ; parent = parent . parentNode ) {
2012-03-21 04:47:31 +01:00
clone = dom . clone ( parent , FALSE ) ;
2010-07-02 01:48:07 +02:00
for ( i = 0 ; i < formatList . length ; i ++ ) {
if ( removeFormat ( formatList [ i ] , vars , clone , clone ) ) {
clone = 0 ;
break ;
}
}
// Build wrapper node
if ( clone ) {
if ( lastClone )
clone . appendChild ( lastClone ) ;
if ( ! firstClone )
firstClone = clone ;
lastClone = clone ;
}
}
// Never split block elements if the format is mixed
if ( split && ( ! format . mixed || ! isBlock ( format _root ) ) )
container = dom . split ( format _root , container ) ;
// Wrap container in cloned formats
if ( lastClone ) {
target . parentNode . insertBefore ( lastClone , target ) ;
firstClone . appendChild ( target ) ;
}
}
return container ;
} ;
function splitToFormatRoot ( container ) {
return wrapAndSplit ( findFormatRoot ( container ) , container , container , true ) ;
} ;
function unwrap ( start ) {
var node = dom . get ( start ? '_start' : '_end' ) ,
out = node [ start ? 'firstChild' : 'lastChild' ] ;
// If the end is placed within the start the result will be removed
// So this checks if the out node is a bookmark node if it is it
// checks for another more suitable node
if ( isBookmarkNode ( out ) )
out = out [ start ? 'firstChild' : 'lastChild' ] ;
dom . remove ( node , true ) ;
return out ;
} ;
function removeRngStyle ( rng ) {
2012-05-16 02:18:46 +02:00
var startContainer , endContainer , node ;
2010-07-02 01:48:07 +02:00
rng = expandRng ( rng , formatList , TRUE ) ;
if ( format . split ) {
startContainer = getContainer ( rng , TRUE ) ;
endContainer = getContainer ( rng ) ;
if ( startContainer != endContainer ) {
2012-05-16 02:18:46 +02:00
// WebKit will render the table incorrectly if we wrap a TD in a SPAN so lets see if the can use the first child instead
// This will happen if you tripple click a table cell and use remove formatting
if ( /^(TR|TD)$/ . test ( startContainer . nodeName ) && startContainer . firstChild ) {
startContainer = ( startContainer . nodeName == "TD" ? startContainer . firstChild : startContainer . firstChild . firstChild ) || startContainer ;
}
2010-07-02 01:48:07 +02:00
// Wrap start/end nodes in span element since these might be cloned/moved
2012-03-21 04:47:31 +01:00
startContainer = wrap ( startContainer , 'span' , { id : '_start' , 'data-mce-type' : 'bookmark' } ) ;
endContainer = wrap ( endContainer , 'span' , { id : '_end' , 'data-mce-type' : 'bookmark' } ) ;
2010-07-02 01:48:07 +02:00
// Split start/end
splitToFormatRoot ( startContainer ) ;
splitToFormatRoot ( endContainer ) ;
// Unwrap start/end to get real elements again
startContainer = unwrap ( TRUE ) ;
endContainer = unwrap ( ) ;
} else
startContainer = endContainer = splitToFormatRoot ( startContainer ) ;
// Update range positions since they might have changed after the split operations
rng . startContainer = startContainer . parentNode ;
rng . startOffset = nodeIndex ( startContainer ) ;
rng . endContainer = endContainer . parentNode ;
rng . endOffset = nodeIndex ( endContainer ) + 1 ;
}
// Remove items between start/end
rangeUtils . walk ( rng , function ( nodes ) {
each ( nodes , function ( node ) {
process ( node ) ;
2012-03-21 04:47:31 +01:00
// Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
if ( node . nodeType === 1 && ed . dom . getStyle ( node , 'text-decoration' ) === 'underline' && node . parentNode && getTextDecoration ( node . parentNode ) === 'underline' ) {
removeFormat ( { 'deep' : false , 'exact' : true , 'inline' : 'span' , 'styles' : { 'textDecoration' : 'underline' } } , null , node ) ;
}
2010-07-02 01:48:07 +02:00
} ) ;
} ) ;
} ;
// Handle node
if ( node ) {
2012-03-21 04:47:31 +01:00
if ( node . nodeType ) {
rng = dom . createRng ( ) ;
rng . setStartBefore ( node ) ;
rng . setEndAfter ( node ) ;
removeRngStyle ( rng ) ;
} else {
removeRngStyle ( node ) ;
}
2010-07-02 01:48:07 +02:00
return ;
}
2012-03-21 04:47:31 +01:00
if ( ! selection . isCollapsed ( ) || ! format . inline || dom . select ( 'td.mceSelected,th.mceSelected' ) . length ) {
2010-07-02 01:48:07 +02:00
bookmark = selection . getBookmark ( ) ;
removeRngStyle ( selection . getRng ( TRUE ) ) ;
selection . moveToBookmark ( bookmark ) ;
2012-03-21 04:47:31 +01:00
// Check if start element still has formatting then we are at: "<b>text|</b>text" and need to move the start into the next text node
if ( format . inline && match ( name , vars , selection . getStart ( ) ) ) {
moveStart ( selection . getRng ( true ) ) ;
}
2010-07-02 01:48:07 +02:00
ed . nodeChanged ( ) ;
} else
performCaretAction ( 'remove' , name , vars ) ;
} ;
function toggle ( name , vars , node ) {
2012-03-21 04:47:31 +01:00
var fmt = get ( name ) ;
2012-05-16 02:18:46 +02:00
if ( match ( name , vars , node ) && ( ! ( 'toggle' in fmt [ 0 ] ) || fmt [ 0 ] . toggle ) )
2010-07-02 01:48:07 +02:00
remove ( name , vars , node ) ;
else
apply ( name , vars , node ) ;
} ;
function matchNode ( node , name , vars , similar ) {
var formatList = get ( name ) , format , i , classes ;
function matchItems ( node , format , item _name ) {
var key , value , items = format [ item _name ] , i ;
2012-03-21 04:47:31 +01:00
// Custom match
if ( format . onmatch ) {
return format . onmatch ( node , format , item _name ) ;
}
2010-07-02 01:48:07 +02:00
// Check all items
if ( items ) {
// Non indexed object
2012-05-16 02:18:46 +02:00
if ( items . length === undef ) {
2010-07-02 01:48:07 +02:00
for ( key in items ) {
if ( items . hasOwnProperty ( key ) ) {
if ( item _name === 'attributes' )
value = dom . getAttrib ( node , key ) ;
else
value = getStyle ( node , key ) ;
if ( similar && ! value && ! format . exact )
return ;
if ( ( ! similar || format . exact ) && ! isEq ( value , replaceVars ( items [ key ] , vars ) ) )
return ;
}
}
} else {
// Only one match needed for indexed arrays
for ( i = 0 ; i < items . length ; i ++ ) {
if ( item _name === 'attributes' ? dom . getAttrib ( node , items [ i ] ) : getStyle ( node , items [ i ] ) )
return format ;
}
}
}
return format ;
} ;
if ( formatList && node ) {
// Check each format in list
for ( i = 0 ; i < formatList . length ; i ++ ) {
format = formatList [ i ] ;
// Name name, attributes, styles and classes
if ( matchName ( node , format ) && matchItems ( node , format , 'attributes' ) && matchItems ( node , format , 'styles' ) ) {
// Match classes
if ( classes = format . classes ) {
for ( i = 0 ; i < classes . length ; i ++ ) {
if ( ! dom . hasClass ( node , classes [ i ] ) )
return ;
}
}
return format ;
}
}
}
} ;
function match ( name , vars , node ) {
2012-03-21 04:47:31 +01:00
var startNode ;
2010-07-02 01:48:07 +02:00
function matchParents ( node ) {
// Find first node with similar format settings
node = dom . getParent ( node , function ( node ) {
return ! ! matchNode ( node , name , vars , true ) ;
} ) ;
// Do an exact check on the similar format element
return matchNode ( node , name , vars ) ;
} ;
// Check specified node
if ( node )
return matchParents ( node ) ;
// Check selected node
node = selection . getNode ( ) ;
if ( matchParents ( node ) )
return TRUE ;
// Check start node if it's different
startNode = selection . getStart ( ) ;
if ( startNode != node ) {
if ( matchParents ( startNode ) )
return TRUE ;
}
return FALSE ;
} ;
function matchAll ( names , vars ) {
var startElement , matchedFormatNames = [ ] , checkedMap = { } , i , ni , name ;
// Check start of selection for formats
startElement = selection . getStart ( ) ;
dom . getParent ( startElement , function ( node ) {
var i , name ;
for ( i = 0 ; i < names . length ; i ++ ) {
name = names [ i ] ;
if ( ! checkedMap [ name ] && matchNode ( node , name , vars ) ) {
checkedMap [ name ] = true ;
matchedFormatNames . push ( name ) ;
}
}
} ) ;
return matchedFormatNames ;
} ;
function canApply ( name ) {
var formatList = get ( name ) , startNode , parents , i , x , selector ;
if ( formatList ) {
startNode = selection . getStart ( ) ;
parents = getParents ( startNode ) ;
for ( x = formatList . length - 1 ; x >= 0 ; x -- ) {
selector = formatList [ x ] . selector ;
// Format is not selector based, then always return TRUE
if ( ! selector )
return TRUE ;
for ( i = parents . length - 1 ; i >= 0 ; i -- ) {
if ( dom . is ( parents [ i ] , selector ) )
return TRUE ;
}
}
}
return FALSE ;
} ;
// Expose to public
tinymce . extend ( this , {
get : get ,
register : register ,
apply : apply ,
remove : remove ,
toggle : toggle ,
match : match ,
matchAll : matchAll ,
matchNode : matchNode ,
canApply : canApply
} ) ;
2012-05-16 02:18:46 +02:00
// Initialize
defaultFormats ( ) ;
addKeyboardShortcuts ( ) ;
2010-07-02 01:48:07 +02:00
// Private functions
function matchName ( node , format ) {
// Check for inline match
if ( isEq ( node , format . inline ) )
return TRUE ;
// Check for block match
if ( isEq ( node , format . block ) )
return TRUE ;
// Check for selector match
if ( format . selector )
return dom . is ( node , format . selector ) ;
} ;
function isEq ( str1 , str2 ) {
str1 = str1 || '' ;
str2 = str2 || '' ;
str1 = '' + ( str1 . nodeName || str1 ) ;
str2 = '' + ( str2 . nodeName || str2 ) ;
return str1 . toLowerCase ( ) == str2 . toLowerCase ( ) ;
} ;
function getStyle ( node , name ) {
var styleVal = dom . getStyle ( node , name ) ;
// Force the format to hex
if ( name == 'color' || name == 'backgroundColor' )
styleVal = dom . toHex ( styleVal ) ;
// Opera will return bold as 700
if ( name == 'fontWeight' && styleVal == 700 )
styleVal = 'bold' ;
return '' + styleVal ;
} ;
function replaceVars ( value , vars ) {
if ( typeof ( value ) != "string" )
value = value ( vars ) ;
else if ( vars ) {
value = value . replace ( /%(\w+)/g , function ( str , name ) {
return vars [ name ] || str ;
} ) ;
}
return value ;
} ;
function isWhiteSpaceNode ( node ) {
2012-03-21 04:47:31 +01:00
return node && node . nodeType === 3 && /^([\t \r\n]+|)$/ . test ( node . nodeValue ) ;
2010-07-02 01:48:07 +02:00
} ;
function wrap ( node , name , attrs ) {
var wrapper = dom . create ( name , attrs ) ;
node . parentNode . insertBefore ( wrapper , node ) ;
wrapper . appendChild ( node ) ;
return wrapper ;
} ;
function expandRng ( rng , format , remove ) {
2012-05-16 02:18:46 +02:00
var sibling , lastIdx , leaf , endPoint ,
2012-03-21 04:47:31 +01:00
startContainer = rng . startContainer ,
2010-07-02 01:48:07 +02:00
startOffset = rng . startOffset ,
endContainer = rng . endContainer ,
2012-05-16 02:18:46 +02:00
endOffset = rng . endOffset ;
2010-07-02 01:48:07 +02:00
// This function walks up the tree if there is no siblings before/after the node
2012-03-21 04:47:31 +01:00
function findParentContainer ( start ) {
2012-05-16 02:18:46 +02:00
var container , parent , child , sibling , siblingName , root ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
container = parent = start ? startContainer : endContainer ;
siblingName = start ? 'previousSibling' : 'nextSibling' ;
root = dom . getRoot ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// If it's a text node and the offset is inside the text
if ( container . nodeType == 3 && ! isWhiteSpaceNode ( container ) ) {
if ( start ? startOffset > 0 : endOffset < container . nodeValue . length ) {
2010-07-02 01:48:07 +02:00
return container ;
2012-03-21 04:47:31 +01:00
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
for ( ; ; ) {
// Stop expanding on block elements
if ( ! format [ 0 ] . block _expand && isBlock ( parent ) )
return parent ;
// Walk left/right
for ( sibling = parent [ siblingName ] ; sibling ; sibling = sibling [ siblingName ] ) {
if ( ! isBookmarkNode ( sibling ) && ! isWhiteSpaceNode ( sibling ) ) {
return parent ;
}
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Check if we can move up are we at root level or body level
if ( parent . parentNode == root ) {
container = parent ;
break ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
parent = parent . parentNode ;
2010-07-02 01:48:07 +02:00
}
return container ;
} ;
2012-03-21 04:47:31 +01:00
// This function walks down the tree to find the leaf at the selection.
// The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
function findLeaf ( node , offset ) {
2012-05-16 02:18:46 +02:00
if ( offset === undef )
2012-03-21 04:47:31 +01:00
offset = node . nodeType === 3 ? node . length : node . childNodes . length ;
while ( node && node . hasChildNodes ( ) ) {
node = node . childNodes [ offset ] ;
if ( node )
offset = node . nodeType === 3 ? node . length : node . childNodes . length ;
}
return { node : node , offset : offset } ;
}
2010-07-02 01:48:07 +02:00
// If index based start position then resolve it
if ( startContainer . nodeType == 1 && startContainer . hasChildNodes ( ) ) {
lastIdx = startContainer . childNodes . length - 1 ;
startContainer = startContainer . childNodes [ startOffset > lastIdx ? lastIdx : startOffset ] ;
if ( startContainer . nodeType == 3 )
startOffset = 0 ;
}
// If index based end position then resolve it
if ( endContainer . nodeType == 1 && endContainer . hasChildNodes ( ) ) {
lastIdx = endContainer . childNodes . length - 1 ;
endContainer = endContainer . childNodes [ endOffset > lastIdx ? lastIdx : endOffset - 1 ] ;
if ( endContainer . nodeType == 3 )
endOffset = endContainer . nodeValue . length ;
}
2012-03-21 04:47:31 +01:00
// Expands the node to the closes contentEditable false element if it exists
function findParentContentEditable ( node ) {
var parent = node ;
while ( parent ) {
if ( parent . nodeType === 1 && getContentEditable ( parent ) ) {
return getContentEditable ( parent ) === "false" ? parent : node ;
}
parent = parent . parentNode ;
}
return node ;
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
function findWordEndPoint ( container , offset , start ) {
var walker , node , pos , lastTextNode ;
function findSpace ( node , offset ) {
var pos , pos2 , str = node . nodeValue ;
if ( typeof ( offset ) == "undefined" ) {
offset = start ? str . length : 0 ;
}
if ( start ) {
pos = str . lastIndexOf ( ' ' , offset ) ;
pos2 = str . lastIndexOf ( '\u00a0' , offset ) ;
pos = pos > pos2 ? pos : pos2 ;
// Include the space on remove to avoid tag soup
if ( pos !== - 1 && ! remove ) {
pos ++ ;
}
} else {
pos = str . indexOf ( ' ' , offset ) ;
pos2 = str . indexOf ( '\u00a0' , offset ) ;
pos = pos !== - 1 && ( pos2 === - 1 || pos < pos2 ) ? pos : pos2 ;
}
return pos ;
} ;
if ( container . nodeType === 3 ) {
pos = findSpace ( container , offset ) ;
if ( pos !== - 1 ) {
return { container : container , offset : pos } ;
}
lastTextNode = container ;
}
// Walk the nodes inside the block
walker = new TreeWalker ( container , dom . getParent ( container , isBlock ) || ed . getBody ( ) ) ;
while ( node = walker [ start ? 'prev' : 'next' ] ( ) ) {
if ( node . nodeType === 3 ) {
lastTextNode = node ;
pos = findSpace ( node ) ;
if ( pos !== - 1 ) {
return { container : node , offset : pos } ;
}
} else if ( isBlock ( node ) ) {
break ;
}
}
if ( lastTextNode ) {
if ( start ) {
offset = 0 ;
} else {
offset = lastTextNode . length ;
}
return { container : lastTextNode , offset : offset } ;
}
} ;
function findSelectorEndPoint ( container , sibling _name ) {
var parents , i , y , curFormat ;
if ( container . nodeType == 3 && container . nodeValue . length === 0 && container [ sibling _name ] )
container = container [ sibling _name ] ;
parents = getParents ( container ) ;
for ( i = 0 ; i < parents . length ; i ++ ) {
for ( y = 0 ; y < format . length ; y ++ ) {
curFormat = format [ y ] ;
// If collapsed state is set then skip formats that doesn't match that
if ( "collapsed" in curFormat && curFormat . collapsed !== rng . collapsed )
continue ;
if ( dom . is ( parents [ i ] , curFormat . selector ) )
return parents [ i ] ;
}
}
return container ;
} ;
function findBlockEndPoint ( container , sibling _name , sibling _name2 ) {
var node ;
// Expand to block of similar type
if ( ! format [ 0 ] . wrapper )
node = dom . getParent ( container , format [ 0 ] . block ) ;
// Expand to first wrappable block element or any block element
if ( ! node )
node = dom . getParent ( container . nodeType == 3 ? container . parentNode : container , isBlock ) ;
// Exclude inner lists from wrapping
if ( node && format [ 0 ] . wrapper )
node = getParents ( node , 'ul,ol' ) . reverse ( ) [ 0 ] || node ;
// Didn't find a block element look for first/last wrappable element
if ( ! node ) {
node = container ;
while ( node [ sibling _name ] && ! isBlock ( node [ sibling _name ] ) ) {
node = node [ sibling _name ] ;
// Break on BR but include it will be removed later on
// we can't remove it now since we need to check if it can be wrapped
if ( isEq ( node , 'br' ) )
break ;
}
}
return node || container ;
} ;
2012-03-21 04:47:31 +01:00
// Expand to closest contentEditable element
startContainer = findParentContentEditable ( startContainer ) ;
endContainer = findParentContentEditable ( endContainer ) ;
// Exclude bookmark nodes if possible
if ( isBookmarkNode ( startContainer . parentNode ) || isBookmarkNode ( startContainer ) ) {
startContainer = isBookmarkNode ( startContainer ) ? startContainer : startContainer . parentNode ;
2010-07-02 01:48:07 +02:00
startContainer = startContainer . nextSibling || startContainer ;
2012-03-21 04:47:31 +01:00
if ( startContainer . nodeType == 3 )
startOffset = 0 ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( isBookmarkNode ( endContainer . parentNode ) || isBookmarkNode ( endContainer ) ) {
endContainer = isBookmarkNode ( endContainer ) ? endContainer : endContainer . parentNode ;
2010-07-02 01:48:07 +02:00
endContainer = endContainer . previousSibling || endContainer ;
2012-03-21 04:47:31 +01:00
if ( endContainer . nodeType == 3 )
endOffset = endContainer . length ;
}
if ( format [ 0 ] . inline ) {
if ( rng . collapsed ) {
// Expand left to closest word boundery
endPoint = findWordEndPoint ( startContainer , startOffset , true ) ;
if ( endPoint ) {
startContainer = endPoint . container ;
startOffset = endPoint . offset ;
}
// Expand right to closest word boundery
endPoint = findWordEndPoint ( endContainer , endOffset ) ;
if ( endPoint ) {
endContainer = endPoint . container ;
endOffset = endPoint . offset ;
}
}
// Avoid applying formatting to a trailing space.
leaf = findLeaf ( endContainer , endOffset ) ;
if ( leaf . node ) {
while ( leaf . node && leaf . offset === 0 && leaf . node . previousSibling )
leaf = findLeaf ( leaf . node . previousSibling ) ;
if ( leaf . node && leaf . offset > 0 && leaf . node . nodeType === 3 &&
leaf . node . nodeValue . charAt ( leaf . offset - 1 ) === ' ' ) {
if ( leaf . offset > 1 ) {
endContainer = leaf . node ;
endContainer . splitText ( leaf . offset - 1 ) ;
}
}
}
}
2010-07-02 01:48:07 +02:00
// Move start/end point up the tree if the leaves are sharp and if we are in different containers
// Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
// This will reduce the number of wrapper elements that needs to be created
// Move start point up the tree
if ( format [ 0 ] . inline || format [ 0 ] . block _expand ) {
2012-03-21 04:47:31 +01:00
if ( ! format [ 0 ] . inline || ( startContainer . nodeType != 3 || startOffset === 0 ) ) {
startContainer = findParentContainer ( true ) ;
}
if ( ! format [ 0 ] . inline || ( endContainer . nodeType != 3 || endOffset === endContainer . nodeValue . length ) ) {
endContainer = findParentContainer ( ) ;
}
2010-07-02 01:48:07 +02:00
}
// Expand start/end container to matching selector
if ( format [ 0 ] . selector && format [ 0 ] . expand !== FALSE && ! format [ 0 ] . inline ) {
// Find new startContainer/endContainer if there is better one
startContainer = findSelectorEndPoint ( startContainer , 'previousSibling' ) ;
endContainer = findSelectorEndPoint ( endContainer , 'nextSibling' ) ;
}
// Expand start/end container to matching block element or text node
if ( format [ 0 ] . block || format [ 0 ] . selector ) {
// Find new startContainer/endContainer if there is better one
startContainer = findBlockEndPoint ( startContainer , 'previousSibling' ) ;
endContainer = findBlockEndPoint ( endContainer , 'nextSibling' ) ;
// Non block element then try to expand up the leaf
if ( format [ 0 ] . block ) {
if ( ! isBlock ( startContainer ) )
2012-03-21 04:47:31 +01:00
startContainer = findParentContainer ( true ) ;
2010-07-02 01:48:07 +02:00
if ( ! isBlock ( endContainer ) )
2012-03-21 04:47:31 +01:00
endContainer = findParentContainer ( ) ;
2010-07-02 01:48:07 +02:00
}
}
// Setup index for startContainer
if ( startContainer . nodeType == 1 ) {
startOffset = nodeIndex ( startContainer ) ;
startContainer = startContainer . parentNode ;
}
// Setup index for endContainer
if ( endContainer . nodeType == 1 ) {
endOffset = nodeIndex ( endContainer ) + 1 ;
endContainer = endContainer . parentNode ;
}
// Return new range like object
return {
startContainer : startContainer ,
startOffset : startOffset ,
endContainer : endContainer ,
endOffset : endOffset
} ;
}
function removeFormat ( format , vars , node , compare _node ) {
var i , attrs , stylesModified ;
// Check if node matches format
if ( ! matchName ( node , format ) )
return FALSE ;
// Should we compare with format attribs and styles
if ( format . remove != 'all' ) {
// Remove styles
each ( format . styles , function ( value , name ) {
value = replaceVars ( value , vars ) ;
// Indexed array
if ( typeof ( name ) === 'number' ) {
name = value ;
compare _node = 0 ;
}
if ( ! compare _node || isEq ( getStyle ( compare _node , name ) , value ) )
dom . setStyle ( node , name , '' ) ;
stylesModified = 1 ;
} ) ;
// Remove style attribute if it's empty
if ( stylesModified && dom . getAttrib ( node , 'style' ) == '' ) {
node . removeAttribute ( 'style' ) ;
2012-03-21 04:47:31 +01:00
node . removeAttribute ( 'data-mce-style' ) ;
2010-07-02 01:48:07 +02:00
}
// Remove attributes
each ( format . attributes , function ( value , name ) {
var valueOut ;
value = replaceVars ( value , vars ) ;
// Indexed array
if ( typeof ( name ) === 'number' ) {
name = value ;
compare _node = 0 ;
}
if ( ! compare _node || isEq ( dom . getAttrib ( compare _node , name ) , value ) ) {
// Keep internal classes
if ( name == 'class' ) {
value = dom . getAttrib ( node , name ) ;
if ( value ) {
// Build new class value where everything is removed except the internal prefixed classes
valueOut = '' ;
each ( value . split ( /\s+/ ) , function ( cls ) {
if ( /mce\w+/ . test ( cls ) )
valueOut += ( valueOut ? ' ' : '' ) + cls ;
} ) ;
// We got some internal classes left
if ( valueOut ) {
dom . setAttrib ( node , name , valueOut ) ;
return ;
}
}
}
// IE6 has a bug where the attribute doesn't get removed correctly
if ( name == "class" )
node . removeAttribute ( 'className' ) ;
// Remove mce prefixed attributes
if ( MCE _ATTR _RE . test ( name ) )
2012-03-21 04:47:31 +01:00
node . removeAttribute ( 'data-mce-' + name ) ;
2010-07-02 01:48:07 +02:00
node . removeAttribute ( name ) ;
}
} ) ;
// Remove classes
each ( format . classes , function ( value ) {
value = replaceVars ( value , vars ) ;
if ( ! compare _node || dom . hasClass ( compare _node , value ) )
dom . removeClass ( node , value ) ;
} ) ;
// Check for non internal attributes
attrs = dom . getAttribs ( node ) ;
for ( i = 0 ; i < attrs . length ; i ++ ) {
if ( attrs [ i ] . nodeName . indexOf ( '_' ) !== 0 )
return FALSE ;
}
}
// Remove the inline child if it's empty for example <b> or <span>
if ( format . remove != 'none' ) {
removeNode ( node , format ) ;
return TRUE ;
}
} ;
function removeNode ( node , format ) {
var parentNode = node . parentNode , rootBlockElm ;
2012-05-16 02:18:46 +02:00
function find ( node , next , inc ) {
node = getNonWhiteSpaceSibling ( node , next , inc ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
return ! node || ( node . nodeName == 'BR' || isBlock ( node ) ) ;
} ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
if ( format . block ) {
if ( ! forcedRootBlock ) {
2010-07-02 01:48:07 +02:00
// Append BR elements if needed before we remove the block
if ( isBlock ( node ) && ! isBlock ( parentNode ) ) {
if ( ! find ( node , FALSE ) && ! find ( node . firstChild , TRUE , 1 ) )
node . insertBefore ( dom . create ( 'br' ) , node . firstChild ) ;
if ( ! find ( node , TRUE ) && ! find ( node . lastChild , FALSE , 1 ) )
node . appendChild ( dom . create ( 'br' ) ) ;
}
} else {
// Wrap the block in a forcedRootBlock if we are at the root of document
if ( parentNode == dom . getRoot ( ) ) {
if ( ! format . list _block || ! isEq ( node , format . list _block ) ) {
each ( tinymce . grep ( node . childNodes ) , function ( node ) {
if ( isValid ( forcedRootBlock , node . nodeName . toLowerCase ( ) ) ) {
if ( ! rootBlockElm )
rootBlockElm = wrap ( node , forcedRootBlock ) ;
else
rootBlockElm . appendChild ( node ) ;
} else
rootBlockElm = 0 ;
} ) ;
}
}
}
}
// Never remove nodes that isn't the specified inline element if a selector is specified too
if ( format . selector && format . inline && ! isEq ( format . inline , node ) )
return ;
dom . remove ( node , 1 ) ;
} ;
function getNonWhiteSpaceSibling ( node , next , inc ) {
if ( node ) {
next = next ? 'nextSibling' : 'previousSibling' ;
for ( node = inc ? node : node [ next ] ; node ; node = node [ next ] ) {
if ( node . nodeType == 1 || ! isWhiteSpaceNode ( node ) )
return node ;
}
}
} ;
function isBookmarkNode ( node ) {
2012-03-21 04:47:31 +01:00
return node && node . nodeType == 1 && node . getAttribute ( 'data-mce-type' ) == 'bookmark' ;
2010-07-02 01:48:07 +02:00
} ;
function mergeSiblings ( prev , next ) {
var marker , sibling , tmpSibling ;
function compareElements ( node1 , node2 ) {
// Not the same name
if ( node1 . nodeName != node2 . nodeName )
return FALSE ;
function getAttribs ( node ) {
var attribs = { } ;
each ( dom . getAttribs ( node ) , function ( attr ) {
var name = attr . nodeName . toLowerCase ( ) ;
// Don't compare internal attributes or style
if ( name . indexOf ( '_' ) !== 0 && name !== 'style' )
attribs [ name ] = dom . getAttrib ( node , name ) ;
} ) ;
return attribs ;
} ;
function compareObjects ( obj1 , obj2 ) {
var value , name ;
for ( name in obj1 ) {
// Obj1 has item obj2 doesn't have
if ( obj1 . hasOwnProperty ( name ) ) {
value = obj2 [ name ] ;
// Obj2 doesn't have obj1 item
2012-05-16 02:18:46 +02:00
if ( value === undef )
2010-07-02 01:48:07 +02:00
return FALSE ;
// Obj2 item has a different value
if ( obj1 [ name ] != value )
return FALSE ;
// Delete similar value
delete obj2 [ name ] ;
}
}
2012-03-21 04:47:31 +01:00
// Check if obj 2 has something obj 1 doesn't have
for ( name in obj2 ) {
// Obj2 has item obj1 doesn't have
if ( obj2 . hasOwnProperty ( name ) )
return FALSE ;
}
return TRUE ;
} ;
// Attribs are not the same
if ( ! compareObjects ( getAttribs ( node1 ) , getAttribs ( node2 ) ) )
return FALSE ;
// Styles are not the same
if ( ! compareObjects ( dom . parseStyle ( dom . getAttrib ( node1 , 'style' ) ) , dom . parseStyle ( dom . getAttrib ( node2 , 'style' ) ) ) )
return FALSE ;
return TRUE ;
} ;
2012-05-16 02:18:46 +02:00
function findElementSibling ( node , sibling _name ) {
for ( sibling = node ; sibling ; sibling = sibling [ sibling _name ] ) {
if ( sibling . nodeType == 3 && sibling . nodeValue . length !== 0 )
return node ;
2012-03-21 04:47:31 +01:00
2012-05-16 02:18:46 +02:00
if ( sibling . nodeType == 1 && ! isBookmarkNode ( sibling ) )
return sibling ;
}
2012-03-21 04:47:31 +01:00
2012-05-16 02:18:46 +02:00
return node ;
} ;
2012-03-21 04:47:31 +01:00
2012-05-16 02:18:46 +02:00
// Check if next/prev exists and that they are elements
if ( prev && next ) {
2012-03-21 04:47:31 +01:00
// If previous sibling is empty then jump over it
prev = findElementSibling ( prev , 'previousSibling' ) ;
next = findElementSibling ( next , 'nextSibling' ) ;
// Compare next and previous nodes
if ( compareElements ( prev , next ) ) {
// Append nodes between
for ( sibling = prev . nextSibling ; sibling && sibling != next ; ) {
tmpSibling = sibling ;
sibling = sibling . nextSibling ;
prev . appendChild ( tmpSibling ) ;
}
// Remove next node
dom . remove ( next ) ;
// Move children into prev node
each ( tinymce . grep ( next . childNodes ) , function ( node ) {
prev . appendChild ( node ) ;
} ) ;
return prev ;
}
}
return next ;
} ;
function isTextBlock ( name ) {
return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/ . test ( name ) ;
} ;
function getContainer ( rng , start ) {
var container , offset , lastIdx , walker ;
container = rng [ start ? 'startContainer' : 'endContainer' ] ;
offset = rng [ start ? 'startOffset' : 'endOffset' ] ;
if ( container . nodeType == 1 ) {
lastIdx = container . childNodes . length - 1 ;
if ( ! start && offset )
offset -- ;
container = container . childNodes [ offset > lastIdx ? lastIdx : offset ] ;
}
// If start text node is excluded then walk to the next node
if ( container . nodeType === 3 && start && offset >= container . nodeValue . length ) {
container = new TreeWalker ( container , ed . getBody ( ) ) . next ( ) || container ;
}
// If end text node is excluded then walk to the previous node
2012-05-16 02:18:46 +02:00
if ( container . nodeType === 3 && ! start && offset === 0 ) {
2012-03-21 04:47:31 +01:00
container = new TreeWalker ( container , ed . getBody ( ) ) . prev ( ) || container ;
}
return container ;
} ;
function performCaretAction ( type , name , vars ) {
var caretContainerId = '_mce_caret' , debug = ed . settings . caret _debug ;
// Creates a caret container bogus element
function createCaretContainer ( fill ) {
var caretContainer = dom . create ( 'span' , { id : caretContainerId , 'data-mce-bogus' : true , style : debug ? 'color:red' : '' } ) ;
if ( fill ) {
caretContainer . appendChild ( ed . getDoc ( ) . createTextNode ( INVISIBLE _CHAR ) ) ;
}
return caretContainer ;
} ;
function isCaretContainerEmpty ( node , nodes ) {
while ( node ) {
if ( ( node . nodeType === 3 && node . nodeValue !== INVISIBLE _CHAR ) || node . childNodes . length > 1 ) {
return false ;
}
// Collect nodes
if ( nodes && node . nodeType === 1 ) {
nodes . push ( node ) ;
}
node = node . firstChild ;
}
return true ;
} ;
// Returns any parent caret container element
function getParentCaretContainer ( node ) {
while ( node ) {
if ( node . id === caretContainerId ) {
return node ;
}
node = node . parentNode ;
}
} ;
// Finds the first text node in the specified node
function findFirstTextNode ( node ) {
var walker ;
if ( node ) {
walker = new TreeWalker ( node , node ) ;
for ( node = walker . current ( ) ; node ; node = walker . next ( ) ) {
if ( node . nodeType === 3 ) {
return node ;
}
}
}
} ;
// Removes the caret container for the specified node or all on the current document
function removeCaretContainer ( node , move _caret ) {
var child , rng ;
if ( ! node ) {
node = getParentCaretContainer ( selection . getStart ( ) ) ;
if ( ! node ) {
while ( node = dom . get ( caretContainerId ) ) {
removeCaretContainer ( node , false ) ;
}
}
} else {
rng = selection . getRng ( true ) ;
if ( isCaretContainerEmpty ( node ) ) {
if ( move _caret !== false ) {
rng . setStartBefore ( node ) ;
rng . setEndBefore ( node ) ;
}
dom . remove ( node ) ;
} else {
child = findFirstTextNode ( node ) ;
if ( child . nodeValue . charAt ( 0 ) === INVISIBLE _CHAR ) {
child = child . deleteData ( 0 , 1 ) ;
}
dom . remove ( node , 1 ) ;
}
selection . setRng ( rng ) ;
}
} ;
// Applies formatting to the caret postion
function applyCaretFormat ( ) {
var rng , caretContainer , textNode , offset , bookmark , container , text ;
rng = selection . getRng ( true ) ;
offset = rng . startOffset ;
container = rng . startContainer ;
text = container . nodeValue ;
caretContainer = getParentCaretContainer ( selection . getStart ( ) ) ;
if ( caretContainer ) {
textNode = findFirstTextNode ( caretContainer ) ;
}
// Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character
if ( text && offset > 0 && offset < text . length && /\w/ . test ( text . charAt ( offset ) ) && /\w/ . test ( text . charAt ( offset - 1 ) ) ) {
// Get bookmark of caret position
bookmark = selection . getBookmark ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Collapse bookmark range (WebKit)
rng . collapse ( true ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Expand the range to the closest word and split it at those points
rng = expandRng ( rng , get ( name ) ) ;
rng = rangeUtils . split ( rng ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Apply the format to the range
apply ( name , vars , rng ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Move selection back to caret position
selection . moveToBookmark ( bookmark ) ;
} else {
if ( ! caretContainer || textNode . nodeValue !== INVISIBLE _CHAR ) {
caretContainer = createCaretContainer ( true ) ;
textNode = caretContainer . firstChild ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
rng . insertNode ( caretContainer ) ;
offset = 1 ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
apply ( name , vars , caretContainer ) ;
} else {
apply ( name , vars , caretContainer ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
// Move selection to text node
selection . setCursorLocation ( textNode , offset ) ;
}
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function removeCaretFormat ( ) {
var rng = selection . getRng ( true ) , container , offset , bookmark ,
hasContentAfter , node , formatNode , parents = [ ] , i , caretContainer ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
container = rng . startContainer ;
offset = rng . startOffset ;
node = container ;
if ( container . nodeType == 3 ) {
if ( offset != container . nodeValue . length || container . nodeValue === INVISIBLE _CHAR ) {
hasContentAfter = true ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
node = node . parentNode ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
while ( node ) {
if ( matchNode ( node , name , vars ) ) {
formatNode = node ;
break ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
if ( node . nextSibling ) {
hasContentAfter = true ;
}
parents . push ( node ) ;
node = node . parentNode ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
// Node doesn't have the specified format
if ( ! formatNode ) {
return ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Is there contents after the caret then remove the format on the element
if ( hasContentAfter ) {
// Get bookmark of caret position
bookmark = selection . getBookmark ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Collapse bookmark range (WebKit)
rng . collapse ( true ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Expand the range to the closest word and split it at those points
rng = expandRng ( rng , get ( name ) , true ) ;
rng = rangeUtils . split ( rng ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Remove the format from the range
remove ( name , vars , rng ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Move selection back to caret position
selection . moveToBookmark ( bookmark ) ;
} else {
caretContainer = createCaretContainer ( ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
node = caretContainer ;
for ( i = parents . length - 1 ; i >= 0 ; i -- ) {
node . appendChild ( dom . clone ( parents [ i ] , false ) ) ;
node = node . firstChild ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Insert invisible character into inner most format element
node . appendChild ( dom . doc . createTextNode ( INVISIBLE _CHAR ) ) ;
node = node . firstChild ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Insert caret container after the formated node
dom . insertAfter ( caretContainer , formatNode ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Move selection to text node
selection . setCursorLocation ( node , 1 ) ;
}
2010-07-02 01:48:07 +02:00
} ;
2012-03-21 04:47:31 +01:00
// Only bind the caret events once
if ( ! self . _hasCaretEvents ) {
// Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
ed . onBeforeGetContent . addToTop ( function ( ) {
var nodes = [ ] , i ;
if ( isCaretContainerEmpty ( getParentCaretContainer ( selection . getStart ( ) ) , nodes ) ) {
// Mark children
i = nodes . length ;
while ( i -- ) {
dom . setAttrib ( nodes [ i ] , 'data-mce-bogus' , '1' ) ;
}
}
} ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Remove caret container on mouse up and on key up
tinymce . each ( 'onMouseUp onKeyUp' . split ( ' ' ) , function ( name ) {
ed [ name ] . addToTop ( function ( ) {
removeCaretContainer ( ) ;
} ) ;
2010-07-02 01:48:07 +02:00
} ) ;
2012-03-21 04:47:31 +01:00
// Remove caret container on keydown and it's a backspace, enter or left/right arrow keys
ed . onKeyDown . addToTop ( function ( ed , e ) {
var keyCode = e . keyCode ;
if ( keyCode == 8 || keyCode == 37 || keyCode == 39 ) {
removeCaretContainer ( getParentCaretContainer ( selection . getStart ( ) ) ) ;
}
2010-07-02 01:48:07 +02:00
} ) ;
2012-05-16 02:18:46 +02:00
// Remove bogus state if they got filled by contents using editor.selection.setContent
selection . onSetContent . add ( function ( ) {
dom . getParent ( selection . getStart ( ) , function ( node ) {
if ( node . id !== caretContainerId && dom . getAttrib ( node , 'data-mce-bogus' ) && ! dom . isEmpty ( node ) ) {
dom . setAttrib ( node , 'data-mce-bogus' , null ) ;
}
} ) ;
} ) ;
2012-03-21 04:47:31 +01:00
self . _hasCaretEvents = true ;
}
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Do apply or remove caret format
if ( type == "apply" ) {
applyCaretFormat ( ) ;
} else {
removeCaretFormat ( ) ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
} ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
function moveStart ( rng ) {
var container = rng . startContainer ,
2012-05-16 02:18:46 +02:00
offset = rng . startOffset , isAtEndOfText ,
2012-03-21 04:47:31 +01:00
walker , node , nodes , tmpNode ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
// Convert text node into index if possible
if ( container . nodeType == 3 && offset >= container . nodeValue . length ) {
// Get the parent container location and walk from there
2012-05-16 02:18:46 +02:00
offset = nodeIndex ( container ) ;
2012-03-21 04:47:31 +01:00
container = container . parentNode ;
2012-05-16 02:18:46 +02:00
isAtEndOfText = true ;
2010-07-02 01:48:07 +02:00
}
2012-03-21 04:47:31 +01:00
// Move startContainer/startOffset in to a suitable node
if ( container . nodeType == 1 ) {
nodes = container . childNodes ;
container = nodes [ Math . min ( offset , nodes . length - 1 ) ] ;
walker = new TreeWalker ( container , dom . getParent ( container , dom . isBlock ) ) ;
// If offset is at end of the parent node walk to the next one
2012-05-16 02:18:46 +02:00
if ( offset > nodes . length - 1 || isAtEndOfText )
2012-03-21 04:47:31 +01:00
walker . next ( ) ;
for ( node = walker . current ( ) ; node ; node = walker . next ( ) ) {
if ( node . nodeType == 3 && ! isWhiteSpaceNode ( node ) ) {
// IE has a "neat" feature where it moves the start node into the closest element
// we can avoid this by inserting an element before it and then remove it after we set the selection
tmpNode = dom . create ( 'a' , null , INVISIBLE _CHAR ) ;
node . parentNode . insertBefore ( tmpNode , node ) ;
// Set selection and remove tmpNode
rng . setStart ( node , 0 ) ;
selection . setRng ( rng ) ;
dom . remove ( tmpNode ) ;
2010-07-02 01:48:07 +02:00
2012-03-21 04:47:31 +01:00
return ;
2010-07-02 01:48:07 +02:00
}
}
}
} ;
} ;
} ) ( tinymce ) ;
tinymce . onAddEditor . add ( function ( tinymce , ed ) {
var filters , fontSizes , dom , settings = ed . settings ;
2012-05-16 02:18:46 +02:00
function replaceWithSpan ( node , styles ) {
tinymce . each ( styles , function ( value , name ) {
if ( value )
dom . setStyle ( node , name , value ) ;
} ) ;
2010-07-02 01:48:07 +02:00
2012-05-16 02:18:46 +02:00
dom . rename ( node , 'span' ) ;
} ;
function convert ( editor , params ) {
dom = editor . dom ;
if ( settings . convert _fonts _to _spans ) {
tinymce . each ( dom . select ( 'font,u,strike' , params . node ) , function ( node ) {
filters [ node . nodeName . toLowerCase ( ) ] ( ed . dom , node ) ;
2012-03-21 04:47:31 +01:00
} ) ;
2012-05-16 02:18:46 +02:00
}
} ;
2012-03-21 04:47:31 +01:00
2012-05-16 02:18:46 +02:00
if ( settings . inline _styles ) {
fontSizes = tinymce . explode ( settings . font _size _legacy _values ) ;
2010-07-02 01:48:07 +02:00
filters = {
font : function ( dom , node ) {
replaceWithSpan ( node , {
backgroundColor : node . style . backgroundColor ,
color : node . color ,
fontFamily : node . face ,
2012-05-16 02:18:46 +02:00
fontSize : fontSizes [ parseInt ( node . size , 10 ) - 1 ]
2010-07-02 01:48:07 +02:00
} ) ;
} ,
u : function ( dom , node ) {
replaceWithSpan ( node , {
textDecoration : 'underline'
} ) ;
} ,
strike : function ( dom , node ) {
replaceWithSpan ( node , {
textDecoration : 'line-through'
} ) ;
}
} ;
ed . onPreProcess . add ( convert ) ;
2012-03-21 04:47:31 +01:00
ed . onSetContent . add ( convert ) ;
2010-07-02 01:48:07 +02:00
ed . onInit . add ( function ( ) {
ed . selection . onSetContent . add ( convert ) ;
} ) ;
}
} ) ;
2012-03-21 04:47:31 +01:00
( function ( tinymce ) {
var TreeWalker = tinymce . dom . TreeWalker ;
tinymce . EnterKey = function ( editor ) {
var dom = editor . dom , selection = editor . selection , settings = editor . settings , undoManager = editor . undoManager ;
function handleEnterKey ( evt ) {
2012-05-16 02:18:46 +02:00
var rng = selection . getRng ( true ) , tmpRng , editableRoot , container , offset , parentBlock , documentMode ,
newBlock , fragment , containerBlock , parentBlockName , containerBlockName , newBlockName , isAfterLastNodeInContainer ;
2012-03-21 04:47:31 +01:00
// Returns true if the block can be split into two blocks or not
function canSplitBlock ( node ) {
2012-05-16 02:18:46 +02:00
return node &&
dom . isBlock ( node ) &&
! /^(TD|TH|CAPTION)$/ . test ( node . nodeName ) &&
! /^(fixed|absolute)/i . test ( node . style . position ) &&
dom . getContentEditable ( node ) !== "true" ;
2012-03-21 04:47:31 +01:00
} ;
// Moves the caret to a suitable position within the root for example in the first non pure whitespace text node or before an image
function moveToCaretPosition ( root ) {
2012-05-16 02:18:46 +02:00
var walker , node , rng , y , viewPort , lastNode = root , tempElm ;
2012-03-21 04:47:31 +01:00
rng = dom . createRng ( ) ;
if ( root . hasChildNodes ( ) ) {
walker = new TreeWalker ( root , root ) ;
while ( node = walker . current ( ) ) {
if ( node . nodeType == 3 ) {
rng . setStart ( node , 0 ) ;
rng . setEnd ( node , 0 ) ;
break ;
}
if ( /^(BR|IMG)$/ . test ( node . nodeName ) ) {
rng . setStartBefore ( node ) ;
rng . setEndBefore ( node ) ;
break ;
}
lastNode = node ;
node = walker . next ( ) ;
}
if ( ! node ) {
rng . setStart ( lastNode , 0 ) ;
rng . setEnd ( lastNode , 0 ) ;
}
} else {
if ( root . nodeName == 'BR' ) {
2012-05-16 02:18:46 +02:00
if ( root . nextSibling && dom . isBlock ( root . nextSibling ) ) {
// Trick on older IE versions to render the caret before the BR between two lists
if ( ! documentMode || documentMode < 9 ) {
tempElm = dom . create ( 'br' ) ;
root . parentNode . insertBefore ( tempElm , root ) ;
}
rng . setStartBefore ( root ) ;
rng . setEndBefore ( root ) ;
} else {
rng . setStartAfter ( root ) ;
rng . setEndAfter ( root ) ;
}
2012-03-21 04:47:31 +01:00
} else {
rng . setStart ( root , 0 ) ;
rng . setEnd ( root , 0 ) ;
}
}
selection . setRng ( rng ) ;
2012-05-16 02:18:46 +02:00
// Remove tempElm created for old IE:s
dom . remove ( tempElm ) ;
2012-03-21 04:47:31 +01:00
viewPort = dom . getViewPort ( editor . getWin ( ) ) ;
// scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs
y = dom . getPos ( root ) . y ;
if ( y < viewPort . y || y + 25 > viewPort . y + viewPort . h ) {
editor . getWin ( ) . scrollTo ( 0 , y < viewPort . y ? y : y - viewPort . h + 25 ) ; // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks
}
} ;
// Creates a new block element by cloning the current one or creating a new one if the name is specified
// This function will also copy any text formatting from the parent block and add it to the new one
function createNewBlock ( name ) {
var node = container , block , clonedNode , caretNode ;
2012-05-16 02:18:46 +02:00
block = name || parentBlockName == "TABLE" ? dom . create ( name || newBlockName ) : parentBlock . cloneNode ( false ) ;
2012-03-21 04:47:31 +01:00
caretNode = block ;
// Clone any parent styles
2012-05-16 02:18:46 +02:00
if ( settings . keep _styles !== false ) {
do {
if ( /^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/ . test ( node . nodeName ) ) {
clonedNode = node . cloneNode ( false ) ;
dom . setAttrib ( clonedNode , 'id' , '' ) ; // Remove ID since it needs to be document unique
if ( block . hasChildNodes ( ) ) {
clonedNode . appendChild ( block . firstChild ) ;
block . appendChild ( clonedNode ) ;
} else {
caretNode = clonedNode ;
block . appendChild ( clonedNode ) ;
}
2012-03-21 04:47:31 +01:00
}
2012-05-16 02:18:46 +02:00
} while ( node = node . parentNode ) ;
}
2012-03-21 04:47:31 +01:00
// BR is needed in empty blocks on non IE browsers
if ( ! tinymce . isIE ) {
caretNode . innerHTML = '<br>' ;
}
return block ;
} ;
// Returns true/false if the caret is at the start/end of the parent block element
function isCaretAtStartOrEndOfBlock ( start ) {
2012-05-16 02:18:46 +02:00
var walker , node , name ;
2012-03-21 04:47:31 +01:00
// Caret is in the middle of a text node like "a|b"
if ( container . nodeType == 3 && ( start ? offset > 0 : offset < container . nodeValue . length ) ) {
return false ;
}
2012-05-16 02:18:46 +02:00
// If after the last element in block node edge case for #5091
if ( container . parentNode == parentBlock && isAfterLastNodeInContainer && ! start ) {
return true ;
}
// Caret can be before/after a table
if ( container . nodeName === "TABLE" || ( container . previousSibling && container . previousSibling . nodeName == "TABLE" ) ) {
return ( isAfterLastNodeInContainer && ! start ) || ( ! isAfterLastNodeInContainer && start ) ;
}
2012-03-21 04:47:31 +01:00
// Walk the DOM and look for text nodes or non empty elements
walker = new TreeWalker ( container , parentBlock ) ;
while ( node = ( start ? walker . prev ( ) : walker . next ( ) ) ) {
if ( node . nodeType === 1 ) {
// Ignore bogus elements
if ( node . getAttribute ( 'data-mce-bogus' ) ) {
continue ;
}
// Keep empty elements like <img />
name = node . nodeName . toLowerCase ( ) ;
if ( name === 'IMG' ) {
return false ;
}
} else if ( node . nodeType === 3 && ! /^[ \t\r\n]*$/ . test ( node . nodeValue ) ) {
return false ;
}
}
return true ;
} ;
// Wraps any text nodes or inline elements in the specified forced root block name
function wrapSelfAndSiblingsInDefaultBlock ( container , offset ) {
2012-05-16 02:18:46 +02:00
var newBlock , parentBlock , startNode , node , next , blockName = newBlockName || 'P' ;
2012-03-21 04:47:31 +01:00
// Not in a block element or in a table cell or caption
parentBlock = dom . getParent ( container , dom . isBlock ) ;
2012-05-16 02:18:46 +02:00
if ( ! parentBlock || ! canSplitBlock ( parentBlock ) ) {
parentBlock = parentBlock || editableRoot ;
2012-03-21 04:47:31 +01:00
if ( ! parentBlock . hasChildNodes ( ) ) {
2012-05-16 02:18:46 +02:00
newBlock = dom . create ( blockName ) ;
2012-03-21 04:47:31 +01:00
parentBlock . appendChild ( newBlock ) ;
rng . setStart ( newBlock , 0 ) ;
rng . setEnd ( newBlock , 0 ) ;
return newBlock ;
}
// Find parent that is the first child of parentBlock
node = container ;
while ( node . parentNode != parentBlock ) {
node = node . parentNode ;
}
// Loop left to find start node start wrapping at
while ( node && ! dom . isBlock ( node ) ) {
startNode = node ;
node = node . previousSibling ;
}
if ( startNode ) {
2012-05-16 02:18:46 +02:00
newBlock = dom . create ( blockName ) ;
2012-03-21 04:47:31 +01:00
startNode . parentNode . insertBefore ( newBlock , startNode ) ;
// Start wrapping until we hit a block
node = startNode ;
while ( node && ! dom . isBlock ( node ) ) {
next = node . nextSibling ;
newBlock . appendChild ( node ) ;
node = next ;
}
// Restore range to it's past location
rng . setStart ( container , offset ) ;
rng . setEnd ( container , offset ) ;
}
}
return container ;
} ;
// Inserts a block or br before/after or in the middle of a split list of the LI is empty
function handleEmptyListItem ( ) {
function isFirstOrLastLi ( first ) {
var node = containerBlock [ first ? 'firstChild' : 'lastChild' ] ;
// Find first/last element since there might be whitespace there
while ( node ) {
if ( node . nodeType == 1 ) {
break ;
}
node = node [ first ? 'nextSibling' : 'previousSibling' ] ;
}
return node === parentBlock ;
} ;
newBlock = newBlockName ? createNewBlock ( newBlockName ) : dom . create ( 'BR' ) ;
if ( isFirstOrLastLi ( true ) && isFirstOrLastLi ( ) ) {
// Is first and last list item then replace the OL/UL with a text block
dom . replace ( newBlock , containerBlock ) ;
} else if ( isFirstOrLastLi ( true ) ) {
// First LI in list then remove LI and add text block before list
containerBlock . parentNode . insertBefore ( newBlock , containerBlock ) ;
} else if ( isFirstOrLastLi ( ) ) {
// Last LI in list then temove LI and add text block after list
dom . insertAfter ( newBlock , containerBlock ) ;
} else {
// Middle LI in list the split the list and insert a text block in the middle
// Extract after fragment and insert it after the current block
tmpRng = rng . cloneRange ( ) ;
tmpRng . setStartAfter ( parentBlock ) ;
tmpRng . setEndAfter ( containerBlock ) ;
fragment = tmpRng . extractContents ( ) ;
dom . insertAfter ( fragment , containerBlock ) ;
dom . insertAfter ( newBlock , containerBlock ) ;
}
dom . remove ( parentBlock ) ;
moveToCaretPosition ( newBlock ) ;
undoManager . add ( ) ;
} ;
// Walks the parent block to the right and look for BR elements
function hasRightSideBr ( ) {
var walker = new TreeWalker ( container , parentBlock ) , node ;
while ( node = walker . current ( ) ) {
if ( node . nodeName == 'BR' ) {
return true ;
}
node = walker . next ( ) ;
}
}
// Inserts a BR element if the forced_root_block option is set to false or empty string
function insertBr ( ) {
2012-05-16 02:18:46 +02:00
var brElm , extraBr ;
2012-03-21 04:47:31 +01:00
if ( container && container . nodeType == 3 && offset >= container . nodeValue . length ) {
// Insert extra BR element at the end block elements
if ( ! tinymce . isIE && ! hasRightSideBr ( ) ) {
brElm = dom . create ( 'br' )
rng . insertNode ( brElm ) ;
rng . setStartAfter ( brElm ) ;
rng . setEndAfter ( brElm ) ;
extraBr = true ;
}
}
brElm = dom . create ( 'br' ) ;
rng . insertNode ( brElm ) ;
// Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it
if ( tinymce . isIE && parentBlockName == 'PRE' && ( ! documentMode || documentMode < 8 ) ) {
brElm . parentNode . insertBefore ( dom . doc . createTextNode ( '\r' ) , brElm ) ;
}
if ( ! extraBr ) {
rng . setStartAfter ( brElm ) ;
rng . setEndAfter ( brElm ) ;
} else {
rng . setStartBefore ( brElm ) ;
rng . setEndBefore ( brElm ) ;
}
selection . setRng ( rng ) ;
undoManager . add ( ) ;
} ;
// Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element
function trimLeadingLineBreaks ( node ) {
do {
if ( node . nodeType === 3 ) {
node . nodeValue = node . nodeValue . replace ( /^[\r\n]+/ , '' ) ;
}
node = node . firstChild ;
} while ( node ) ;
} ;
2012-05-16 02:18:46 +02:00
function getEditableRoot ( node ) {
var root = dom . getRoot ( ) , parent , editableRoot ;
// Get all parents until we hit a non editable parent or the root
parent = node ;
while ( parent !== root && dom . getContentEditable ( parent ) !== "false" ) {
if ( dom . getContentEditable ( parent ) === "true" ) {
editableRoot = parent ;
}
parent = parent . parentNode ;
}
return parent !== root ? editableRoot : root ;
} ;
2012-03-21 04:47:31 +01:00
// Delete any selected contents
if ( ! rng . collapsed ) {
editor . execCommand ( 'Delete' ) ;
return ;
}
// Event is blocked by some other handler for example the lists plugin
if ( evt . isDefaultPrevented ( ) ) {
return ;
}
// Setup range items and newBlockName
container = rng . startContainer ;
offset = rng . startOffset ;
newBlockName = settings . forced _root _block ;
newBlockName = newBlockName ? newBlockName . toUpperCase ( ) : '' ;
2012-05-16 02:18:46 +02:00
documentMode = dom . doc . documentMode ;
2012-03-21 04:47:31 +01:00
// Resolve node index
if ( container . nodeType == 1 && container . hasChildNodes ( ) ) {
2012-05-16 02:18:46 +02:00
isAfterLastNodeInContainer = offset > container . childNodes . length - 1 ;
2012-03-21 04:47:31 +01:00
container = container . childNodes [ Math . min ( offset , container . childNodes . length - 1 ) ] || container ;
offset = 0 ;
}
2012-05-16 02:18:46 +02:00
// Get editable root node normaly the body element but sometimes a div or span
editableRoot = getEditableRoot ( container ) ;
// If there is no editable root then enter is done inside a contentEditable false element
if ( ! editableRoot ) {
return ;
}
2012-03-21 04:47:31 +01:00
undoManager . beforeChange ( ) ;
2012-05-16 02:18:46 +02:00
// If editable root isn't block nor the root of the editor
if ( ! dom . isBlock ( editableRoot ) && editableRoot != dom . getRoot ( ) ) {
if ( ! newBlockName || evt . shiftKey ) {
insertBr ( ) ;
}
return ;
}
2012-03-21 04:47:31 +01:00
// Wrap the current node and it's sibling in a default block if it's needed.
// for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>
2012-05-16 02:18:46 +02:00
// This won't happen if root blocks are disabled or the shiftKey is pressed
if ( ( newBlockName && ! evt . shiftKey ) || ( ! newBlockName && evt . shiftKey ) ) {
container = wrapSelfAndSiblingsInDefaultBlock ( container , offset ) ;
}
2012-03-21 04:47:31 +01:00
// Find parent block and setup empty block paddings
parentBlock = dom . getParent ( container , dom . isBlock ) ;
containerBlock = parentBlock ? dom . getParent ( parentBlock . parentNode , dom . isBlock ) : null ;
// Setup block names
parentBlockName = parentBlock ? parentBlock . nodeName . toUpperCase ( ) : '' ; // IE < 9 & HTML5
containerBlockName = containerBlock ? containerBlock . nodeName . toUpperCase ( ) : '' ; // IE < 9 & HTML5
// Handle enter inside an empty list item
if ( parentBlockName == 'LI' && dom . isEmpty ( parentBlock ) ) {
// Let the list plugin or browser handle nested lists for now
if ( /^(UL|OL|LI)$/ . test ( containerBlock . parentNode . nodeName ) ) {
return false ;
}
handleEmptyListItem ( ) ;
return ;
}
// Don't split PRE tags but insert a BR instead easier when writing code samples etc
if ( parentBlockName == 'PRE' && settings . br _in _pre !== false ) {
if ( ! evt . shiftKey ) {
insertBr ( ) ;
return ;
}
} else {
// If no root block is configured then insert a BR by default or if the shiftKey is pressed
if ( ( ! newBlockName && ! evt . shiftKey && parentBlockName != 'LI' ) || ( newBlockName && evt . shiftKey ) ) {
insertBr ( ) ;
return ;
}
}
// Default block name if it's not configured
newBlockName = newBlockName || 'P' ;
// Insert new block before/after the parent block depending on caret location
if ( isCaretAtStartOrEndOfBlock ( ) ) {
// If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup
if ( /^(H[1-6]|PRE)$/ . test ( parentBlockName ) && containerBlockName != 'HGROUP' ) {
newBlock = createNewBlock ( newBlockName ) ;
} else {
newBlock = createNewBlock ( ) ;
}
// Split the current container block element if enter is pressed inside an empty inner block element
if ( settings . end _container _on _empty _block && canSplitBlock ( containerBlock ) && dom . isEmpty ( parentBlock ) ) {
// Split container block for example a BLOCKQUOTE at the current blockParent location for example a P
newBlock = dom . split ( containerBlock , parentBlock ) ;
} else {
dom . insertAfter ( newBlock , parentBlock ) ;
}
} else if ( isCaretAtStartOrEndOfBlock ( true ) ) {
// Insert new block before
newBlock = parentBlock . parentNode . insertBefore ( createNewBlock ( ) , parentBlock ) ;
} else {
// Extract after fragment and insert it after the current block
tmpRng = rng . cloneRange ( ) ;
tmpRng . setEndAfter ( parentBlock ) ;
fragment = tmpRng . extractContents ( ) ;
trimLeadingLineBreaks ( fragment ) ;
newBlock = fragment . firstChild ;
dom . insertAfter ( fragment , parentBlock ) ;
}
dom . setAttrib ( newBlock , 'id' , '' ) ; // Remove ID since it needs to be document unique
moveToCaretPosition ( newBlock ) ;
undoManager . add ( ) ;
}
editor . onKeyDown . add ( function ( ed , evt ) {
if ( evt . keyCode == 13 ) {
if ( handleEnterKey ( evt ) !== false ) {
evt . preventDefault ( ) ;
}
}
} ) ;
} ;
} ) ( tinymce ) ;
2012-05-16 02:18:46 +02:00