style.js 73 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086
  1. /**
  2. * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or http://ckeditor.com/license
  4. */
  5. 'use strict';
  6. /**
  7. * Block style type.
  8. *
  9. * Read more in the {@link CKEDITOR.style} class documentation.
  10. *
  11. * @readonly
  12. * @property {Number} [=1]
  13. * @member CKEDITOR
  14. */
  15. CKEDITOR.STYLE_BLOCK = 1;
  16. /**
  17. * Inline style type.
  18. *
  19. * Read more in the {@link CKEDITOR.style} class documentation.
  20. *
  21. * @readonly
  22. * @property {Number} [=2]
  23. * @member CKEDITOR
  24. */
  25. CKEDITOR.STYLE_INLINE = 2;
  26. /**
  27. * Object style type.
  28. *
  29. * Read more in the {@link CKEDITOR.style} class documentation.
  30. *
  31. * @readonly
  32. * @property {Number} [=3]
  33. * @member CKEDITOR
  34. */
  35. CKEDITOR.STYLE_OBJECT = 3;
  36. ( function() {
  37. var blockElements = {
  38. address: 1, div: 1, h1: 1, h2: 1, h3: 1, h4: 1, h5: 1, h6: 1, p: 1,
  39. pre: 1, section: 1, header: 1, footer: 1, nav: 1, article: 1, aside: 1, figure: 1,
  40. dialog: 1, hgroup: 1, time: 1, meter: 1, menu: 1, command: 1, keygen: 1, output: 1,
  41. progress: 1, details: 1, datagrid: 1, datalist: 1
  42. },
  43. objectElements = {
  44. a: 1, blockquote: 1, embed: 1, hr: 1, img: 1, li: 1, object: 1, ol: 1, table: 1, td: 1,
  45. tr: 1, th: 1, ul: 1, dl: 1, dt: 1, dd: 1, form: 1, audio: 1, video: 1
  46. };
  47. var semicolonFixRegex = /\s*(?:;\s*|$)/,
  48. varRegex = /#\((.+?)\)/g;
  49. var notBookmark = CKEDITOR.dom.walker.bookmark( 0, 1 ),
  50. nonWhitespaces = CKEDITOR.dom.walker.whitespaces( 1 );
  51. /**
  52. * A class representing a style instance for the specific style definition.
  53. * In this approach, a style is a set of properties, like attributes and styles,
  54. * which can be applied to and removed from a {@link CKEDITOR.dom.selection selection} through
  55. * {@link CKEDITOR.editor editor} methods: {@link CKEDITOR.editor#applyStyle} and {@link CKEDITOR.editor#removeStyle},
  56. * respectively.
  57. *
  58. * Three default style types are available: {@link CKEDITOR#STYLE_BLOCK STYLE_BLOCK}, {@link CKEDITOR#STYLE_INLINE STYLE_INLINE},
  59. * and {@link CKEDITOR#STYLE_OBJECT STYLE_OBJECT}. Based on its type, a style heavily changes its behavior.
  60. * You can read more about style types in the [Style Types section of the Styles guide](#!/guide/dev_styles-section-style-types).
  61. *
  62. * It is possible to define a custom style type by subclassing this class by using the {@link #addCustomHandler} method.
  63. * However, because of great complexity of the styles handling job, it is only possible in very specific cases.
  64. *
  65. * ### Usage
  66. *
  67. * Basic usage:
  68. *
  69. * // Define a block style.
  70. * var style = new CKEDITOR.style( { element: 'h1' } );
  71. *
  72. * // Considering the following selection:
  73. * // <p>Foo</p><p>Bar^</p>
  74. * // Executing:
  75. * editor.applyStyle( style );
  76. * // Will give:
  77. * // <p>Foo</p><h1>Bar^</h1>
  78. * style.checkActive( editor.elementPath(), editor ); // -> true
  79. *
  80. * editor.removeStyle( style );
  81. * // Will give:
  82. * // <p>Foo</p><p>Bar^</p>
  83. *
  84. * style.checkActive( editor.elementPath(), editor ); // -> false
  85. *
  86. * Object style:
  87. *
  88. * // Define an object style.
  89. * var style = new CKEDITOR.style( { element: 'img', attributes: { 'class': 'foo' } } );
  90. *
  91. * // Considering the following selection:
  92. * // <p><img src="bar.png" alt="" />Foo^</p>
  93. * // Executing:
  94. * editor.applyStyle( style );
  95. * // Will not apply the style, because the image is not selected.
  96. * // You can check if a style can be applied on the current selection with:
  97. * style.checkApplicable( editor.elementPath(), editor ); // -> false
  98. *
  99. * // Considering the following selection:
  100. * // <p>[<img src="bar.png" alt="" />]Foo</p>
  101. * // Executing
  102. * editor.applyStyle( style );
  103. * // Will give:
  104. * // <p>[<img src="bar.png" alt="" class="foo" />]Foo</p>
  105. *
  106. * ### API changes introduced in CKEditor 4.4
  107. *
  108. * Before CKEditor 4.4 all style instances had no access at all to the {@link CKEDITOR.editor editor instance}
  109. * within which the style is used. Neither the style constructor, nor style methods were requiring
  110. * passing the editor instance which made styles independent of the editor and hence its settings and state.
  111. * This design decision came from CKEditor 3; it started causing problems and became an unsolvable obstacle for
  112. * the {@link CKEDITOR.style.customHandlers.widget widget style handler} which we introduced in CKEditor 4.4.
  113. *
  114. * There were two possible solutions. Passing an editor instance to the style constructor or to every method.
  115. * The first approach would be clean, however, having in mind the backward compatibility, we did not decide
  116. * to go for it. It would bind the style to one editor instance, making it unusable with other editor instances.
  117. * That could break many implementations reusing styles between editors. Therefore, we decided to take the longer
  118. * but safer path &mdash; the editor instance became an argument for nearly all style methods, however,
  119. * for backward compatibility reasons, all these methods will work without it. Even the newly
  120. * implemented {@link CKEDITOR.style.customHandlers.widget widget style handler}'s methods will not fail,
  121. * although they will also not work by aborting at an early stage.
  122. *
  123. * Therefore, you can safely upgrade to CKEditor 4.4 even if you use style methods without providing
  124. * the editor instance. You must only align your code if your implementation should handle widget styles
  125. * or any other custom style handler. Of course, we recommend doing this in any case to avoid potential
  126. * problems in the future.
  127. *
  128. * @class
  129. * @constructor Creates a style class instance.
  130. * @param styleDefinition
  131. * @param variablesValues
  132. */
  133. CKEDITOR.style = function( styleDefinition, variablesValues ) {
  134. if ( typeof styleDefinition.type == 'string' )
  135. return new CKEDITOR.style.customHandlers[ styleDefinition.type ]( styleDefinition );
  136. // Inline style text as attribute should be converted
  137. // to styles object.
  138. var attrs = styleDefinition.attributes;
  139. if ( attrs && attrs.style ) {
  140. styleDefinition.styles = CKEDITOR.tools.extend( {},
  141. styleDefinition.styles, CKEDITOR.tools.parseCssText( attrs.style ) );
  142. delete attrs.style;
  143. }
  144. if ( variablesValues ) {
  145. styleDefinition = CKEDITOR.tools.clone( styleDefinition );
  146. replaceVariables( styleDefinition.attributes, variablesValues );
  147. replaceVariables( styleDefinition.styles, variablesValues );
  148. }
  149. var element = this.element = styleDefinition.element ?
  150. (
  151. typeof styleDefinition.element == 'string' ?
  152. styleDefinition.element.toLowerCase() : styleDefinition.element
  153. ) : '*';
  154. this.type = styleDefinition.type ||
  155. (
  156. blockElements[ element ] ? CKEDITOR.STYLE_BLOCK :
  157. objectElements[ element ] ? CKEDITOR.STYLE_OBJECT :
  158. CKEDITOR.STYLE_INLINE
  159. );
  160. // If the 'element' property is an object with a set of possible element, it will be applied like an object style: only to existing elements
  161. if ( typeof this.element == 'object' )
  162. this.type = CKEDITOR.STYLE_OBJECT;
  163. this._ = {
  164. definition: styleDefinition
  165. };
  166. };
  167. CKEDITOR.style.prototype = {
  168. /**
  169. * Applies the style on the editor's current selection.
  170. *
  171. * Before the style is applied, the method checks if the {@link #checkApplicable style is applicable}.
  172. *
  173. * **Note:** The recommended way of applying the style is by using the
  174. * {@link CKEDITOR.editor#applyStyle} method, which is a shorthand for this method.
  175. *
  176. * @param {CKEDITOR.editor/CKEDITOR.dom.document} editor The editor instance in which
  177. * the style will be applied.
  178. * A {@link CKEDITOR.dom.document} instance is accepted for backward compatibility
  179. * reasons, although since CKEditor 4.4 this type of argument is deprecated. Read more about
  180. * the signature change in the {@link CKEDITOR.style} documentation.
  181. */
  182. apply: function( editor ) {
  183. // Backward compatibility.
  184. if ( editor instanceof CKEDITOR.dom.document )
  185. return applyStyleOnSelection.call( this, editor.getSelection() );
  186. if ( this.checkApplicable( editor.elementPath(), editor ) ) {
  187. var initialEnterMode = this._.enterMode;
  188. // See comment in removeStyle.
  189. if ( !initialEnterMode )
  190. this._.enterMode = editor.activeEnterMode;
  191. applyStyleOnSelection.call( this, editor.getSelection(), 0, editor );
  192. this._.enterMode = initialEnterMode;
  193. }
  194. },
  195. /**
  196. * Removes the style from the editor's current selection.
  197. *
  198. * Before the style is applied, the method checks if {@link #checkApplicable style could be applied}.
  199. *
  200. * **Note:** The recommended way of removing the style is by using the
  201. * {@link CKEDITOR.editor#removeStyle} method, which is a shorthand for this method.
  202. *
  203. * @param {CKEDITOR.editor/CKEDITOR.dom.document} editor The editor instance in which
  204. * the style will be removed.
  205. * A {@link CKEDITOR.dom.document} instance is accepted for backward compatibility
  206. * reasons, although since CKEditor 4.4 this type of argument is deprecated. Read more about
  207. * the signature change in the {@link CKEDITOR.style} documentation.
  208. */
  209. remove: function( editor ) {
  210. // Backward compatibility.
  211. if ( editor instanceof CKEDITOR.dom.document )
  212. return applyStyleOnSelection.call( this, editor.getSelection(), 1 );
  213. if ( this.checkApplicable( editor.elementPath(), editor ) ) {
  214. var initialEnterMode = this._.enterMode;
  215. // Before CKEditor 4.4 style knew nothing about editor, so in order to provide enterMode
  216. // which should be used developers were forced to hack the style object (see #10190).
  217. // Since CKEditor 4.4 style knows about editor (at least when it's being applied/removed), but we
  218. // use _.enterMode for backward compatibility with those hacks.
  219. // Note: we should not change style's enter mode if it was already set.
  220. if ( !initialEnterMode )
  221. this._.enterMode = editor.activeEnterMode;
  222. applyStyleOnSelection.call( this, editor.getSelection(), 1, editor );
  223. this._.enterMode = initialEnterMode;
  224. }
  225. },
  226. /**
  227. * Applies the style on the provided range. Unlike {@link #apply} this
  228. * method does not take care of setting the selection, however, the range
  229. * is updated to the correct place.
  230. *
  231. * **Note:** If you want to apply the style on the editor selection,
  232. * you probably want to use {@link CKEDITOR.editor#applyStyle}.
  233. *
  234. * @param {CKEDITOR.dom.range} range
  235. * @param {CKEDITOR.editor} editor The editor instance. Required argument since
  236. * CKEditor 4.4. The style system will work without it, but it is highly
  237. * recommended to provide it for integration with all features. Read more about
  238. * the signature change in the {@link CKEDITOR.style} documentation.
  239. */
  240. applyToRange: function( range ) {
  241. this.applyToRange =
  242. this.type == CKEDITOR.STYLE_INLINE ? applyInlineStyle :
  243. this.type == CKEDITOR.STYLE_BLOCK ? applyBlockStyle :
  244. this.type == CKEDITOR.STYLE_OBJECT ? applyObjectStyle :
  245. null;
  246. return this.applyToRange( range );
  247. },
  248. /**
  249. * Removes the style from the provided range. Unlike {@link #remove} this
  250. * method does not take care of setting the selection, however, the range
  251. * is updated to the correct place.
  252. *
  253. * **Note:** If you want to remove the style from the editor selection,
  254. * you probably want to use {@link CKEDITOR.editor#removeStyle}.
  255. *
  256. * @param {CKEDITOR.dom.range} range
  257. * @param {CKEDITOR.editor} editor The editor instance. Required argument since
  258. * CKEditor 4.4. The style system will work without it, but it is highly
  259. * recommended to provide it for integration with all features. Read more about
  260. * the signature change in the {@link CKEDITOR.style} documentation.
  261. */
  262. removeFromRange: function( range ) {
  263. this.removeFromRange =
  264. this.type == CKEDITOR.STYLE_INLINE ? removeInlineStyle :
  265. this.type == CKEDITOR.STYLE_BLOCK ? removeBlockStyle :
  266. this.type == CKEDITOR.STYLE_OBJECT ? removeObjectStyle :
  267. null;
  268. return this.removeFromRange( range );
  269. },
  270. /**
  271. * Applies the style to the element. This method bypasses all checks
  272. * and applies the style attributes directly on the provided element. Use with caution.
  273. *
  274. * See {@link CKEDITOR.editor#applyStyle}.
  275. *
  276. * @param {CKEDITOR.dom.element} element
  277. * @param {CKEDITOR.editor} editor The editor instance. Required argument since
  278. * CKEditor 4.4. The style system will work without it, but it is highly
  279. * recommended to provide it for integration with all features. Read more about
  280. * the signature change in the {@link CKEDITOR.style} documentation.
  281. */
  282. applyToObject: function( element ) {
  283. setupElement( element, this );
  284. },
  285. /**
  286. * Gets the style state inside the elements path.
  287. *
  288. * @param {CKEDITOR.dom.elementPath} elementPath
  289. * @param {CKEDITOR.editor} editor The editor instance. Required argument since
  290. * CKEditor 4.4. The style system will work without it, but it is highly
  291. * recommended to provide it for integration with all features. Read more about
  292. * the signature change in the {@link CKEDITOR.style} documentation.
  293. * @returns {Boolean} `true` if the element is active in the elements path.
  294. */
  295. checkActive: function( elementPath, editor ) {
  296. switch ( this.type ) {
  297. case CKEDITOR.STYLE_BLOCK:
  298. return this.checkElementRemovable( elementPath.block || elementPath.blockLimit, true, editor );
  299. case CKEDITOR.STYLE_OBJECT:
  300. case CKEDITOR.STYLE_INLINE:
  301. var elements = elementPath.elements;
  302. for ( var i = 0, element; i < elements.length; i++ ) {
  303. element = elements[ i ];
  304. if ( this.type == CKEDITOR.STYLE_INLINE && ( element == elementPath.block || element == elementPath.blockLimit ) )
  305. continue;
  306. if ( this.type == CKEDITOR.STYLE_OBJECT ) {
  307. var name = element.getName();
  308. if ( !( typeof this.element == 'string' ? name == this.element : name in this.element ) )
  309. continue;
  310. }
  311. if ( this.checkElementRemovable( element, true, editor ) )
  312. return true;
  313. }
  314. }
  315. return false;
  316. },
  317. /**
  318. * Whether this style can be applied at the specified elements path.
  319. *
  320. * @param {CKEDITOR.dom.elementPath} elementPath The elements path to
  321. * check the style against.
  322. * @param {CKEDITOR.editor} editor The editor instance. Required argument since
  323. * CKEditor 4.4. The style system will work without it, but it is highly
  324. * recommended to provide it for integration with all features. Read more about
  325. * the signature change in the {@link CKEDITOR.style} documentation.
  326. * @param {CKEDITOR.filter} [filter] If defined, the style will be
  327. * checked against this filter as well.
  328. * @returns {Boolean} `true` if this style can be applied at the elements path.
  329. */
  330. checkApplicable: function( elementPath, editor, filter ) {
  331. // Backward compatibility.
  332. if ( editor && editor instanceof CKEDITOR.filter )
  333. filter = editor;
  334. if ( filter && !filter.check( this ) )
  335. return false;
  336. switch ( this.type ) {
  337. case CKEDITOR.STYLE_OBJECT:
  338. return !!elementPath.contains( this.element );
  339. case CKEDITOR.STYLE_BLOCK:
  340. return !!elementPath.blockLimit.getDtd()[ this.element ];
  341. }
  342. return true;
  343. },
  344. /**
  345. * Checks if the element matches the current style definition.
  346. *
  347. * @param {CKEDITOR.dom.element} element
  348. * @param {Boolean} fullMatch
  349. * @param {CKEDITOR.editor} editor The editor instance. Required argument since
  350. * CKEditor 4.4. The style system will work without it, but it is highly
  351. * recommended to provide it for integration with all features. Read more about
  352. * the signature change in the {@link CKEDITOR.style} documentation.
  353. * @returns {Boolean}
  354. */
  355. checkElementMatch: function( element, fullMatch ) {
  356. var def = this._.definition;
  357. if ( !element || !def.ignoreReadonly && element.isReadOnly() )
  358. return false;
  359. var attribs,
  360. name = element.getName();
  361. // If the element name is the same as the style name.
  362. if ( typeof this.element == 'string' ? name == this.element : name in this.element ) {
  363. // If no attributes are defined in the element.
  364. if ( !fullMatch && !element.hasAttributes() )
  365. return true;
  366. attribs = getAttributesForComparison( def );
  367. if ( attribs._length ) {
  368. for ( var attName in attribs ) {
  369. if ( attName == '_length' )
  370. continue;
  371. var elementAttr = element.getAttribute( attName ) || '';
  372. // Special treatment for 'style' attribute is required.
  373. if ( attName == 'style' ? compareCssText( attribs[ attName ], elementAttr ) : attribs[ attName ] == elementAttr ) {
  374. if ( !fullMatch )
  375. return true;
  376. } else if ( fullMatch ) {
  377. return false;
  378. }
  379. }
  380. if ( fullMatch )
  381. return true;
  382. } else {
  383. return true;
  384. }
  385. }
  386. return false;
  387. },
  388. /**
  389. * Checks if an element, or any of its attributes, is removable by the
  390. * current style definition.
  391. *
  392. * @param {CKEDITOR.dom.element} element
  393. * @param {Boolean} fullMatch
  394. * @param {CKEDITOR.editor} editor The editor instance. Required argument since
  395. * CKEditor 4.4. The style system will work without it, but it is highly
  396. * recommended to provide it for integration with all features. Read more about
  397. * the signature change in the {@link CKEDITOR.style} documentation.
  398. * @returns {Boolean}
  399. */
  400. checkElementRemovable: function( element, fullMatch, editor ) {
  401. // Check element matches the style itself.
  402. if ( this.checkElementMatch( element, fullMatch, editor ) )
  403. return true;
  404. // Check if the element matches the style overrides.
  405. var override = getOverrides( this )[ element.getName() ];
  406. if ( override ) {
  407. var attribs, attName;
  408. // If no attributes have been defined, remove the element.
  409. if ( !( attribs = override.attributes ) )
  410. return true;
  411. for ( var i = 0; i < attribs.length; i++ ) {
  412. attName = attribs[ i ][ 0 ];
  413. var actualAttrValue = element.getAttribute( attName );
  414. if ( actualAttrValue ) {
  415. var attValue = attribs[ i ][ 1 ];
  416. // Remove the attribute if:
  417. // - The override definition value is null;
  418. // - The override definition value is a string that
  419. // matches the attribute value exactly.
  420. // - The override definition value is a regex that
  421. // has matches in the attribute value.
  422. if ( attValue === null )
  423. return true;
  424. if ( typeof attValue == 'string' ) {
  425. if ( actualAttrValue == attValue )
  426. return true;
  427. } else if ( attValue.test( actualAttrValue ) ) {
  428. return true;
  429. }
  430. }
  431. }
  432. }
  433. return false;
  434. },
  435. /**
  436. * Builds the preview HTML based on the styles definition.
  437. *
  438. * @param {String} [label] The label used in the style preview.
  439. * @return {String} The HTML of preview.
  440. */
  441. buildPreview: function( label ) {
  442. var styleDefinition = this._.definition,
  443. html = [],
  444. elementName = styleDefinition.element;
  445. // Avoid <bdo> in the preview.
  446. if ( elementName == 'bdo' )
  447. elementName = 'span';
  448. html = [ '<', elementName ];
  449. // Assign all defined attributes.
  450. var attribs = styleDefinition.attributes;
  451. if ( attribs ) {
  452. for ( var att in attribs )
  453. html.push( ' ', att, '="', attribs[ att ], '"' );
  454. }
  455. // Assign the style attribute.
  456. var cssStyle = CKEDITOR.style.getStyleText( styleDefinition );
  457. if ( cssStyle )
  458. html.push( ' style="', cssStyle, '"' );
  459. html.push( '>', ( label || styleDefinition.name ), '</', elementName, '>' );
  460. return html.join( '' );
  461. },
  462. /**
  463. * Returns the style definition.
  464. *
  465. * @since 4.1
  466. * @returns {Object}
  467. */
  468. getDefinition: function() {
  469. return this._.definition;
  470. }
  471. /**
  472. * If defined (for example by {@link CKEDITOR.style#addCustomHandler custom style handler}), it returns
  473. * the {@link CKEDITOR.filter.allowedContentRules allowed content rules} which should be added to the
  474. * {@link CKEDITOR.filter} when enabling this style.
  475. *
  476. * **Note:** This method is not defined in the {@link CKEDITOR.style} class.
  477. *
  478. * @since 4.4
  479. * @method toAllowedContentRules
  480. * @param {CKEDITOR.editor} [editor] The editor instance.
  481. * @returns {CKEDITOR.filter.allowedContentRules} The rules that should represent this style in the {@link CKEDITOR.filter}.
  482. */
  483. };
  484. /**
  485. * Builds the inline style text based on the style definition.
  486. *
  487. * @static
  488. * @param styleDefinition
  489. * @returns {String} Inline style text.
  490. */
  491. CKEDITOR.style.getStyleText = function( styleDefinition ) {
  492. // If we have already computed it, just return it.
  493. var stylesDef = styleDefinition._ST;
  494. if ( stylesDef )
  495. return stylesDef;
  496. stylesDef = styleDefinition.styles;
  497. // Builds the StyleText.
  498. var stylesText = ( styleDefinition.attributes && styleDefinition.attributes.style ) || '',
  499. specialStylesText = '';
  500. if ( stylesText.length )
  501. stylesText = stylesText.replace( semicolonFixRegex, ';' );
  502. for ( var style in stylesDef ) {
  503. var styleVal = stylesDef[ style ],
  504. text = ( style + ':' + styleVal ).replace( semicolonFixRegex, ';' );
  505. // Some browsers don't support 'inherit' property value, leave them intact. (#5242)
  506. if ( styleVal == 'inherit' )
  507. specialStylesText += text;
  508. else
  509. stylesText += text;
  510. }
  511. // Browsers make some changes to the style when applying them. So, here
  512. // we normalize it to the browser format.
  513. if ( stylesText.length )
  514. stylesText = CKEDITOR.tools.normalizeCssText( stylesText, true );
  515. stylesText += specialStylesText;
  516. // Return it, saving it to the next request.
  517. return ( styleDefinition._ST = stylesText );
  518. };
  519. /**
  520. * Namespace containing custom style handlers added with {@link CKEDITOR.style#addCustomHandler}.
  521. *
  522. * @since 4.4
  523. * @class
  524. * @singleton
  525. */
  526. CKEDITOR.style.customHandlers = {};
  527. /**
  528. * Creates a {@link CKEDITOR.style} subclass and registers it in the style system.
  529. * Registered class will be used as a handler for a style of this type. This allows
  530. * to extend the styles system, which by default uses only the {@link CKEDITOR.style}, with
  531. * new functionality. Registered classes are accessible in the {@link CKEDITOR.style.customHandlers}.
  532. *
  533. * ### The Style Class Definition
  534. *
  535. * The definition object is used to override properties in a prototype inherited
  536. * from the {@link CKEDITOR.style} class. It must contain a `type` property which is
  537. * a name of the new type and therefore it must be unique. The default style types
  538. * ({@link CKEDITOR#STYLE_BLOCK STYLE_BLOCK}, {@link CKEDITOR#STYLE_INLINE STYLE_INLINE},
  539. * and {@link CKEDITOR#STYLE_OBJECT STYLE_OBJECT}) are integers, but for easier identification
  540. * it is recommended to use strings as custom type names.
  541. *
  542. * Besides `type`, the definition may contain two more special properties:
  543. *
  544. * * `setup {Function}` &ndash; An optional callback executed when a style instance is created.
  545. * Like the style constructor, it is executed in style context and with the style definition as an argument.
  546. * * `assignedTo {Number}` &ndash; Can be set to one of the default style types. Some editor
  547. * features like the Styles drop-down assign styles to one of the default groups based on
  548. * the style type. By using this property it is possible to notify them to which group this
  549. * custom style should be assigned. It defaults to the {@link CKEDITOR#STYLE_OBJECT}.
  550. *
  551. * Other properties of the definition object will just be used to extend the prototype inherited
  552. * from the {@link CKEDITOR.style} class. So if the definition contains an `apply` method, it will
  553. * override the {@link CKEDITOR.style#apply} method.
  554. *
  555. * ### Usage
  556. *
  557. * Registering a basic handler:
  558. *
  559. * var styleClass = CKEDITOR.style.addCustomHandler( {
  560. * type: 'custom'
  561. * } );
  562. *
  563. * var style = new styleClass( { ... } );
  564. * style instanceof styleClass; // -> true
  565. * style instanceof CKEDITOR.style; // -> true
  566. * style.type; // -> 'custom'
  567. *
  568. * The {@link CKEDITOR.style} constructor used as a factory:
  569. *
  570. * var styleClass = CKEDITOR.style.addCustomHandler( {
  571. * type: 'custom'
  572. * } );
  573. *
  574. * // Style constructor accepts style definition (do not confuse with style class definition).
  575. * var style = new CKEDITOR.style( { type: 'custom', attributes: ... } );
  576. * style instanceof styleClass; // -> true
  577. *
  578. * Thanks to that, integration code using styles does not need to know
  579. * which style handler it should use. It is determined by the {@link CKEDITOR.style} constructor.
  580. *
  581. * Overriding existing {@link CKEDITOR.style} methods:
  582. *
  583. * var styleClass = CKEDITOR.style.addCustomHandler( {
  584. * type: 'custom',
  585. * apply: function( editor ) {
  586. * console.log( 'apply' );
  587. * },
  588. * remove: function( editor ) {
  589. * console.log( 'remove' );
  590. * }
  591. * } );
  592. *
  593. * var style = new CKEDITOR.style( { type: 'custom', attributes: ... } );
  594. * editor.applyStyle( style ); // logged 'apply'
  595. *
  596. * style = new CKEDITOR.style( { element: 'img', attributes: { 'class': 'foo' } } );
  597. * editor.applyStyle( style ); // style is really applied if image was selected
  598. *
  599. * ### Practical Recommendations
  600. *
  601. * The style handling job, which includes such tasks as applying, removing, checking state, and
  602. * checking if a style can be applied, is very complex. Therefore without deep knowledge
  603. * about DOM and especially {@link CKEDITOR.dom.range ranges} and {@link CKEDITOR.dom.walker DOM walker} it is impossible
  604. * to implement a completely custom style handler able to handle block, inline, and object type styles.
  605. * However, it is possible to customize the default implementation by overriding default methods and
  606. * reusing them.
  607. *
  608. * The only style handler which can be implemented from scratch without huge effort is a style
  609. * applicable to objects ([read more about types](http://docs.ckeditor.com/#!/guide/dev_styles-section-style-types)).
  610. * Such style can only be applied when a specific object is selected. An example implementation can
  611. * be found in the [widget plugin](https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/widget/plugin.js).
  612. *
  613. * When implementing a style handler from scratch at least the following methods must be defined:
  614. *
  615. * * {@link CKEDITOR.style#apply apply} and {@link CKEDITOR.style#remove remove},
  616. * * {@link CKEDITOR.style#checkElementRemovable checkElementRemovable} and
  617. * {@link CKEDITOR.style#checkElementMatch checkElementMatch} &ndash; Note that both methods reuse the same logic,
  618. * * {@link CKEDITOR.style#checkActive checkActive} &ndash; Reuses
  619. * {@link CKEDITOR.style#checkElementMatch checkElementMatch},
  620. * * {@link CKEDITOR.style#toAllowedContentRules toAllowedContentRules} &ndash; Not required, but very useful in
  621. * case of a custom style that has to notify the {@link CKEDITOR.filter} which rules it allows when registered.
  622. *
  623. * @since 4.4
  624. * @static
  625. * @member CKEDITOR.style
  626. * @param definition The style class definition.
  627. * @returns {CKEDITOR.style} The new style class created for the provided definition.
  628. */
  629. CKEDITOR.style.addCustomHandler = function( definition ) {
  630. var styleClass = function( styleDefinition ) {
  631. this._ = {
  632. definition: styleDefinition
  633. };
  634. if ( this.setup )
  635. this.setup( styleDefinition );
  636. };
  637. styleClass.prototype = CKEDITOR.tools.extend(
  638. // Prototype of CKEDITOR.style.
  639. CKEDITOR.tools.prototypedCopy( CKEDITOR.style.prototype ),
  640. // Defaults.
  641. {
  642. assignedTo: CKEDITOR.STYLE_OBJECT
  643. },
  644. // Passed definition - overrides.
  645. definition,
  646. true
  647. );
  648. this.customHandlers[ definition.type ] = styleClass;
  649. return styleClass;
  650. };
  651. // Gets the parent element which blocks the styling for an element. This
  652. // can be done through read-only elements (contenteditable=false) or
  653. // elements with the "data-nostyle" attribute.
  654. function getUnstylableParent( element, root ) {
  655. var unstylable, editable;
  656. while ( ( element = element.getParent() ) ) {
  657. if ( element.equals( root ) )
  658. break;
  659. if ( element.getAttribute( 'data-nostyle' ) )
  660. unstylable = element;
  661. else if ( !editable ) {
  662. var contentEditable = element.getAttribute( 'contentEditable' );
  663. if ( contentEditable == 'false' )
  664. unstylable = element;
  665. else if ( contentEditable == 'true' )
  666. editable = 1;
  667. }
  668. }
  669. return unstylable;
  670. }
  671. var posPrecedingIdenticalContained =
  672. CKEDITOR.POSITION_PRECEDING | CKEDITOR.POSITION_IDENTICAL | CKEDITOR.POSITION_IS_CONTAINED,
  673. posFollowingIdenticalContained =
  674. CKEDITOR.POSITION_FOLLOWING | CKEDITOR.POSITION_IDENTICAL | CKEDITOR.POSITION_IS_CONTAINED;
  675. // Checks if the current node can be a child of the style element.
  676. function checkIfNodeCanBeChildOfStyle( def, currentNode, lastNode, nodeName, dtd, nodeIsNoStyle, nodeIsReadonly, includeReadonly ) {
  677. // Style can be applied to text node.
  678. if ( !nodeName )
  679. return 1;
  680. // Style definitely cannot be applied if DTD or data-nostyle do not allow.
  681. if ( !dtd[ nodeName ] || nodeIsNoStyle )
  682. return 0;
  683. // Non-editable element cannot be styled is we shouldn't include readonly elements.
  684. if ( nodeIsReadonly && !includeReadonly )
  685. return 0;
  686. // Check that we haven't passed lastNode yet and that style's childRule allows this style on current element.
  687. return checkPositionAndRule( currentNode, lastNode, def, posPrecedingIdenticalContained );
  688. }
  689. // Check if the style element can be a child of the current
  690. // node parent or if the element is not defined in the DTD.
  691. function checkIfStyleCanBeChildOf( def, currentParent, elementName, isUnknownElement ) {
  692. return currentParent &&
  693. ( ( currentParent.getDtd() || CKEDITOR.dtd.span )[ elementName ] || isUnknownElement ) &&
  694. ( !def.parentRule || def.parentRule( currentParent ) );
  695. }
  696. function checkIfStartsRange( nodeName, currentNode, lastNode ) {
  697. return (
  698. !nodeName || !CKEDITOR.dtd.$removeEmpty[ nodeName ] ||
  699. ( currentNode.getPosition( lastNode ) | posPrecedingIdenticalContained ) == posPrecedingIdenticalContained
  700. );
  701. }
  702. function checkIfTextOrReadonlyOrEmptyElement( currentNode, nodeIsReadonly ) {
  703. var nodeType = currentNode.type;
  704. return nodeType == CKEDITOR.NODE_TEXT || nodeIsReadonly || ( nodeType == CKEDITOR.NODE_ELEMENT && !currentNode.getChildCount() );
  705. }
  706. // Checks if position is a subset of posBitFlags and that nodeA fulfills style def rule.
  707. function checkPositionAndRule( nodeA, nodeB, def, posBitFlags ) {
  708. return ( nodeA.getPosition( nodeB ) | posBitFlags ) == posBitFlags &&
  709. ( !def.childRule || def.childRule( nodeA ) );
  710. }
  711. function applyInlineStyle( range ) {
  712. var document = range.document;
  713. if ( range.collapsed ) {
  714. // Create the element to be inserted in the DOM.
  715. var collapsedElement = getElement( this, document );
  716. // Insert the empty element into the DOM at the range position.
  717. range.insertNode( collapsedElement );
  718. // Place the selection right inside the empty element.
  719. range.moveToPosition( collapsedElement, CKEDITOR.POSITION_BEFORE_END );
  720. return;
  721. }
  722. var elementName = this.element,
  723. def = this._.definition,
  724. isUnknownElement;
  725. // Indicates that fully selected read-only elements are to be included in the styling range.
  726. var ignoreReadonly = def.ignoreReadonly,
  727. includeReadonly = ignoreReadonly || def.includeReadonly;
  728. // If the read-only inclusion is not available in the definition, try
  729. // to get it from the root data (most often it's the editable).
  730. if ( includeReadonly == null )
  731. includeReadonly = range.root.getCustomData( 'cke_includeReadonly' );
  732. // Get the DTD definition for the element. Defaults to "span".
  733. var dtd = CKEDITOR.dtd[ elementName ];
  734. if ( !dtd ) {
  735. isUnknownElement = true;
  736. dtd = CKEDITOR.dtd.span;
  737. }
  738. // Expand the range.
  739. range.enlarge( CKEDITOR.ENLARGE_INLINE, 1 );
  740. range.trim();
  741. // Get the first node to be processed and the last, which concludes the processing.
  742. var boundaryNodes = range.createBookmark(),
  743. firstNode = boundaryNodes.startNode,
  744. lastNode = boundaryNodes.endNode,
  745. currentNode = firstNode,
  746. styleRange;
  747. if ( !ignoreReadonly ) {
  748. // Check if the boundaries are inside non stylable elements.
  749. var root = range.getCommonAncestor(),
  750. firstUnstylable = getUnstylableParent( firstNode, root ),
  751. lastUnstylable = getUnstylableParent( lastNode, root );
  752. // If the first element can't be styled, we'll start processing right
  753. // after its unstylable root.
  754. if ( firstUnstylable )
  755. currentNode = firstUnstylable.getNextSourceNode( true );
  756. // If the last element can't be styled, we'll stop processing on its
  757. // unstylable root.
  758. if ( lastUnstylable )
  759. lastNode = lastUnstylable;
  760. }
  761. // Do nothing if the current node now follows the last node to be processed.
  762. if ( currentNode.getPosition( lastNode ) == CKEDITOR.POSITION_FOLLOWING )
  763. currentNode = 0;
  764. while ( currentNode ) {
  765. var applyStyle = false;
  766. if ( currentNode.equals( lastNode ) ) {
  767. currentNode = null;
  768. applyStyle = true;
  769. } else {
  770. var nodeName = currentNode.type == CKEDITOR.NODE_ELEMENT ? currentNode.getName() : null,
  771. nodeIsReadonly = nodeName && ( currentNode.getAttribute( 'contentEditable' ) == 'false' ),
  772. nodeIsNoStyle = nodeName && currentNode.getAttribute( 'data-nostyle' );
  773. // Skip bookmarks.
  774. if ( nodeName && currentNode.data( 'cke-bookmark' ) ) {
  775. currentNode = currentNode.getNextSourceNode( true );
  776. continue;
  777. }
  778. // Find all nested editables of a non-editable block and apply this style inside them.
  779. if ( nodeIsReadonly && includeReadonly && CKEDITOR.dtd.$block[ nodeName ] )
  780. applyStyleOnNestedEditables.call( this, currentNode );
  781. // Check if the current node can be a child of the style element.
  782. if ( checkIfNodeCanBeChildOfStyle( def, currentNode, lastNode, nodeName, dtd, nodeIsNoStyle, nodeIsReadonly, includeReadonly ) ) {
  783. var currentParent = currentNode.getParent();
  784. // Check if the style element can be a child of the current
  785. // node parent or if the element is not defined in the DTD.
  786. if ( checkIfStyleCanBeChildOf( def, currentParent, elementName, isUnknownElement ) ) {
  787. // This node will be part of our range, so if it has not
  788. // been started, place its start right before the node.
  789. // In the case of an element node, it will be included
  790. // only if it is entirely inside the range.
  791. if ( !styleRange && checkIfStartsRange( nodeName, currentNode, lastNode ) ) {
  792. styleRange = range.clone();
  793. styleRange.setStartBefore( currentNode );
  794. }
  795. // Non element nodes, readonly elements, or empty
  796. // elements can be added completely to the range.
  797. if ( checkIfTextOrReadonlyOrEmptyElement( currentNode, nodeIsReadonly ) ) {
  798. var includedNode = currentNode;
  799. var parentNode;
  800. // This node is about to be included completelly, but,
  801. // if this is the last node in its parent, we must also
  802. // check if the parent itself can be added completelly
  803. // to the range, otherwise apply the style immediately.
  804. while (
  805. ( applyStyle = !includedNode.getNext( notBookmark ) ) &&
  806. ( parentNode = includedNode.getParent(), dtd[ parentNode.getName() ] ) &&
  807. checkPositionAndRule( parentNode, firstNode, def, posFollowingIdenticalContained )
  808. ) {
  809. includedNode = parentNode;
  810. }
  811. styleRange.setEndAfter( includedNode );
  812. }
  813. } else {
  814. applyStyle = true;
  815. }
  816. }
  817. // Style isn't applicable to current element, so apply style to
  818. // range ending at previously chosen position, or nowhere if we haven't
  819. // yet started styleRange.
  820. else {
  821. applyStyle = true;
  822. }
  823. // Get the next node to be processed.
  824. // If we're currently on a non-editable element or non-styleable element,
  825. // then we'll be moved to current node's sibling (or even further), so we'll
  826. // avoid messing up its content.
  827. currentNode = currentNode.getNextSourceNode( nodeIsNoStyle || nodeIsReadonly );
  828. }
  829. // Apply the style if we have something to which apply it.
  830. if ( applyStyle && styleRange && !styleRange.collapsed ) {
  831. // Build the style element, based on the style object definition.
  832. var styleNode = getElement( this, document ),
  833. styleHasAttrs = styleNode.hasAttributes();
  834. // Get the element that holds the entire range.
  835. var parent = styleRange.getCommonAncestor();
  836. var removeList = {
  837. styles: {},
  838. attrs: {},
  839. // Styles cannot be removed.
  840. blockedStyles: {},
  841. // Attrs cannot be removed.
  842. blockedAttrs: {}
  843. };
  844. var attName, styleName, value;
  845. // Loop through the parents, removing the redundant attributes
  846. // from the element to be applied.
  847. while ( styleNode && parent ) {
  848. if ( parent.getName() == elementName ) {
  849. for ( attName in def.attributes ) {
  850. if ( removeList.blockedAttrs[ attName ] || !( value = parent.getAttribute( styleName ) ) )
  851. continue;
  852. if ( styleNode.getAttribute( attName ) == value )
  853. removeList.attrs[ attName ] = 1;
  854. else
  855. removeList.blockedAttrs[ attName ] = 1;
  856. }
  857. for ( styleName in def.styles ) {
  858. if ( removeList.blockedStyles[ styleName ] || !( value = parent.getStyle( styleName ) ) )
  859. continue;
  860. if ( styleNode.getStyle( styleName ) == value )
  861. removeList.styles[ styleName ] = 1;
  862. else
  863. removeList.blockedStyles[ styleName ] = 1;
  864. }
  865. }
  866. parent = parent.getParent();
  867. }
  868. for ( attName in removeList.attrs )
  869. styleNode.removeAttribute( attName );
  870. for ( styleName in removeList.styles )
  871. styleNode.removeStyle( styleName );
  872. if ( styleHasAttrs && !styleNode.hasAttributes() )
  873. styleNode = null;
  874. if ( styleNode ) {
  875. // Move the contents of the range to the style element.
  876. styleRange.extractContents().appendTo( styleNode );
  877. // Insert it into the range position (it is collapsed after
  878. // extractContents.
  879. styleRange.insertNode( styleNode );
  880. // Here we do some cleanup, removing all duplicated
  881. // elements from the style element.
  882. removeFromInsideElement.call( this, styleNode );
  883. // Let's merge our new style with its neighbors, if possible.
  884. styleNode.mergeSiblings();
  885. // As the style system breaks text nodes constantly, let's normalize
  886. // things for performance.
  887. // With IE, some paragraphs get broken when calling normalize()
  888. // repeatedly. Also, for IE, we must normalize body, not documentElement.
  889. // IE is also known for having a "crash effect" with normalize().
  890. // We should try to normalize with IE too in some way, somewhere.
  891. if ( !CKEDITOR.env.ie )
  892. styleNode.$.normalize();
  893. }
  894. // Style already inherit from parents, left just to clear up any internal overrides. (#5931)
  895. else {
  896. styleNode = new CKEDITOR.dom.element( 'span' );
  897. styleRange.extractContents().appendTo( styleNode );
  898. styleRange.insertNode( styleNode );
  899. removeFromInsideElement.call( this, styleNode );
  900. styleNode.remove( true );
  901. }
  902. // Style applied, let's release the range, so it gets
  903. // re-initialization in the next loop.
  904. styleRange = null;
  905. }
  906. }
  907. // Remove the bookmark nodes.
  908. range.moveToBookmark( boundaryNodes );
  909. // Minimize the result range to exclude empty text nodes. (#5374)
  910. range.shrink( CKEDITOR.SHRINK_TEXT );
  911. // Get inside the remaining element if range.shrink( TEXT ) has failed because of non-editable elements inside.
  912. // E.g. range.shrink( TEXT ) will not get inside:
  913. // [<b><i contenteditable="false">x</i></b>]
  914. // but range.shrink( ELEMENT ) will.
  915. range.shrink( CKEDITOR.NODE_ELEMENT, true );
  916. }
  917. function removeInlineStyle( range ) {
  918. // Make sure our range has included all "collpased" parent inline nodes so
  919. // that our operation logic can be simpler.
  920. range.enlarge( CKEDITOR.ENLARGE_INLINE, 1 );
  921. var bookmark = range.createBookmark(),
  922. startNode = bookmark.startNode;
  923. if ( range.collapsed ) {
  924. var startPath = new CKEDITOR.dom.elementPath( startNode.getParent(), range.root ),
  925. // The topmost element in elementspatch which we should jump out of.
  926. boundaryElement;
  927. for ( var i = 0, element; i < startPath.elements.length && ( element = startPath.elements[ i ] ); i++ ) {
  928. // 1. If it's collaped inside text nodes, try to remove the style from the whole element.
  929. //
  930. // 2. Otherwise if it's collapsed on element boundaries, moving the selection
  931. // outside the styles instead of removing the whole tag,
  932. // also make sure other inner styles were well preserverd.(#3309)
  933. if ( element == startPath.block || element == startPath.blockLimit )
  934. break;
  935. if ( this.checkElementRemovable( element ) ) {
  936. var isStart;
  937. if ( range.collapsed && ( range.checkBoundaryOfElement( element, CKEDITOR.END ) || ( isStart = range.checkBoundaryOfElement( element, CKEDITOR.START ) ) ) ) {
  938. boundaryElement = element;
  939. boundaryElement.match = isStart ? 'start' : 'end';
  940. } else {
  941. // Before removing the style node, there may be a sibling to the style node
  942. // that's exactly the same to the one to be removed. To the user, it makes
  943. // no difference that they're separate entities in the DOM tree. So, merge
  944. // them before removal.
  945. element.mergeSiblings();
  946. if ( element.is( this.element ) )
  947. removeFromElement.call( this, element );
  948. else
  949. removeOverrides( element, getOverrides( this )[ element.getName() ] );
  950. }
  951. }
  952. }
  953. // Re-create the style tree after/before the boundary element,
  954. // the replication start from bookmark start node to define the
  955. // new range.
  956. if ( boundaryElement ) {
  957. var clonedElement = startNode;
  958. for ( i = 0; ; i++ ) {
  959. var newElement = startPath.elements[ i ];
  960. if ( newElement.equals( boundaryElement ) )
  961. break;
  962. // Avoid copying any matched element.
  963. else if ( newElement.match )
  964. continue;
  965. else
  966. newElement = newElement.clone();
  967. newElement.append( clonedElement );
  968. clonedElement = newElement;
  969. }
  970. clonedElement[ boundaryElement.match == 'start' ? 'insertBefore' : 'insertAfter' ]( boundaryElement );
  971. }
  972. } else {
  973. // Now our range isn't collapsed. Lets walk from the start node to the end
  974. // node via DFS and remove the styles one-by-one.
  975. var endNode = bookmark.endNode,
  976. me = this;
  977. breakNodes();
  978. // Now, do the DFS walk.
  979. var currentNode = startNode;
  980. while ( !currentNode.equals( endNode ) ) {
  981. // Need to get the next node first because removeFromElement() can remove
  982. // the current node from DOM tree.
  983. var nextNode = currentNode.getNextSourceNode();
  984. if ( currentNode.type == CKEDITOR.NODE_ELEMENT && this.checkElementRemovable( currentNode ) ) {
  985. // Remove style from element or overriding element.
  986. if ( currentNode.getName() == this.element )
  987. removeFromElement.call( this, currentNode );
  988. else
  989. removeOverrides( currentNode, getOverrides( this )[ currentNode.getName() ] );
  990. // removeFromElement() may have merged the next node with something before
  991. // the startNode via mergeSiblings(). In that case, the nextNode would
  992. // contain startNode and we'll have to call breakNodes() again and also
  993. // reassign the nextNode to something after startNode.
  994. if ( nextNode.type == CKEDITOR.NODE_ELEMENT && nextNode.contains( startNode ) ) {
  995. breakNodes();
  996. nextNode = startNode.getNext();
  997. }
  998. }
  999. currentNode = nextNode;
  1000. }
  1001. }
  1002. range.moveToBookmark( bookmark );
  1003. // See the comment for range.shrink in applyInlineStyle.
  1004. range.shrink( CKEDITOR.NODE_ELEMENT, true );
  1005. // Find out the style ancestor that needs to be broken down at startNode
  1006. // and endNode.
  1007. function breakNodes() {
  1008. var startPath = new CKEDITOR.dom.elementPath( startNode.getParent() ),
  1009. endPath = new CKEDITOR.dom.elementPath( endNode.getParent() ),
  1010. breakStart = null,
  1011. breakEnd = null;
  1012. for ( var i = 0; i < startPath.elements.length; i++ ) {
  1013. var element = startPath.elements[ i ];
  1014. if ( element == startPath.block || element == startPath.blockLimit )
  1015. break;
  1016. if ( me.checkElementRemovable( element, true ) )
  1017. breakStart = element;
  1018. }
  1019. for ( i = 0; i < endPath.elements.length; i++ ) {
  1020. element = endPath.elements[ i ];
  1021. if ( element == endPath.block || element == endPath.blockLimit )
  1022. break;
  1023. if ( me.checkElementRemovable( element, true ) )
  1024. breakEnd = element;
  1025. }
  1026. if ( breakEnd )
  1027. endNode.breakParent( breakEnd );
  1028. if ( breakStart )
  1029. startNode.breakParent( breakStart );
  1030. }
  1031. }
  1032. // Apply style to nested editables inside editablesContainer.
  1033. // @param {CKEDITOR.dom.element} editablesContainer
  1034. // @context CKEDITOR.style
  1035. function applyStyleOnNestedEditables( editablesContainer ) {
  1036. var editables = findNestedEditables( editablesContainer ),
  1037. editable,
  1038. l = editables.length,
  1039. i = 0,
  1040. range = l && new CKEDITOR.dom.range( editablesContainer.getDocument() );
  1041. for ( ; i < l; ++i ) {
  1042. editable = editables[ i ];
  1043. // Check if style is allowed by this editable's ACF.
  1044. if ( checkIfAllowedInEditable( editable, this ) ) {
  1045. range.selectNodeContents( editable );
  1046. applyInlineStyle.call( this, range );
  1047. }
  1048. }
  1049. }
  1050. // Finds nested editables within container. Does not return
  1051. // editables nested in another editable (twice).
  1052. function findNestedEditables( container ) {
  1053. var editables = [];
  1054. container.forEach( function( element ) {
  1055. if ( element.getAttribute( 'contenteditable' ) == 'true' ) {
  1056. editables.push( element );
  1057. return false; // Skip children.
  1058. }
  1059. }, CKEDITOR.NODE_ELEMENT, true );
  1060. return editables;
  1061. }
  1062. // Checks if style is allowed in this editable.
  1063. function checkIfAllowedInEditable( editable, style ) {
  1064. var filter = CKEDITOR.filter.instances[ editable.data( 'cke-filter' ) ];
  1065. return filter ? filter.check( style ) : 1;
  1066. }
  1067. // Checks if style is allowed by iterator's active filter.
  1068. function checkIfAllowedByIterator( iterator, style ) {
  1069. return iterator.activeFilter ? iterator.activeFilter.check( style ) : 1;
  1070. }
  1071. function applyObjectStyle( range ) {
  1072. // Selected or parent element. (#9651)
  1073. var start = range.getEnclosedNode() || range.getCommonAncestor( false, true ),
  1074. element = new CKEDITOR.dom.elementPath( start, range.root ).contains( this.element, 1 );
  1075. element && !element.isReadOnly() && setupElement( element, this );
  1076. }
  1077. function removeObjectStyle( range ) {
  1078. var parent = range.getCommonAncestor( true, true ),
  1079. element = new CKEDITOR.dom.elementPath( parent, range.root ).contains( this.element, 1 );
  1080. if ( !element )
  1081. return;
  1082. var style = this,
  1083. def = style._.definition,
  1084. attributes = def.attributes;
  1085. // Remove all defined attributes.
  1086. if ( attributes ) {
  1087. for ( var att in attributes )
  1088. element.removeAttribute( att, attributes[ att ] );
  1089. }
  1090. // Assign all defined styles.
  1091. if ( def.styles ) {
  1092. for ( var i in def.styles ) {
  1093. if ( def.styles.hasOwnProperty( i ) )
  1094. element.removeStyle( i );
  1095. }
  1096. }
  1097. }
  1098. function applyBlockStyle( range ) {
  1099. // Serializible bookmarks is needed here since
  1100. // elements may be merged.
  1101. var bookmark = range.createBookmark( true );
  1102. var iterator = range.createIterator();
  1103. iterator.enforceRealBlocks = true;
  1104. // make recognize <br /> tag as a separator in ENTER_BR mode (#5121)
  1105. if ( this._.enterMode )
  1106. iterator.enlargeBr = ( this._.enterMode != CKEDITOR.ENTER_BR );
  1107. var block,
  1108. doc = range.document,
  1109. newBlock;
  1110. while ( ( block = iterator.getNextParagraph() ) ) {
  1111. if ( !block.isReadOnly() && checkIfAllowedByIterator( iterator, this ) ) {
  1112. newBlock = getElement( this, doc, block );
  1113. replaceBlock( block, newBlock );
  1114. }
  1115. }
  1116. range.moveToBookmark( bookmark );
  1117. }
  1118. function removeBlockStyle( range ) {
  1119. // Serializible bookmarks is needed here since
  1120. // elements may be merged.
  1121. var bookmark = range.createBookmark( 1 );
  1122. var iterator = range.createIterator();
  1123. iterator.enforceRealBlocks = true;
  1124. iterator.enlargeBr = this._.enterMode != CKEDITOR.ENTER_BR;
  1125. var block,
  1126. newBlock;
  1127. while ( ( block = iterator.getNextParagraph() ) ) {
  1128. if ( this.checkElementRemovable( block ) ) {
  1129. // <pre> get special treatment.
  1130. if ( block.is( 'pre' ) ) {
  1131. newBlock = this._.enterMode == CKEDITOR.ENTER_BR ? null :
  1132. range.document.createElement( this._.enterMode == CKEDITOR.ENTER_P ? 'p' : 'div' );
  1133. newBlock && block.copyAttributes( newBlock );
  1134. replaceBlock( block, newBlock );
  1135. } else {
  1136. removeFromElement.call( this, block );
  1137. }
  1138. }
  1139. }
  1140. range.moveToBookmark( bookmark );
  1141. }
  1142. // Replace the original block with new one, with special treatment
  1143. // for <pre> blocks to make sure content format is well preserved, and merging/splitting adjacent
  1144. // when necessary. (#3188)
  1145. function replaceBlock( block, newBlock ) {
  1146. // Block is to be removed, create a temp element to
  1147. // save contents.
  1148. var removeBlock = !newBlock;
  1149. if ( removeBlock ) {
  1150. newBlock = block.getDocument().createElement( 'div' );
  1151. block.copyAttributes( newBlock );
  1152. }
  1153. var newBlockIsPre = newBlock && newBlock.is( 'pre' ),
  1154. blockIsPre = block.is( 'pre' ),
  1155. isToPre = newBlockIsPre && !blockIsPre,
  1156. isFromPre = !newBlockIsPre && blockIsPre;
  1157. if ( isToPre )
  1158. newBlock = toPre( block, newBlock );
  1159. else if ( isFromPre )
  1160. // Split big <pre> into pieces before start to convert.
  1161. newBlock = fromPres( removeBlock ? [ block.getHtml() ] : splitIntoPres( block ), newBlock );
  1162. else
  1163. block.moveChildren( newBlock );
  1164. newBlock.replace( block );
  1165. if ( newBlockIsPre ) {
  1166. // Merge previous <pre> blocks.
  1167. mergePre( newBlock );
  1168. } else if ( removeBlock ) {
  1169. removeNoAttribsElement( newBlock );
  1170. }
  1171. }
  1172. // Merge a <pre> block with a previous sibling if available.
  1173. function mergePre( preBlock ) {
  1174. var previousBlock;
  1175. if ( !( ( previousBlock = preBlock.getPrevious( nonWhitespaces ) ) && previousBlock.type == CKEDITOR.NODE_ELEMENT && previousBlock.is( 'pre' ) ) )
  1176. return;
  1177. // Merge the previous <pre> block contents into the current <pre>
  1178. // block.
  1179. //
  1180. // Another thing to be careful here is that currentBlock might contain
  1181. // a '\n' at the beginning, and previousBlock might contain a '\n'
  1182. // towards the end. These new lines are not normally displayed but they
  1183. // become visible after merging.
  1184. var mergedHtml = replace( previousBlock.getHtml(), /\n$/, '' ) + '\n\n' +
  1185. replace( preBlock.getHtml(), /^\n/, '' );
  1186. // Krugle: IE normalizes innerHTML from <pre>, breaking whitespaces.
  1187. if ( CKEDITOR.env.ie )
  1188. preBlock.$.outerHTML = '<pre>' + mergedHtml + '</pre>';
  1189. else
  1190. preBlock.setHtml( mergedHtml );
  1191. previousBlock.remove();
  1192. }
  1193. // Split into multiple <pre> blocks separated by double line-break.
  1194. function splitIntoPres( preBlock ) {
  1195. // Exclude the ones at header OR at tail,
  1196. // and ignore bookmark content between them.
  1197. var duoBrRegex = /(\S\s*)\n(?:\s|(<span[^>]+data-cke-bookmark.*?\/span>))*\n(?!$)/gi,
  1198. pres = [],
  1199. splitedHtml = replace( preBlock.getOuterHtml(), duoBrRegex, function( match, charBefore, bookmark ) {
  1200. return charBefore + '</pre>' + bookmark + '<pre>';
  1201. } );
  1202. splitedHtml.replace( /<pre\b.*?>([\s\S]*?)<\/pre>/gi, function( match, preContent ) {
  1203. pres.push( preContent );
  1204. } );
  1205. return pres;
  1206. }
  1207. // Wrapper function of String::replace without considering of head/tail bookmarks nodes.
  1208. function replace( str, regexp, replacement ) {
  1209. var headBookmark = '',
  1210. tailBookmark = '';
  1211. str = str.replace( /(^<span[^>]+data-cke-bookmark.*?\/span>)|(<span[^>]+data-cke-bookmark.*?\/span>$)/gi, function( str, m1, m2 ) {
  1212. m1 && ( headBookmark = m1 );
  1213. m2 && ( tailBookmark = m2 );
  1214. return '';
  1215. } );
  1216. return headBookmark + str.replace( regexp, replacement ) + tailBookmark;
  1217. }
  1218. // Converting a list of <pre> into blocks with format well preserved.
  1219. function fromPres( preHtmls, newBlock ) {
  1220. var docFrag;
  1221. if ( preHtmls.length > 1 )
  1222. docFrag = new CKEDITOR.dom.documentFragment( newBlock.getDocument() );
  1223. for ( var i = 0; i < preHtmls.length; i++ ) {
  1224. var blockHtml = preHtmls[ i ];
  1225. // 1. Trim the first and last line-breaks immediately after and before <pre>,
  1226. // they're not visible.
  1227. blockHtml = blockHtml.replace( /(\r\n|\r)/g, '\n' );
  1228. blockHtml = replace( blockHtml, /^[ \t]*\n/, '' );
  1229. blockHtml = replace( blockHtml, /\n$/, '' );
  1230. // 2. Convert spaces or tabs at the beginning or at the end to &nbsp;
  1231. blockHtml = replace( blockHtml, /^[ \t]+|[ \t]+$/g, function( match, offset ) {
  1232. if ( match.length == 1 ) // one space, preserve it
  1233. return '&nbsp;';
  1234. else if ( !offset ) // beginning of block
  1235. return CKEDITOR.tools.repeat( '&nbsp;', match.length - 1 ) + ' ';
  1236. else // end of block
  1237. return ' ' + CKEDITOR.tools.repeat( '&nbsp;', match.length - 1 );
  1238. } );
  1239. // 3. Convert \n to <BR>.
  1240. // 4. Convert contiguous (i.e. non-singular) spaces or tabs to &nbsp;
  1241. blockHtml = blockHtml.replace( /\n/g, '<br>' );
  1242. blockHtml = blockHtml.replace( /[ \t]{2,}/g, function( match ) {
  1243. return CKEDITOR.tools.repeat( '&nbsp;', match.length - 1 ) + ' ';
  1244. } );
  1245. if ( docFrag ) {
  1246. var newBlockClone = newBlock.clone();
  1247. newBlockClone.setHtml( blockHtml );
  1248. docFrag.append( newBlockClone );
  1249. } else {
  1250. newBlock.setHtml( blockHtml );
  1251. }
  1252. }
  1253. return docFrag || newBlock;
  1254. }
  1255. // Converting from a non-PRE block to a PRE block in formatting operations.
  1256. function toPre( block, newBlock ) {
  1257. var bogus = block.getBogus();
  1258. bogus && bogus.remove();
  1259. // First trim the block content.
  1260. var preHtml = block.getHtml();
  1261. // 1. Trim head/tail spaces, they're not visible.
  1262. preHtml = replace( preHtml, /(?:^[ \t\n\r]+)|(?:[ \t\n\r]+$)/g, '' );
  1263. // 2. Delete ANSI whitespaces immediately before and after <BR> because
  1264. // they are not visible.
  1265. preHtml = preHtml.replace( /[ \t\r\n]*(<br[^>]*>)[ \t\r\n]*/gi, '$1' );
  1266. // 3. Compress other ANSI whitespaces since they're only visible as one
  1267. // single space previously.
  1268. // 4. Convert &nbsp; to spaces since &nbsp; is no longer needed in <PRE>.
  1269. preHtml = preHtml.replace( /([ \t\n\r]+|&nbsp;)/g, ' ' );
  1270. // 5. Convert any <BR /> to \n. This must not be done earlier because
  1271. // the \n would then get compressed.
  1272. preHtml = preHtml.replace( /<br\b[^>]*>/gi, '\n' );
  1273. // Krugle: IE normalizes innerHTML to <pre>, breaking whitespaces.
  1274. if ( CKEDITOR.env.ie ) {
  1275. var temp = block.getDocument().createElement( 'div' );
  1276. temp.append( newBlock );
  1277. newBlock.$.outerHTML = '<pre>' + preHtml + '</pre>';
  1278. newBlock.copyAttributes( temp.getFirst() );
  1279. newBlock = temp.getFirst().remove();
  1280. } else {
  1281. newBlock.setHtml( preHtml );
  1282. }
  1283. return newBlock;
  1284. }
  1285. // Removes a style from an element itself, don't care about its subtree.
  1286. function removeFromElement( element, keepDataAttrs ) {
  1287. var def = this._.definition,
  1288. attributes = def.attributes,
  1289. styles = def.styles,
  1290. overrides = getOverrides( this )[ element.getName() ],
  1291. // If the style is only about the element itself, we have to remove the element.
  1292. removeEmpty = CKEDITOR.tools.isEmpty( attributes ) && CKEDITOR.tools.isEmpty( styles );
  1293. // Remove definition attributes/style from the elemnt.
  1294. for ( var attName in attributes ) {
  1295. // The 'class' element value must match (#1318).
  1296. if ( ( attName == 'class' || this._.definition.fullMatch ) && element.getAttribute( attName ) != normalizeProperty( attName, attributes[ attName ] ) )
  1297. continue;
  1298. // Do not touch data-* attributes (#11011) (#11258).
  1299. if ( keepDataAttrs && attName.slice( 0, 5 ) == 'data-' )
  1300. continue;
  1301. removeEmpty = element.hasAttribute( attName );
  1302. element.removeAttribute( attName );
  1303. }
  1304. for ( var styleName in styles ) {
  1305. // Full match style insist on having fully equivalence. (#5018)
  1306. if ( this._.definition.fullMatch && element.getStyle( styleName ) != normalizeProperty( styleName, styles[ styleName ], true ) )
  1307. continue;
  1308. removeEmpty = removeEmpty || !!element.getStyle( styleName );
  1309. element.removeStyle( styleName );
  1310. }
  1311. // Remove overrides, but don't remove the element if it's a block element
  1312. removeOverrides( element, overrides, blockElements[ element.getName() ] );
  1313. if ( removeEmpty ) {
  1314. if ( this._.definition.alwaysRemoveElement )
  1315. removeNoAttribsElement( element, 1 );
  1316. else {
  1317. if ( !CKEDITOR.dtd.$block[ element.getName() ] || this._.enterMode == CKEDITOR.ENTER_BR && !element.hasAttributes() )
  1318. removeNoAttribsElement( element );
  1319. else
  1320. element.renameNode( this._.enterMode == CKEDITOR.ENTER_P ? 'p' : 'div' );
  1321. }
  1322. }
  1323. }
  1324. // Removes a style from inside an element. Called on applyStyle to make cleanup
  1325. // before apply. During clean up this function keep data-* attribute in contrast
  1326. // to removeFromElement.
  1327. function removeFromInsideElement( element ) {
  1328. var overrides = getOverrides( this ),
  1329. innerElements = element.getElementsByTag( this.element ),
  1330. innerElement;
  1331. for ( var i = innerElements.count(); --i >= 0; ) {
  1332. innerElement = innerElements.getItem( i );
  1333. // Do not remove elements which are read only (e.g. duplicates inside widgets).
  1334. if ( !innerElement.isReadOnly() )
  1335. removeFromElement.call( this, innerElement, true );
  1336. }
  1337. // Now remove any other element with different name that is
  1338. // defined to be overriden.
  1339. for ( var overrideElement in overrides ) {
  1340. if ( overrideElement != this.element ) {
  1341. innerElements = element.getElementsByTag( overrideElement );
  1342. for ( i = innerElements.count() - 1; i >= 0; i-- ) {
  1343. innerElement = innerElements.getItem( i );
  1344. // Do not remove elements which are read only (e.g. duplicates inside widgets).
  1345. if ( !innerElement.isReadOnly() )
  1346. removeOverrides( innerElement, overrides[ overrideElement ] );
  1347. }
  1348. }
  1349. }
  1350. }
  1351. // Remove overriding styles/attributes from the specific element.
  1352. // Note: Remove the element if no attributes remain.
  1353. // @param {Object} element
  1354. // @param {Object} overrides
  1355. // @param {Boolean} Don't remove the element
  1356. function removeOverrides( element, overrides, dontRemove ) {
  1357. var attributes = overrides && overrides.attributes;
  1358. if ( attributes ) {
  1359. for ( var i = 0; i < attributes.length; i++ ) {
  1360. var attName = attributes[ i ][ 0 ],
  1361. actualAttrValue;
  1362. if ( ( actualAttrValue = element.getAttribute( attName ) ) ) {
  1363. var attValue = attributes[ i ][ 1 ];
  1364. // Remove the attribute if:
  1365. // - The override definition value is null ;
  1366. // - The override definition valie is a string that
  1367. // matches the attribute value exactly.
  1368. // - The override definition value is a regex that
  1369. // has matches in the attribute value.
  1370. if ( attValue === null || ( attValue.test && attValue.test( actualAttrValue ) ) || ( typeof attValue == 'string' && actualAttrValue == attValue ) )
  1371. element.removeAttribute( attName );
  1372. }
  1373. }
  1374. }
  1375. if ( !dontRemove )
  1376. removeNoAttribsElement( element );
  1377. }
  1378. // If the element has no more attributes, remove it.
  1379. function removeNoAttribsElement( element, forceRemove ) {
  1380. // If no more attributes remained in the element, remove it,
  1381. // leaving its children.
  1382. if ( !element.hasAttributes() || forceRemove ) {
  1383. if ( CKEDITOR.dtd.$block[ element.getName() ] ) {
  1384. var previous = element.getPrevious( nonWhitespaces ),
  1385. next = element.getNext( nonWhitespaces );
  1386. if ( previous && ( previous.type == CKEDITOR.NODE_TEXT || !previous.isBlockBoundary( { br: 1 } ) ) )
  1387. element.append( 'br', 1 );
  1388. if ( next && ( next.type == CKEDITOR.NODE_TEXT || !next.isBlockBoundary( { br: 1 } ) ) )
  1389. element.append( 'br' );
  1390. element.remove( true );
  1391. } else {
  1392. // Removing elements may open points where merging is possible,
  1393. // so let's cache the first and last nodes for later checking.
  1394. var firstChild = element.getFirst();
  1395. var lastChild = element.getLast();
  1396. element.remove( true );
  1397. if ( firstChild ) {
  1398. // Check the cached nodes for merging.
  1399. firstChild.type == CKEDITOR.NODE_ELEMENT && firstChild.mergeSiblings();
  1400. if ( lastChild && !firstChild.equals( lastChild ) && lastChild.type == CKEDITOR.NODE_ELEMENT )
  1401. lastChild.mergeSiblings();
  1402. }
  1403. }
  1404. }
  1405. }
  1406. function getElement( style, targetDocument, element ) {
  1407. var el,
  1408. elementName = style.element;
  1409. // The "*" element name will always be a span for this function.
  1410. if ( elementName == '*' )
  1411. elementName = 'span';
  1412. // Create the element.
  1413. el = new CKEDITOR.dom.element( elementName, targetDocument );
  1414. // #6226: attributes should be copied before the new ones are applied
  1415. if ( element )
  1416. element.copyAttributes( el );
  1417. el = setupElement( el, style );
  1418. // Avoid ID duplication.
  1419. if ( targetDocument.getCustomData( 'doc_processing_style' ) && el.hasAttribute( 'id' ) )
  1420. el.removeAttribute( 'id' );
  1421. else
  1422. targetDocument.setCustomData( 'doc_processing_style', 1 );
  1423. return el;
  1424. }
  1425. function setupElement( el, style ) {
  1426. var def = style._.definition,
  1427. attributes = def.attributes,
  1428. styles = CKEDITOR.style.getStyleText( def );
  1429. // Assign all defined attributes.
  1430. if ( attributes ) {
  1431. for ( var att in attributes )
  1432. el.setAttribute( att, attributes[ att ] );
  1433. }
  1434. // Assign all defined styles.
  1435. if ( styles )
  1436. el.setAttribute( 'style', styles );
  1437. return el;
  1438. }
  1439. function replaceVariables( list, variablesValues ) {
  1440. for ( var item in list ) {
  1441. list[ item ] = list[ item ].replace( varRegex, function( match, varName ) {
  1442. return variablesValues[ varName ];
  1443. } );
  1444. }
  1445. }
  1446. // Returns an object that can be used for style matching comparison.
  1447. // Attributes names and values are all lowercased, and the styles get
  1448. // merged with the style attribute.
  1449. function getAttributesForComparison( styleDefinition ) {
  1450. // If we have already computed it, just return it.
  1451. var attribs = styleDefinition._AC;
  1452. if ( attribs )
  1453. return attribs;
  1454. attribs = {};
  1455. var length = 0;
  1456. // Loop through all defined attributes.
  1457. var styleAttribs = styleDefinition.attributes;
  1458. if ( styleAttribs ) {
  1459. for ( var styleAtt in styleAttribs ) {
  1460. length++;
  1461. attribs[ styleAtt ] = styleAttribs[ styleAtt ];
  1462. }
  1463. }
  1464. // Includes the style definitions.
  1465. var styleText = CKEDITOR.style.getStyleText( styleDefinition );
  1466. if ( styleText ) {
  1467. if ( !attribs.style )
  1468. length++;
  1469. attribs.style = styleText;
  1470. }
  1471. // Appends the "length" information to the object.
  1472. attribs._length = length;
  1473. // Return it, saving it to the next request.
  1474. return ( styleDefinition._AC = attribs );
  1475. }
  1476. // Get the the collection used to compare the elements and attributes,
  1477. // defined in this style overrides, with other element. All information in
  1478. // it is lowercased.
  1479. // @param {CKEDITOR.style} style
  1480. function getOverrides( style ) {
  1481. if ( style._.overrides )
  1482. return style._.overrides;
  1483. var overrides = ( style._.overrides = {} ),
  1484. definition = style._.definition.overrides;
  1485. if ( definition ) {
  1486. // The override description can be a string, object or array.
  1487. // Internally, well handle arrays only, so transform it if needed.
  1488. if ( !CKEDITOR.tools.isArray( definition ) )
  1489. definition = [ definition ];
  1490. // Loop through all override definitions.
  1491. for ( var i = 0; i < definition.length; i++ ) {
  1492. var override = definition[ i ],
  1493. elementName,
  1494. overrideEl,
  1495. attrs;
  1496. // If can be a string with the element name.
  1497. if ( typeof override == 'string' )
  1498. elementName = override.toLowerCase();
  1499. // Or an object.
  1500. else {
  1501. elementName = override.element ? override.element.toLowerCase() : style.element;
  1502. attrs = override.attributes;
  1503. }
  1504. // We can have more than one override definition for the same
  1505. // element name, so we attempt to simply append information to
  1506. // it if it already exists.
  1507. overrideEl = overrides[ elementName ] || ( overrides[ elementName ] = {} );
  1508. if ( attrs ) {
  1509. // The returning attributes list is an array, because we
  1510. // could have different override definitions for the same
  1511. // attribute name.
  1512. var overrideAttrs = ( overrideEl.attributes = overrideEl.attributes || [] );
  1513. for ( var attName in attrs ) {
  1514. // Each item in the attributes array is also an array,
  1515. // where [0] is the attribute name and [1] is the
  1516. // override value.
  1517. overrideAttrs.push( [ attName.toLowerCase(), attrs[ attName ] ] );
  1518. }
  1519. }
  1520. }
  1521. }
  1522. return overrides;
  1523. }
  1524. // Make the comparison of attribute value easier by standardizing it.
  1525. function normalizeProperty( name, value, isStyle ) {
  1526. var temp = new CKEDITOR.dom.element( 'span' );
  1527. temp[ isStyle ? 'setStyle' : 'setAttribute' ]( name, value );
  1528. return temp[ isStyle ? 'getStyle' : 'getAttribute' ]( name );
  1529. }
  1530. // Compare two bunch of styles, with the speciality that value 'inherit'
  1531. // is treated as a wildcard which will match any value.
  1532. // @param {Object/String} source
  1533. // @param {Object/String} target
  1534. function compareCssText( source, target ) {
  1535. if ( typeof source == 'string' )
  1536. source = CKEDITOR.tools.parseCssText( source );
  1537. if ( typeof target == 'string' )
  1538. target = CKEDITOR.tools.parseCssText( target, true );
  1539. for ( var name in source ) {
  1540. if ( !( name in target && ( target[ name ] == source[ name ] || source[ name ] == 'inherit' || target[ name ] == 'inherit' ) ) )
  1541. return false;
  1542. }
  1543. return true;
  1544. }
  1545. function applyStyleOnSelection( selection, remove, editor ) {
  1546. var doc = selection.document,
  1547. ranges = selection.getRanges(),
  1548. func = remove ? this.removeFromRange : this.applyToRange,
  1549. range;
  1550. var iterator = ranges.createIterator();
  1551. while ( ( range = iterator.getNextRange() ) )
  1552. func.call( this, range, editor );
  1553. selection.selectRanges( ranges );
  1554. doc.removeCustomData( 'doc_processing_style' );
  1555. }
  1556. } )();
  1557. /**
  1558. * Generic style command. It applies a specific style when executed.
  1559. *
  1560. * var boldStyle = new CKEDITOR.style( { element: 'strong' } );
  1561. * // Register the "bold" command, which applies the bold style.
  1562. * editor.addCommand( 'bold', new CKEDITOR.styleCommand( boldStyle ) );
  1563. *
  1564. * @class
  1565. * @constructor Creates a styleCommand class instance.
  1566. * @extends CKEDITOR.commandDefinition
  1567. * @param {CKEDITOR.style} style The style to be applied when command is executed.
  1568. * @param {Object} [ext] Additional command definition's properties.
  1569. */
  1570. CKEDITOR.styleCommand = function( style, ext ) {
  1571. this.style = style;
  1572. this.allowedContent = style;
  1573. this.requiredContent = style;
  1574. CKEDITOR.tools.extend( this, ext, true );
  1575. };
  1576. /**
  1577. * @param {CKEDITOR.editor} editor
  1578. * @todo
  1579. */
  1580. CKEDITOR.styleCommand.prototype.exec = function( editor ) {
  1581. editor.focus();
  1582. if ( this.state == CKEDITOR.TRISTATE_OFF )
  1583. editor.applyStyle( this.style );
  1584. else if ( this.state == CKEDITOR.TRISTATE_ON )
  1585. editor.removeStyle( this.style );
  1586. };
  1587. /**
  1588. * Manages styles registration and loading. See also {@link CKEDITOR.config#stylesSet}.
  1589. *
  1590. * // The set of styles for the <b>Styles</b> drop-down list.
  1591. * CKEDITOR.stylesSet.add( 'default', [
  1592. * // Block Styles
  1593. * { name: 'Blue Title', element: 'h3', styles: { 'color': 'Blue' } },
  1594. * { name: 'Red Title', element: 'h3', styles: { 'color': 'Red' } },
  1595. *
  1596. * // Inline Styles
  1597. * { name: 'Marker: Yellow', element: 'span', styles: { 'background-color': 'Yellow' } },
  1598. * { name: 'Marker: Green', element: 'span', styles: { 'background-color': 'Lime' } },
  1599. *
  1600. * // Object Styles
  1601. * {
  1602. * name: 'Image on Left',
  1603. * element: 'img',
  1604. * attributes: {
  1605. * style: 'padding: 5px; margin-right: 5px',
  1606. * border: '2',
  1607. * align: 'left'
  1608. * }
  1609. * }
  1610. * ] );
  1611. *
  1612. * @since 3.2
  1613. * @class
  1614. * @singleton
  1615. * @extends CKEDITOR.resourceManager
  1616. */
  1617. CKEDITOR.stylesSet = new CKEDITOR.resourceManager( '', 'stylesSet' );
  1618. // Backward compatibility (#5025).
  1619. CKEDITOR.addStylesSet = CKEDITOR.tools.bind( CKEDITOR.stylesSet.add, CKEDITOR.stylesSet );
  1620. CKEDITOR.loadStylesSet = function( name, url, callback ) {
  1621. CKEDITOR.stylesSet.addExternal( name, url, '' );
  1622. CKEDITOR.stylesSet.load( name, callback );
  1623. };
  1624. CKEDITOR.tools.extend( CKEDITOR.editor.prototype, {
  1625. /**
  1626. * Registers a function to be called whenever the selection position changes in the
  1627. * editing area. The current state is passed to the function. The possible
  1628. * states are {@link CKEDITOR#TRISTATE_ON} and {@link CKEDITOR#TRISTATE_OFF}.
  1629. *
  1630. * // Create a style object for the <b> element.
  1631. * var style = new CKEDITOR.style( { element: 'b' } );
  1632. * var editor = CKEDITOR.instances.editor1;
  1633. * editor.attachStyleStateChange( style, function( state ) {
  1634. * if ( state == CKEDITOR.TRISTATE_ON )
  1635. * alert( 'The current state for the B element is ON' );
  1636. * else
  1637. * alert( 'The current state for the B element is OFF' );
  1638. * } );
  1639. *
  1640. * @member CKEDITOR.editor
  1641. * @param {CKEDITOR.style} style The style to be watched.
  1642. * @param {Function} callback The function to be called.
  1643. */
  1644. attachStyleStateChange: function( style, callback ) {
  1645. // Try to get the list of attached callbacks.
  1646. var styleStateChangeCallbacks = this._.styleStateChangeCallbacks;
  1647. // If it doesn't exist, it means this is the first call. So, let's create
  1648. // all the structure to manage the style checks and the callback calls.
  1649. if ( !styleStateChangeCallbacks ) {
  1650. // Create the callbacks array.
  1651. styleStateChangeCallbacks = this._.styleStateChangeCallbacks = [];
  1652. // Attach to the selectionChange event, so we can check the styles at
  1653. // that point.
  1654. this.on( 'selectionChange', function( ev ) {
  1655. // Loop throw all registered callbacks.
  1656. for ( var i = 0; i < styleStateChangeCallbacks.length; i++ ) {
  1657. var callback = styleStateChangeCallbacks[ i ];
  1658. // Check the current state for the style defined for that callback.
  1659. var currentState = callback.style.checkActive( ev.data.path, this ) ?
  1660. CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF;
  1661. // Call the callback function, passing the current state to it.
  1662. callback.fn.call( this, currentState );
  1663. }
  1664. } );
  1665. }
  1666. // Save the callback info, so it can be checked on the next occurrence of
  1667. // selectionChange.
  1668. styleStateChangeCallbacks.push( { style: style, fn: callback } );
  1669. },
  1670. /**
  1671. * Applies the style upon the editor's current selection. Shorthand for
  1672. * {@link CKEDITOR.style#apply}.
  1673. *
  1674. * @member CKEDITOR.editor
  1675. * @param {CKEDITOR.style} style
  1676. */
  1677. applyStyle: function( style ) {
  1678. style.apply( this );
  1679. },
  1680. /**
  1681. * Removes the style from the editor's current selection. Shorthand for
  1682. * {@link CKEDITOR.style#remove}.
  1683. *
  1684. * @member CKEDITOR.editor
  1685. * @param {CKEDITOR.style} style
  1686. */
  1687. removeStyle: function( style ) {
  1688. style.remove( this );
  1689. },
  1690. /**
  1691. * Gets the current `stylesSet` for this instance.
  1692. *
  1693. * editor.getStylesSet( function( stylesDefinitions ) {} );
  1694. *
  1695. * See also {@link CKEDITOR.editor#stylesSet} event.
  1696. *
  1697. * @member CKEDITOR.editor
  1698. * @param {Function} callback The function to be called with the styles data.
  1699. */
  1700. getStylesSet: function( callback ) {
  1701. if ( !this._.stylesDefinitions ) {
  1702. var editor = this,
  1703. // Respect the backwards compatible definition entry
  1704. configStyleSet = editor.config.stylesCombo_stylesSet || editor.config.stylesSet;
  1705. // The false value means that none styles should be loaded.
  1706. if ( configStyleSet === false ) {
  1707. callback( null );
  1708. return;
  1709. }
  1710. // #5352 Allow to define the styles directly in the config object
  1711. if ( configStyleSet instanceof Array ) {
  1712. editor._.stylesDefinitions = configStyleSet;
  1713. callback( configStyleSet );
  1714. return;
  1715. }
  1716. // Default value is 'default'.
  1717. if ( !configStyleSet )
  1718. configStyleSet = 'default';
  1719. var partsStylesSet = configStyleSet.split( ':' ),
  1720. styleSetName = partsStylesSet[ 0 ],
  1721. externalPath = partsStylesSet[ 1 ];
  1722. CKEDITOR.stylesSet.addExternal( styleSetName, externalPath ? partsStylesSet.slice( 1 ).join( ':' ) : CKEDITOR.getUrl( 'styles.js' ), '' );
  1723. CKEDITOR.stylesSet.load( styleSetName, function( stylesSet ) {
  1724. editor._.stylesDefinitions = stylesSet[ styleSetName ];
  1725. callback( editor._.stylesDefinitions );
  1726. } );
  1727. } else {
  1728. callback( this._.stylesDefinitions );
  1729. }
  1730. }
  1731. } );
  1732. /**
  1733. * Indicates that fully selected read-only elements will be included when
  1734. * applying the style (for inline styles only).
  1735. *
  1736. * @since 3.5
  1737. * @property {Boolean} [includeReadonly=false]
  1738. * @member CKEDITOR.style
  1739. */
  1740. /**
  1741. * Indicates that any matches element of this style will be eventually removed
  1742. * when calling {@link CKEDITOR.editor#removeStyle}.
  1743. *
  1744. * @since 4.0
  1745. * @property {Boolean} [alwaysRemoveElement=false]
  1746. * @member CKEDITOR.style
  1747. */
  1748. /**
  1749. * Disables inline styling on read-only elements.
  1750. *
  1751. * @since 3.5
  1752. * @cfg {Boolean} [disableReadonlyStyling=false]
  1753. * @member CKEDITOR.config
  1754. */
  1755. /**
  1756. * The "styles definition set" to use in the editor. They will be used in the
  1757. * styles combo and the style selector of the div container.
  1758. *
  1759. * The styles may be defined in the page containing the editor, or can be
  1760. * loaded on demand from an external file. In the second case, if this setting
  1761. * contains only a name, the `styles.js` file will be loaded from the
  1762. * CKEditor root folder (what ensures backward compatibility with CKEditor 4.0).
  1763. *
  1764. * Otherwise, this setting has the `name:url` syntax, making it
  1765. * possible to set the URL from which loading the styles file.
  1766. * Note that the `name` has to be equal to the name used in
  1767. * {@link CKEDITOR.stylesSet#add} while registering styles set.
  1768. *
  1769. * **Note**: Since 4.1 it is possible to set `stylesSet` to `false`
  1770. * to prevent loading any styles set.
  1771. *
  1772. * // Do not load any file. Styles set is empty.
  1773. * config.stylesSet = false;
  1774. *
  1775. * // Load the 'mystyles' styles set from styles.js file.
  1776. * config.stylesSet = 'mystyles';
  1777. *
  1778. * // Load the 'mystyles' styles set from a relative URL.
  1779. * config.stylesSet = 'mystyles:/editorstyles/styles.js';
  1780. *
  1781. * // Load from a full URL.
  1782. * config.stylesSet = 'mystyles:http://www.example.com/editorstyles/styles.js';
  1783. *
  1784. * // Load from a list of definitions.
  1785. * config.stylesSet = [
  1786. * { name: 'Strong Emphasis', element: 'strong' },
  1787. * { name: 'Emphasis', element: 'em' },
  1788. * ...
  1789. * ];
  1790. *
  1791. * @since 3.3
  1792. * @cfg {String/Array/Boolean} [stylesSet='default']
  1793. * @member CKEDITOR.config
  1794. */