plugin.js 63 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777
  1. /**
  2. * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or http://ckeditor.com/license
  4. */
  5. 'use strict';
  6. ( function() {
  7. var template = '<img alt="" src="" />',
  8. templateBlock = new CKEDITOR.template(
  9. '<figure class="{captionedClass}">' +
  10. template +
  11. '<figcaption>{captionPlaceholder}</figcaption>' +
  12. '</figure>' ),
  13. alignmentsObj = {
  14. 'left': 0,
  15. 'center': 1,
  16. 'right': 2,
  17. 'baseline': 3,
  18. 'top': 4,
  19. 'bottom': 5,
  20. 'middle': 6,
  21. 'super': 7,
  22. 'sub': 8,
  23. 'text-top': 9,
  24. 'text-bottom': 10
  25. },
  26. regexPercent = /^\s*(\d+\%)\s*$/i;
  27. CKEDITOR.plugins.add( 'image2_chamilo', {
  28. // jscs:disable maximumLineLength
  29. lang: 'af,ar,az,bg,bn,bs,ca,cs,cy,da,de,de-ch,el,en,en-au,en-ca,en-gb,eo,es,et,eu,fa,fi,fo,fr,fr-ca,gl,gu,he,hi,hr,hu,id,is,it,ja,ka,km,ko,ku,lt,lv,mk,mn,ms,nb,nl,no,oc,pl,pt,pt-br,ro,ru,si,sk,sl,sq,sr,sr-latn,sv,th,tr,tt,ug,uk,vi,zh,zh-cn', // %REMOVE_LINE_CORE%
  30. // jscs:enable maximumLineLength
  31. requires: 'widget,dialog',
  32. icons: 'image',
  33. hidpi: true,
  34. onLoad: function() {
  35. CKEDITOR.addCss(
  36. '.cke_image_nocaption{' +
  37. // This is to remove unwanted space so resize
  38. // wrapper is displayed property.
  39. 'line-height:0' +
  40. '}' +
  41. '.cke_editable.cke_image_sw, .cke_editable.cke_image_sw *{cursor:sw-resize !important}' +
  42. '.cke_editable.cke_image_se, .cke_editable.cke_image_se *{cursor:se-resize !important}' +
  43. '.cke_image_resizer{' +
  44. 'display:none;' +
  45. 'position:absolute;' +
  46. 'width:10px;' +
  47. 'height:10px;' +
  48. 'bottom:-5px;' +
  49. 'right:-5px;' +
  50. 'background:#000;' +
  51. 'outline:1px solid #fff;' +
  52. // Prevent drag handler from being misplaced (#11207).
  53. 'line-height:0;' +
  54. 'cursor:se-resize;' +
  55. '}' +
  56. '.cke_image_resizer_wrapper{' +
  57. 'position:relative;' +
  58. 'display:inline-block;' +
  59. 'line-height:0;' +
  60. '}' +
  61. // Bottom-left corner style of the resizer.
  62. '.cke_image_resizer.cke_image_resizer_left{' +
  63. 'right:auto;' +
  64. 'left:-5px;' +
  65. 'cursor:sw-resize;' +
  66. '}' +
  67. '.cke_widget_wrapper:hover .cke_image_resizer,' +
  68. '.cke_image_resizer.cke_image_resizing{' +
  69. 'display:block' +
  70. '}' +
  71. // Expand widget wrapper when linked inline image.
  72. '.cke_widget_wrapper>a{' +
  73. 'display:inline-block' +
  74. '}' );
  75. },
  76. init: function( editor ) {
  77. // Adapts configuration from original image plugin. Should be removed
  78. // when we'll rename image2_chamilo to image.
  79. var config = editor.config,
  80. lang = editor.lang.image2_chamilo,
  81. image = widgetDef( editor );
  82. // Since filebrowser plugin discovers config properties by dialog (plugin?)
  83. // names (sic!), this hack will be necessary as long as Image2Chamilo is not named
  84. // Image. And since Image2Chamilo will never be Image, for sure some filebrowser logic
  85. // got to be refined.
  86. config.filebrowserImage2ChamiloBrowseUrl = config.filebrowserImageBrowseUrl;
  87. config.filebrowserImage2ChamiloUploadUrl = config.filebrowserImageUploadUrl;
  88. // Add custom elementspath names to widget definition.
  89. image.pathName = lang.pathName;
  90. image.editables.caption.pathName = lang.pathNameCaption;
  91. // Register the widget.
  92. editor.widgets.add( 'image', image );
  93. // Add toolbar button for this plugin.
  94. editor.ui.addButton && editor.ui.addButton( 'Image', {
  95. label: editor.lang.common.image,
  96. command: 'image',
  97. toolbar: 'insert,10'
  98. } );
  99. // Register context menu option for editing widget.
  100. if ( editor.contextMenu ) {
  101. editor.addMenuGroup( 'image', 10 );
  102. editor.addMenuItem( 'image', {
  103. label: lang.menu,
  104. command: 'image',
  105. group: 'image'
  106. } );
  107. }
  108. CKEDITOR.dialog.add( 'image2_chamilo', this.path + 'dialogs/image2_chamilo.js' );
  109. },
  110. afterInit: function( editor ) {
  111. // Integrate with align commands (justify plugin).
  112. var align = { left: 1, right: 1, center: 1, block: 1 },
  113. integrate = alignCommandIntegrator( editor );
  114. for ( var value in align )
  115. integrate( value );
  116. // Integrate with link commands (link plugin).
  117. linkCommandIntegrator( editor );
  118. }
  119. } );
  120. // Wiget states (forms) depending on alignment and configuration.
  121. //
  122. // Non-captioned widget (inline styles)
  123. // ┌──────┬───────────────────────────────┬─────────────────────────────┐
  124. // │Align │Internal form │Data │
  125. // ├──────┼───────────────────────────────┼─────────────────────────────┤
  126. // │none │<wrapper> │<img /> │
  127. // │ │ <img /> │ │
  128. // │ │</wrapper> │ │
  129. // ├──────┼───────────────────────────────┼─────────────────────────────┤
  130. // │left │<wrapper style=”float:left”> │<img style=”float:left” /> │
  131. // │ │ <img /> │ │
  132. // │ │</wrapper> │ │
  133. // ├──────┼───────────────────────────────┼─────────────────────────────┤
  134. // │center│<wrapper> │<p style=”text-align:center”>│
  135. // │ │ <p style=”text-align:center”> │ <img /> │
  136. // │ │ <img /> │</p> │
  137. // │ │ </p> │ │
  138. // │ │</wrapper> │ │
  139. // ├──────┼───────────────────────────────┼─────────────────────────────┤
  140. // │right │<wrapper style=”float:right”> │<img style=”float:right” /> │
  141. // │ │ <img /> │ │
  142. // │ │</wrapper> │ │
  143. // └──────┴───────────────────────────────┴─────────────────────────────┘
  144. //
  145. // Non-captioned widget (config.image2_chamilo_alignClasses defined)
  146. // ┌──────┬───────────────────────────────┬─────────────────────────────┐
  147. // │Align │Internal form │Data │
  148. // ├──────┼───────────────────────────────┼─────────────────────────────┤
  149. // │none │<wrapper> │<img /> │
  150. // │ │ <img /> │ │
  151. // │ │</wrapper> │ │
  152. // ├──────┼───────────────────────────────┼─────────────────────────────┤
  153. // │left │<wrapper class=”left”> │<img class=”left” /> │
  154. // │ │ <img /> │ │
  155. // │ │</wrapper> │ │
  156. // ├──────┼───────────────────────────────┼─────────────────────────────┤
  157. // │center│<wrapper> │<p class=”center”> │
  158. // │ │ <p class=”center”> │ <img /> │
  159. // │ │ <img /> │</p> │
  160. // │ │ </p> │ │
  161. // │ │</wrapper> │ │
  162. // ├──────┼───────────────────────────────┼─────────────────────────────┤
  163. // │right │<wrapper class=”right”> │<img class=”right” /> │
  164. // │ │ <img /> │ │
  165. // │ │</wrapper> │ │
  166. // └──────┴───────────────────────────────┴─────────────────────────────┘
  167. //
  168. // Captioned widget (inline styles)
  169. // ┌──────┬────────────────────────────────────────┬────────────────────────────────────────┐
  170. // │Align │Internal form │Data │
  171. // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
  172. // │none │<wrapper> │<figure /> │
  173. // │ │ <figure /> │ │
  174. // │ │</wrapper> │ │
  175. // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
  176. // │left │<wrapper style=”float:left”> │<figure style=”float:left” /> │
  177. // │ │ <figure /> │ │
  178. // │ │</wrapper> │ │
  179. // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
  180. // │center│<wrapper style=”text-align:center”> │<div style=”text-align:center”> │
  181. // │ │ <figure style=”display:inline-block” />│ <figure style=”display:inline-block” />│
  182. // │ │</wrapper> │</p> │
  183. // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
  184. // │right │<wrapper style=”float:right”> │<figure style=”float:right” /> │
  185. // │ │ <figure /> │ │
  186. // │ │</wrapper> │ │
  187. // └──────┴────────────────────────────────────────┴────────────────────────────────────────┘
  188. //
  189. // Captioned widget (config.image2_chamilo_alignClasses defined)
  190. // ┌──────┬────────────────────────────────────────┬────────────────────────────────────────┐
  191. // │Align │Internal form │Data │
  192. // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
  193. // │none │<wrapper> │<figure /> │
  194. // │ │ <figure /> │ │
  195. // │ │</wrapper> │ │
  196. // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
  197. // │left │<wrapper class=”left”> │<figure class=”left” /> │
  198. // │ │ <figure /> │ │
  199. // │ │</wrapper> │ │
  200. // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
  201. // │center│<wrapper class=”center”> │<div class=”center”> │
  202. // │ │ <figure /> │ <figure /> │
  203. // │ │</wrapper> │</p> │
  204. // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
  205. // │right │<wrapper class=”right”> │<figure class=”right” /> │
  206. // │ │ <figure /> │ │
  207. // │ │</wrapper> │ │
  208. // └──────┴────────────────────────────────────────┴────────────────────────────────────────┘
  209. //
  210. // @param {CKEDITOR.editor}
  211. // @returns {Object}
  212. function widgetDef( editor ) {
  213. var alignClasses = editor.config.image2_chamilo_alignClasses,
  214. captionedClass = editor.config.image2_chamilo_captionedClass;
  215. function deflate() {
  216. if ( this.deflated )
  217. return;
  218. // Remember whether widget was focused before destroyed.
  219. if ( editor.widgets.focused == this.widget )
  220. this.focused = true;
  221. editor.widgets.destroy( this.widget );
  222. // Mark widget was destroyed.
  223. this.deflated = true;
  224. }
  225. function inflate() {
  226. var editable = editor.editable(),
  227. doc = editor.document;
  228. // Create a new widget. This widget will be either captioned
  229. // non-captioned, block or inline according to what is the
  230. // new state of the widget.
  231. if ( this.deflated ) {
  232. this.widget = editor.widgets.initOn( this.element, 'image', this.widget.data );
  233. // Once widget was re-created, it may become an inline element without
  234. // block wrapper (i.e. when unaligned, end not captioned). Let's do some
  235. // sort of autoparagraphing here (#10853).
  236. if ( this.widget.inline && !( new CKEDITOR.dom.elementPath( this.widget.wrapper, editable ).block ) ) {
  237. var block = doc.createElement( editor.activeEnterMode == CKEDITOR.ENTER_P ? 'p' : 'div' );
  238. block.replace( this.widget.wrapper );
  239. this.widget.wrapper.move( block );
  240. }
  241. // The focus must be transferred from the old one (destroyed)
  242. // to the new one (just created).
  243. if ( this.focused ) {
  244. this.widget.focus();
  245. delete this.focused;
  246. }
  247. delete this.deflated;
  248. }
  249. // If now widget was destroyed just update wrapper's alignment.
  250. // According to the new state.
  251. else {
  252. setWrapperAlign( this.widget, alignClasses );
  253. }
  254. }
  255. return {
  256. allowedContent: getWidgetAllowedContent( editor ),
  257. requiredContent: 'img[src,alt]',
  258. features: getWidgetFeatures( editor ),
  259. styleableElements: 'img figure',
  260. // This widget converts style-driven dimensions to attributes.
  261. contentTransformations: [
  262. [ 'img[width]: sizeToAttribute' ]
  263. ],
  264. // This widget has an editable caption.
  265. editables: {
  266. caption: {
  267. selector: 'figcaption',
  268. allowedContent: 'br em strong sub sup u s; a[!href,target]'
  269. }
  270. },
  271. parts: {
  272. image: 'img',
  273. caption: 'figcaption'
  274. // parts#link defined in widget#init
  275. },
  276. // The name of this widget's dialog.
  277. dialog: 'image2_chamilo',
  278. // Template of the widget: plain image.
  279. template: template,
  280. data: function() {
  281. var features = this.features;
  282. // Image can't be captioned when figcaption is disallowed (#11004).
  283. if ( this.data.hasCaption && !editor.filter.checkFeature( features.caption ) )
  284. this.data.hasCaption = false;
  285. // Image can't be aligned when floating is disallowed (#11004).
  286. if ( this.data.align != 'none' && !editor.filter.checkFeature( features.align ) )
  287. this.data.align = 'none';
  288. // Convert the internal form of the widget from the old state to the new one.
  289. this.shiftState( {
  290. widget: this,
  291. element: this.element,
  292. oldData: this.oldData,
  293. newData: this.data,
  294. deflate: deflate,
  295. inflate: inflate
  296. } );
  297. // Update widget.parts.link since it will not auto-update unless widget
  298. // is destroyed and re-inited.
  299. if ( !this.data.link ) {
  300. if ( this.parts.link )
  301. delete this.parts.link;
  302. } else {
  303. if ( !this.parts.link )
  304. this.parts.link = this.parts.image.getParent();
  305. }
  306. this.parts.image.setAttributes( {
  307. src: this.data.src,
  308. class: this.data.isResponsive ? 'img-responsive' : '',
  309. // This internal is required by the editor.
  310. 'data-cke-saved-src': this.data.src,
  311. alt: this.data.alt
  312. } );
  313. // If shifting non-captioned -> captioned, remove classes
  314. // related to styles from <img/>.
  315. if ( this.oldData && !this.oldData.hasCaption && this.data.hasCaption ) {
  316. for ( var c in this.data.classes )
  317. this.parts.image.removeClass( c );
  318. }
  319. // Set dimensions of the image according to gathered data.
  320. // Do it only when the attributes are allowed (#11004).
  321. if ( editor.filter.checkFeature( features.dimension ) )
  322. setDimensions( this );
  323. // Cache current data.
  324. this.oldData = CKEDITOR.tools.extend( {}, this.data );
  325. },
  326. init: function() {
  327. var helpers = CKEDITOR.plugins.image2_chamilo,
  328. image = this.parts.image,
  329. data = {
  330. hasCaption: !!this.parts.caption,
  331. src: image.getAttribute( 'src' ),
  332. alt: image.getAttribute( 'alt' ) || '',
  333. width: image.getAttribute( 'width' ) || '',
  334. height: image.getAttribute( 'height' ) || '',
  335. isResponsive: !!image.$.className.match(/img-responsive/i),
  336. // Lock ratio is on by default (#10833).
  337. lock: this.ready ? helpers.checkHasNaturalRatio( image ) : true
  338. };
  339. // If we used 'a' in widget#parts definition, it could happen that
  340. // selected element is a child of widget.parts#caption. Since there's no clever
  341. // way to solve it with CSS selectors, it's done like that. (#11783).
  342. var link = image.getAscendant( 'a' );
  343. if ( link && this.wrapper.contains( link ) )
  344. this.parts.link = link;
  345. // Depending on configuration, read style/class from element and
  346. // then remove it. Removed style/class will be set on wrapper in #data listener.
  347. // Note: Center alignment is detected during upcast, so only left/right cases
  348. // are checked below.
  349. if ( !data.align ) {
  350. var alignElement = data.hasCaption ? this.element : image;
  351. // Read the initial left/right alignment from the class set on element.
  352. if ( alignClasses ) {
  353. if ( alignElement.hasClass( alignClasses[ 0 ] ) ) {
  354. data.align = 'left';
  355. } else if ( alignElement.hasClass( alignClasses[ 2 ] ) ) {
  356. data.align = 'right';
  357. } else if ( alignElement.hasClass( alignClasses[ 3 ] ) ) {
  358. data.align = 'baseline';
  359. } else if ( alignElement.hasClass( alignClasses[ 4 ] ) ) {
  360. data.align = 'top';
  361. } else if ( alignElement.hasClass( alignClasses[ 5 ] ) ) {
  362. data.align = 'bottom';
  363. } else if ( alignElement.hasClass( alignClasses[ 6 ] ) ) {
  364. data.align = 'middle';
  365. } else if ( alignElement.hasClass( alignClasses[ 7 ] ) ) {
  366. data.align = 'super';
  367. } else if ( alignElement.hasClass( alignClasses[ 8 ] ) ) {
  368. data.align = 'sub';
  369. } else if ( alignElement.hasClass( alignClasses[ 9 ] ) ) {
  370. data.align = 'text-top';
  371. } else if ( alignElement.hasClass( alignClasses[ 10 ] ) ) {
  372. data.align = 'text-bottom';
  373. }
  374. if ( data.align ) {
  375. alignElement.removeClass( alignClasses[ alignmentsObj[ data.align ] ] );
  376. } else {
  377. data.align = 'none';
  378. }
  379. }
  380. // Read initial float style from figure/image and then remove it.
  381. else {
  382. data.align = alignElement.getStyle( 'float' );
  383. if ( data.align !== 'left' && data.align !== 'right' ) {
  384. data.align = alignElement.getStyle('vertical-align');
  385. } else {
  386. data.align = 'none';
  387. }
  388. alignElement.removeStyle( 'float' );
  389. alignElement.removeStyle( 'vertical-align' );
  390. }
  391. }
  392. // Update data.link object with attributes if the link has been discovered.
  393. if ( editor.plugins.link && this.parts.link ) {
  394. data.link = helpers.getLinkAttributesParser()( editor, this.parts.link );
  395. // Get rid of cke_widget_* classes in data. Otherwise
  396. // they might appear in link dialog.
  397. var advanced = data.link.advanced;
  398. if ( advanced && advanced.advCSSClasses ) {
  399. advanced.advCSSClasses = CKEDITOR.tools.trim( advanced.advCSSClasses.replace( /cke_\S+/, '' ) );
  400. }
  401. }
  402. // Get rid of extra vertical space when there's no caption.
  403. // It will improve the look of the resizer.
  404. this.wrapper[ ( data.hasCaption ? 'remove' : 'add' ) + 'Class' ]( 'cke_image_nocaption' );
  405. this.setData( data );
  406. // Setup dynamic image resizing with mouse.
  407. // Don't initialize resizer when dimensions are disallowed (#11004).
  408. if ( editor.filter.checkFeature( this.features.dimension ) && editor.config.image2_chamilo_disableResizer !== true )
  409. setupResizer( this );
  410. this.shiftState = helpers.stateShifter( this.editor );
  411. // Add widget editing option to its context menu.
  412. this.on( 'contextMenu', function( evt ) {
  413. evt.data.image = CKEDITOR.TRISTATE_OFF;
  414. // Integrate context menu items for link.
  415. // Note that widget may be wrapped in a link, which
  416. // does not belong to that widget (#11814).
  417. if ( this.parts.link || this.wrapper.getAscendant( 'a' ) )
  418. evt.data.link = evt.data.unlink = CKEDITOR.TRISTATE_OFF;
  419. } );
  420. // Pass the reference to this widget to the dialog.
  421. this.on( 'dialog', function( evt ) {
  422. evt.data.widget = this;
  423. }, this );
  424. },
  425. // Overrides default method to handle internal mutability of Image2Chamilo.
  426. // @see CKEDITOR.plugins.widget#addClass
  427. addClass: function( className ) {
  428. getStyleableElement( this ).addClass( className );
  429. },
  430. // Overrides default method to handle internal mutability of Image2Chamilo.
  431. // @see CKEDITOR.plugins.widget#hasClass
  432. hasClass: function( className ) {
  433. return getStyleableElement( this ).hasClass( className );
  434. },
  435. // Overrides default method to handle internal mutability of Image2Chamilo.
  436. // @see CKEDITOR.plugins.widget#removeClass
  437. removeClass: function( className ) {
  438. getStyleableElement( this ).removeClass( className );
  439. },
  440. // Overrides default method to handle internal mutability of Image2Chamilo.
  441. // @see CKEDITOR.plugins.widget#getClasses
  442. getClasses: ( function() {
  443. var classRegex = new RegExp( '^(' + [].concat( captionedClass, alignClasses ).join( '|' ) + ')$' );
  444. return function() {
  445. var classes = this.repository.parseElementClasses( getStyleableElement( this ).getAttribute( 'class' ) );
  446. // Neither config.image2_chamilo_captionedClass nor config.image2_chamilo_alignClasses
  447. // do not belong to style classes.
  448. for ( var c in classes ) {
  449. if ( classRegex.test( c ) )
  450. delete classes[ c ];
  451. }
  452. return classes;
  453. };
  454. } )(),
  455. upcast: upcastWidgetElement( editor ),
  456. downcast: downcastWidgetElement( editor ),
  457. getLabel: function() {
  458. var label = ( this.data.alt || '' ) + ' ' + this.pathName;
  459. return this.editor.lang.widget.label.replace( /%1/, label );
  460. }
  461. };
  462. }
  463. /**
  464. * A set of Enhanced Image (image2_chamilo) plugin helpers.
  465. *
  466. * @class
  467. * @singleton
  468. */
  469. CKEDITOR.plugins.image2_chamilo = {
  470. stateShifter: function( editor ) {
  471. // Tag name used for centering non-captioned widgets.
  472. var doc = editor.document,
  473. alignClasses = editor.config.image2_chamilo_alignClasses,
  474. captionedClass = editor.config.image2_chamilo_captionedClass,
  475. editable = editor.editable(),
  476. // The order that stateActions get executed. It matters!
  477. shiftables = [ 'hasCaption', 'align', 'link' ];
  478. // Atomic procedures, one per state variable.
  479. var stateActions = {
  480. align: function( shift, oldValue, newValue ) {
  481. var el = shift.element;
  482. // Alignment changed.
  483. if ( shift.changed.align ) {
  484. // No caption in the new state.
  485. if ( !shift.newData.hasCaption ) {
  486. // Changed to "center" (non-captioned).
  487. if ( newValue == 'center' ) {
  488. shift.deflate();
  489. shift.element = wrapInCentering( editor, el );
  490. }
  491. // Changed to "non-center" from "center" while caption removed.
  492. if ( !shift.changed.hasCaption && oldValue == 'center' && newValue != 'center' ) {
  493. shift.deflate();
  494. shift.element = unwrapFromCentering( el );
  495. }
  496. }
  497. }
  498. // Alignment remains and "center" removed caption.
  499. else if ( newValue == 'center' && shift.changed.hasCaption && !shift.newData.hasCaption ) {
  500. shift.deflate();
  501. shift.element = wrapInCentering( editor, el );
  502. }
  503. // Finally set display for figure.
  504. if ( !alignClasses && el.is( 'figure' ) ) {
  505. if ( newValue == 'center' )
  506. el.setStyle( 'display', 'inline-block' );
  507. else
  508. el.removeStyle( 'display' );
  509. }
  510. },
  511. hasCaption: function( shift, oldValue, newValue ) {
  512. // This action is for real state change only.
  513. if ( !shift.changed.hasCaption )
  514. return;
  515. // Get <img/> or <a><img/></a> from widget. Note that widget element might itself
  516. // be what we're looking for. Also element can be <p style="text-align:center"><a>...</a></p>.
  517. var imageOrLink;
  518. if ( shift.element.is( { img: 1, a: 1 } ) )
  519. imageOrLink = shift.element;
  520. else
  521. imageOrLink = shift.element.findOne( 'a,img' );
  522. // Switching hasCaption always destroys the widget.
  523. shift.deflate();
  524. // There was no caption, but the caption is to be added.
  525. if ( newValue ) {
  526. // Create new <figure> from widget template.
  527. var figure = CKEDITOR.dom.element.createFromHtml( templateBlock.output( {
  528. captionedClass: captionedClass,
  529. captionPlaceholder: editor.lang.image2_chamilo.captionPlaceholder
  530. } ), doc );
  531. // Replace element with <figure>.
  532. replaceSafely( figure, shift.element );
  533. // Use old <img/> or <a><img/></a> instead of the one from the template,
  534. // so we won't lose additional attributes.
  535. imageOrLink.replace( figure.findOne( 'img' ) );
  536. // Update widget's element.
  537. shift.element = figure;
  538. }
  539. // The caption was present, but now it's to be removed.
  540. else {
  541. // Unwrap <img/> or <a><img/></a> from figure.
  542. imageOrLink.replace( shift.element );
  543. // Update widget's element.
  544. shift.element = imageOrLink;
  545. }
  546. },
  547. link: function( shift, oldValue, newValue ) {
  548. if ( shift.changed.link ) {
  549. var img = shift.element.is( 'img' ) ?
  550. shift.element : shift.element.findOne( 'img' ),
  551. link = shift.element.is( 'a' ) ?
  552. shift.element : shift.element.findOne( 'a' ),
  553. // Why deflate:
  554. // If element is <img/>, it will be wrapped into <a>,
  555. // which becomes a new widget.element.
  556. // If element is <a><img/></a>, it will be unlinked
  557. // so <img/> becomes a new widget.element.
  558. needsDeflate = ( shift.element.is( 'a' ) && !newValue ) || ( shift.element.is( 'img' ) && newValue ),
  559. newEl;
  560. if ( needsDeflate )
  561. shift.deflate();
  562. // If unlinked the image, returned element is <img>.
  563. if ( !newValue )
  564. newEl = unwrapFromLink( link );
  565. else {
  566. // If linked the image, returned element is <a>.
  567. if ( !oldValue )
  568. newEl = wrapInLink( img, shift.newData.link );
  569. // Set and remove all attributes associated with this state.
  570. var attributes = CKEDITOR.plugins.image2_chamilo.getLinkAttributesGetter()( editor, newValue );
  571. if ( !CKEDITOR.tools.isEmpty( attributes.set ) )
  572. ( newEl || link ).setAttributes( attributes.set );
  573. if ( attributes.removed.length )
  574. ( newEl || link ).removeAttributes( attributes.removed );
  575. }
  576. if ( needsDeflate )
  577. shift.element = newEl;
  578. }
  579. },
  580. isResponsive: function (shift, oldValue, newValue) {
  581. if (!shift.changed.isResponsive) {
  582. return;
  583. }
  584. var image = shift.element.find('img');
  585. }
  586. };
  587. function wrapInCentering( editor, element ) {
  588. var attribsAndStyles = {};
  589. if ( alignClasses )
  590. attribsAndStyles.attributes = { 'class': alignClasses[ 1 ] };
  591. else
  592. attribsAndStyles.styles = { 'text-align': 'center' };
  593. // There's no gentle way to center inline element with CSS, so create p/div
  594. // that wraps widget contents and does the trick either with style or class.
  595. var center = doc.createElement(
  596. editor.activeEnterMode == CKEDITOR.ENTER_P ? 'p' : 'div', attribsAndStyles );
  597. // Replace element with centering wrapper.
  598. replaceSafely( center, element );
  599. element.move( center );
  600. return center;
  601. }
  602. function unwrapFromCentering( element ) {
  603. var imageOrLink = element.findOne( 'a,img' );
  604. imageOrLink.replace( element );
  605. return imageOrLink;
  606. }
  607. // Wraps <img/> -> <a><img/></a>.
  608. // Returns reference to <a>.
  609. //
  610. // @param {CKEDITOR.dom.element} img
  611. // @param {Object} linkData
  612. // @returns {CKEDITOR.dom.element}
  613. function wrapInLink( img, linkData ) {
  614. var link = doc.createElement( 'a', {
  615. attributes: {
  616. href: linkData.url
  617. }
  618. } );
  619. link.replace( img );
  620. img.move( link );
  621. return link;
  622. }
  623. // De-wraps <a><img/></a> -> <img/>.
  624. // Returns the reference to <img/>
  625. //
  626. // @param {CKEDITOR.dom.element} link
  627. // @returns {CKEDITOR.dom.element}
  628. function unwrapFromLink( link ) {
  629. var img = link.findOne( 'img' );
  630. img.replace( link );
  631. return img;
  632. }
  633. function replaceSafely( replacing, replaced ) {
  634. if ( replaced.getParent() ) {
  635. var range = editor.createRange();
  636. range.moveToPosition( replaced, CKEDITOR.POSITION_BEFORE_START );
  637. // Remove old element. Do it before insertion to avoid a case when
  638. // element is moved from 'replaced' element before it, what creates
  639. // a tricky case which insertElementIntorRange does not handle.
  640. replaced.remove();
  641. editable.insertElementIntoRange( replacing, range );
  642. }
  643. else {
  644. replacing.replace( replaced );
  645. }
  646. }
  647. return function( shift ) {
  648. var name, i;
  649. shift.changed = {};
  650. for ( i = 0; i < shiftables.length; i++ ) {
  651. name = shiftables[ i ];
  652. shift.changed[ name ] = shift.oldData ?
  653. shift.oldData[ name ] !== shift.newData[ name ] : false;
  654. }
  655. // Iterate over possible state variables.
  656. for ( i = 0; i < shiftables.length; i++ ) {
  657. name = shiftables[ i ];
  658. stateActions[ name ]( shift,
  659. shift.oldData ? shift.oldData[ name ] : null,
  660. shift.newData[ name ] );
  661. }
  662. shift.inflate();
  663. };
  664. },
  665. /**
  666. * Checks whether the current image ratio matches the natural one
  667. * by comparing dimensions.
  668. *
  669. * @param {CKEDITOR.dom.element} image
  670. * @returns {Boolean}
  671. */
  672. checkHasNaturalRatio: function( image ) {
  673. var $ = image.$,
  674. natural = this.getNatural( image );
  675. // The reason for two alternative comparisons is that the rounding can come from
  676. // both dimensions, e.g. there are two cases:
  677. // 1. height is computed as a rounded relation of the real height and the value of width,
  678. // 2. width is computed as a rounded relation of the real width and the value of heigh.
  679. return Math.round( $.clientWidth / natural.width * natural.height ) == $.clientHeight ||
  680. Math.round( $.clientHeight / natural.height * natural.width ) == $.clientWidth;
  681. },
  682. /**
  683. * Returns natural dimensions of the image. For modern browsers
  684. * it uses natural(Width|Height). For old ones (IE8) it creates
  685. * a new image and reads the dimensions.
  686. *
  687. * @param {CKEDITOR.dom.element} image
  688. * @returns {Object}
  689. */
  690. getNatural: function( image ) {
  691. var dimensions;
  692. if ( image.$.naturalWidth ) {
  693. dimensions = {
  694. width: image.$.naturalWidth,
  695. height: image.$.naturalHeight
  696. };
  697. } else {
  698. var img = new Image();
  699. img.src = image.getAttribute( 'src' );
  700. dimensions = {
  701. width: img.width,
  702. height: img.height
  703. };
  704. }
  705. return dimensions;
  706. },
  707. /**
  708. * Returns an attribute getter function. Default getter comes from the Link plugin
  709. * and is documented by {@link CKEDITOR.plugins.link#getLinkAttributes}.
  710. *
  711. * **Note:** It is possible to override this method and use a custom getter e.g.
  712. * in the absence of the Link plugin.
  713. *
  714. * **Note:** If a custom getter is used, a data model format it produces
  715. * must be compatible with {@link CKEDITOR.plugins.link#getLinkAttributes}.
  716. *
  717. * **Note:** A custom getter must understand the data model format produced by
  718. * {@link #getLinkAttributesParser} to work correctly.
  719. *
  720. * @returns {Function} A function that gets (composes) link attributes.
  721. * @since 4.5.5
  722. */
  723. getLinkAttributesGetter: function() {
  724. // #13885
  725. return CKEDITOR.plugins.link.getLinkAttributes;
  726. },
  727. /**
  728. * Returns an attribute parser function. Default parser comes from the Link plugin
  729. * and is documented by {@link CKEDITOR.plugins.link#parseLinkAttributes}.
  730. *
  731. * **Note:** It is possible to override this method and use a custom parser e.g.
  732. * in the absence of the Link plugin.
  733. *
  734. * **Note:** If a custom parser is used, a data model format produced by the parser
  735. * must be compatible with {@link #getLinkAttributesGetter}.
  736. *
  737. * **Note:** If a custom parser is used, it should be compatible with the
  738. * {@link CKEDITOR.plugins.link#parseLinkAttributes} data model format. Otherwise the
  739. * Link plugin dialog may not be populated correctly with parsed data. However
  740. * as long as Enhanced Image is **not** used with the Link plugin dialog, any custom data model
  741. * will work, being stored as an internal property of Enhanced Image widget's data only.
  742. *
  743. * @returns {Function} A function that parses attributes.
  744. * @since 4.5.5
  745. */
  746. getLinkAttributesParser: function() {
  747. // #13885
  748. return CKEDITOR.plugins.link.parseLinkAttributes;
  749. }
  750. };
  751. function setWrapperAlign( widget, alignClasses ) {
  752. var wrapper = widget.wrapper,
  753. align = widget.data.align,
  754. hasCaption = widget.data.hasCaption;
  755. if ( alignClasses ) {
  756. // Remove all align classes first.
  757. for ( var i = 11; i--; )
  758. wrapper.removeClass( alignClasses[ i ] );
  759. if ( align == 'center' ) {
  760. // Avoid touching non-captioned, centered widgets because
  761. // they have the class set on the element instead of wrapper:
  762. //
  763. // <div class="cke_widget_wrapper">
  764. // <p class="center-class">
  765. // <img />
  766. // </p>
  767. // </div>
  768. if ( hasCaption ) {
  769. wrapper.addClass( alignClasses[ 1 ] );
  770. }
  771. } else if ( align != 'none' ) {
  772. wrapper.addClass( alignClasses[ alignmentsObj[ align ] ] );
  773. }
  774. } else {
  775. if ( align == 'center' ) {
  776. if ( hasCaption )
  777. wrapper.setStyle( 'text-align', 'center' );
  778. else
  779. wrapper.removeStyle( 'text-align' );
  780. wrapper.removeStyle( 'float' );
  781. }
  782. else {
  783. if ( align == 'none' )
  784. wrapper.removeStyle( 'float' );
  785. else if (align == 'left' || align == 'right')
  786. wrapper.setStyle( 'float', align );
  787. else
  788. wrapper.setStyle( 'vertical-align', align );
  789. wrapper.removeStyle( 'text-align' );
  790. }
  791. }
  792. }
  793. // Returns a function that creates widgets from all <img> and
  794. // <figure class="{config.image2_chamilo_captionedClass}"> elements.
  795. //
  796. // @param {CKEDITOR.editor} editor
  797. // @returns {Function}
  798. function upcastWidgetElement( editor ) {
  799. var isCenterWrapper = centerWrapperChecker( editor ),
  800. captionedClass = editor.config.image2_chamilo_captionedClass;
  801. // @param {CKEDITOR.htmlParser.element} el
  802. // @param {Object} data
  803. return function( el, data ) {
  804. var dimensions = { width: 1, height: 1 },
  805. name = el.name,
  806. image;
  807. // #11110 Don't initialize on pasted fake objects.
  808. if ( el.attributes[ 'data-cke-realelement' ] )
  809. return;
  810. // If a center wrapper is found, there are 3 possible cases:
  811. //
  812. // 1. <div style="text-align:center"><figure>...</figure></div>.
  813. // In this case centering is done with a class set on widget.wrapper.
  814. // Simply replace centering wrapper with figure (it's no longer necessary).
  815. //
  816. // 2. <p style="text-align:center"><img/></p>.
  817. // Nothing to do here: <p> remains for styling purposes.
  818. //
  819. // 3. <div style="text-align:center"><img/></div>.
  820. // Nothing to do here (2.) but that case is only possible in enterMode different
  821. // than ENTER_P.
  822. if ( isCenterWrapper( el ) ) {
  823. if ( name == 'div' ) {
  824. var figure = el.getFirst( 'figure' );
  825. // Case #1.
  826. if ( figure ) {
  827. el.replaceWith( figure );
  828. el = figure;
  829. }
  830. }
  831. // Cases #2 and #3 (handled transparently)
  832. // If there's a centering wrapper, save it in data.
  833. data.align = 'center';
  834. // Image can be wrapped in link <a><img/></a>.
  835. image = el.getFirst( 'img' ) || el.getFirst( 'a' ).getFirst( 'img' );
  836. }
  837. // No center wrapper has been found.
  838. else if ( name == 'figure' && el.hasClass( captionedClass ) ) {
  839. image = el.getFirst( 'img' ) || el.getFirst( 'a' ).getFirst( 'img' );
  840. // Upcast linked image like <a><img/></a>.
  841. } else if ( isLinkedOrStandaloneImage( el ) ) {
  842. image = el.name == 'a' ? el.children[ 0 ] : el;
  843. }
  844. if ( !image )
  845. return;
  846. // If there's an image, then cool, we got a widget.
  847. // Now just remove dimension attributes expressed with %.
  848. for ( var d in dimensions ) {
  849. var dimension = image.attributes[ d ];
  850. if ( dimension && dimension.match( regexPercent ) )
  851. delete image.attributes[ d ];
  852. }
  853. return el;
  854. };
  855. }
  856. // Returns a function which transforms the widget to the external format
  857. // according to the current configuration.
  858. //
  859. // @param {CKEDITOR.editor}
  860. function downcastWidgetElement( editor ) {
  861. var alignClasses = editor.config.image2_chamilo_alignClasses;
  862. // @param {CKEDITOR.htmlParser.element} el
  863. return function( el ) {
  864. // In case of <a><img/></a>, <img/> is the element to hold
  865. // inline styles or classes (image2_chamilo_alignClasses).
  866. var attrsHolder = el.name == 'a' ? el.getFirst() : el,
  867. attrs = attrsHolder.attributes,
  868. align = this.data.align;
  869. // De-wrap the image from resize handle wrapper.
  870. // Only block widgets have one.
  871. if ( !this.inline ) {
  872. var resizeWrapper = el.getFirst( 'span' );
  873. if ( resizeWrapper )
  874. resizeWrapper.replaceWith( resizeWrapper.getFirst( { img: 1, a: 1 } ) );
  875. }
  876. if ( align && align != 'none' ) {
  877. var styles = CKEDITOR.tools.parseCssText( attrs.style || '' );
  878. var allowedAllignments = {
  879. 'left': 1,
  880. 'right': 1,
  881. 'baseline': 1,
  882. 'top': 1,
  883. 'bottom': 1,
  884. 'middle': 1,
  885. 'super': 1,
  886. 'sub': 1,
  887. 'text-top': 1,
  888. 'text-bottom': 1
  889. };
  890. // When the widget is captioned (<figure>) and internally centering is done
  891. // with widget's wrapper style/class, in the external data representation,
  892. // <figure> must be wrapped with an element holding an style/class:
  893. //
  894. // <div style="text-align:center">
  895. // <figure class="image" style="display:inline-block">...</figure>
  896. // </div>
  897. // or
  898. // <div class="some-center-class">
  899. // <figure class="image">...</figure>
  900. // </div>
  901. //
  902. if ( align == 'center' && el.name == 'figure' ) {
  903. el = el.wrapWith( new CKEDITOR.htmlParser.element( 'div',
  904. alignClasses ? { 'class': alignClasses[ 1 ] } : { style: 'text-align:center' } ) );
  905. }
  906. // If left/right, add float style to the downcasted element.
  907. else if ( align in allowedAllignments ) {
  908. if ( alignClasses )
  909. attrsHolder.addClass( alignClasses[ alignmentsObj[ align ] ] );
  910. else if (align == 'left' || align == 'right')
  911. styles[ 'float' ] = align;
  912. else
  913. styles['vertical-align'] = align;
  914. }
  915. // Update element styles.
  916. if ( !alignClasses && !CKEDITOR.tools.isEmpty( styles ) )
  917. attrs.style = CKEDITOR.tools.writeCssText( styles );
  918. }
  919. return el;
  920. };
  921. }
  922. // Returns a function that checks if an element is a centering wrapper.
  923. //
  924. // @param {CKEDITOR.editor} editor
  925. // @returns {Function}
  926. function centerWrapperChecker( editor ) {
  927. var captionedClass = editor.config.image2_chamilo_captionedClass,
  928. alignClasses = editor.config.image2_chamilo_alignClasses,
  929. validChildren = { figure: 1, a: 1, img: 1 };
  930. return function( el ) {
  931. // Wrapper must be either <div> or <p>.
  932. if ( !( el.name in { div: 1, p: 1 } ) )
  933. return false;
  934. var children = el.children;
  935. // Centering wrapper can have only one child.
  936. if ( children.length !== 1 )
  937. return false;
  938. var child = children[ 0 ];
  939. // Only <figure> or <img /> can be first (only) child of centering wrapper,
  940. // regardless of its type.
  941. if ( !( child.name in validChildren ) )
  942. return false;
  943. // If centering wrapper is <p>, only <img /> can be the child.
  944. // <p style="text-align:center"><img /></p>
  945. if ( el.name == 'p' ) {
  946. if ( !isLinkedOrStandaloneImage( child ) )
  947. return false;
  948. }
  949. // Centering <div> can hold <img/> or <figure>, depending on enterMode.
  950. else {
  951. // If a <figure> is the first (only) child, it must have a class.
  952. // <div style="text-align:center"><figure>...</figure><div>
  953. if ( child.name == 'figure' ) {
  954. if ( !child.hasClass( captionedClass ) )
  955. return false;
  956. } else {
  957. // Centering <div> can hold <img/> or <a><img/></a> only when enterMode
  958. // is ENTER_(BR|DIV).
  959. // <div style="text-align:center"><img /></div>
  960. // <div style="text-align:center"><a><img /></a></div>
  961. if ( editor.enterMode == CKEDITOR.ENTER_P )
  962. return false;
  963. // Regardless of enterMode, a child which is not <figure> must be
  964. // either <img/> or <a><img/></a>.
  965. if ( !isLinkedOrStandaloneImage( child ) )
  966. return false;
  967. }
  968. }
  969. // Centering wrapper got to be... centering. If image2_chamilo_alignClasses are defined,
  970. // check for centering class. Otherwise, check the style.
  971. if ( alignClasses ? el.hasClass( alignClasses[ 1 ] ) :
  972. CKEDITOR.tools.parseCssText( el.attributes.style || '', true )[ 'text-align' ] == 'center' )
  973. return true;
  974. return false;
  975. };
  976. }
  977. // Checks whether element is <img/> or <a><img/></a>.
  978. //
  979. // @param {CKEDITOR.htmlParser.element}
  980. function isLinkedOrStandaloneImage( el ) {
  981. if ( el.name == 'img' )
  982. return true;
  983. else if ( el.name == 'a' )
  984. return el.children.length == 1 && el.getFirst( 'img' );
  985. return false;
  986. }
  987. // Sets width and height of the widget image according to current widget data.
  988. //
  989. // @param {CKEDITOR.plugins.widget} widget
  990. function setDimensions( widget ) {
  991. var data = widget.data,
  992. dimensions = { width: data.width, height: data.height },
  993. image = widget.parts.image;
  994. for ( var d in dimensions ) {
  995. if ( dimensions[ d ] )
  996. image.setAttribute( d, dimensions[ d ] );
  997. else
  998. image.removeAttribute( d );
  999. }
  1000. }
  1001. // Defines all features related to drag-driven image resizing.
  1002. //
  1003. // @param {CKEDITOR.plugins.widget} widget
  1004. function setupResizer( widget ) {
  1005. var editor = widget.editor,
  1006. editable = editor.editable(),
  1007. doc = editor.document,
  1008. // Store the resizer in a widget for testing (#11004).
  1009. resizer = widget.resizer = doc.createElement( 'span' );
  1010. resizer.addClass( 'cke_image_resizer' );
  1011. resizer.setAttribute( 'title', editor.lang.image2_chamilo.resizer );
  1012. resizer.append( new CKEDITOR.dom.text( '\u200b', doc ) );
  1013. // Inline widgets don't need a resizer wrapper as an image spans the entire widget.
  1014. if ( !widget.inline ) {
  1015. var imageOrLink = widget.parts.link || widget.parts.image,
  1016. oldResizeWrapper = imageOrLink.getParent(),
  1017. resizeWrapper = doc.createElement( 'span' );
  1018. resizeWrapper.addClass( 'cke_image_resizer_wrapper' );
  1019. resizeWrapper.append( imageOrLink );
  1020. resizeWrapper.append( resizer );
  1021. widget.element.append( resizeWrapper, true );
  1022. // Remove the old wrapper which could came from e.g. pasted HTML
  1023. // and which could be corrupted (e.g. resizer span has been lost).
  1024. if ( oldResizeWrapper.is( 'span' ) )
  1025. oldResizeWrapper.remove();
  1026. } else {
  1027. widget.wrapper.append( resizer );
  1028. }
  1029. // Calculate values of size variables and mouse offsets.
  1030. resizer.on( 'mousedown', function( evt ) {
  1031. var image = widget.parts.image,
  1032. // "factor" can be either 1 or -1. I.e.: For right-aligned images, we need to
  1033. // subtract the difference to get proper width, etc. Without "factor",
  1034. // resizer starts working the opposite way.
  1035. factor = widget.data.align == 'right' ? -1 : 1,
  1036. // The x-coordinate of the mouse relative to the screen
  1037. // when button gets pressed.
  1038. startX = evt.data.$.screenX,
  1039. startY = evt.data.$.screenY,
  1040. // The initial dimensions and aspect ratio of the image.
  1041. startWidth = image.$.clientWidth,
  1042. startHeight = image.$.clientHeight,
  1043. ratio = startWidth / startHeight,
  1044. listeners = [],
  1045. // A class applied to editable during resizing.
  1046. cursorClass = 'cke_image_s' + ( !~factor ? 'w' : 'e' ),
  1047. nativeEvt, newWidth, newHeight, updateData,
  1048. moveDiffX, moveDiffY, moveRatio;
  1049. // Save the undo snapshot first: before resizing.
  1050. editor.fire( 'saveSnapshot' );
  1051. // Mousemove listeners are removed on mouseup.
  1052. attachToDocuments( 'mousemove', onMouseMove, listeners );
  1053. // Clean up the mousemove listener. Update widget data if valid.
  1054. attachToDocuments( 'mouseup', onMouseUp, listeners );
  1055. // The entire editable will have the special cursor while resizing goes on.
  1056. editable.addClass( cursorClass );
  1057. // This is to always keep the resizer element visible while resizing.
  1058. resizer.addClass( 'cke_image_resizing' );
  1059. // Attaches an event to a global document if inline editor.
  1060. // Additionally, if classic (`iframe`-based) editor, also attaches the same event to `iframe`'s document.
  1061. function attachToDocuments( name, callback, collection ) {
  1062. var globalDoc = CKEDITOR.document,
  1063. listeners = [];
  1064. if ( !doc.equals( globalDoc ) )
  1065. listeners.push( globalDoc.on( name, callback ) );
  1066. listeners.push( doc.on( name, callback ) );
  1067. if ( collection ) {
  1068. for ( var i = listeners.length; i--; )
  1069. collection.push( listeners.pop() );
  1070. }
  1071. }
  1072. // Calculate with first, and then adjust height, preserving ratio.
  1073. function adjustToX() {
  1074. newWidth = startWidth + factor * moveDiffX;
  1075. newHeight = Math.round( newWidth / ratio );
  1076. }
  1077. // Calculate height first, and then adjust width, preserving ratio.
  1078. function adjustToY() {
  1079. newHeight = startHeight - moveDiffY;
  1080. newWidth = Math.round( newHeight * ratio );
  1081. }
  1082. // This is how variables refer to the geometry.
  1083. // Note: x corresponds to moveOffset, this is the position of mouse
  1084. // Note: o corresponds to [startX, startY].
  1085. //
  1086. // +--------------+--------------+
  1087. // | | |
  1088. // | I | II |
  1089. // | | |
  1090. // +------------- o -------------+ _ _ _
  1091. // | | | ^
  1092. // | VI | III | | moveDiffY
  1093. // | | x _ _ _ _ _ v
  1094. // +--------------+---------|----+
  1095. // | |
  1096. // <------->
  1097. // moveDiffX
  1098. function onMouseMove( evt ) {
  1099. nativeEvt = evt.data.$;
  1100. // This is how far the mouse is from the point the button was pressed.
  1101. moveDiffX = nativeEvt.screenX - startX;
  1102. moveDiffY = startY - nativeEvt.screenY;
  1103. // This is the aspect ratio of the move difference.
  1104. moveRatio = Math.abs( moveDiffX / moveDiffY );
  1105. // Left, center or none-aligned widget.
  1106. if ( factor == 1 ) {
  1107. if ( moveDiffX <= 0 ) {
  1108. // Case: IV.
  1109. if ( moveDiffY <= 0 )
  1110. adjustToX();
  1111. // Case: I.
  1112. else {
  1113. if ( moveRatio >= ratio )
  1114. adjustToX();
  1115. else
  1116. adjustToY();
  1117. }
  1118. } else {
  1119. // Case: III.
  1120. if ( moveDiffY <= 0 ) {
  1121. if ( moveRatio >= ratio )
  1122. adjustToY();
  1123. else
  1124. adjustToX();
  1125. }
  1126. // Case: II.
  1127. else {
  1128. adjustToY();
  1129. }
  1130. }
  1131. }
  1132. // Right-aligned widget. It mirrors behaviours, so I becomes II,
  1133. // IV becomes III and vice-versa.
  1134. else {
  1135. if ( moveDiffX <= 0 ) {
  1136. // Case: IV.
  1137. if ( moveDiffY <= 0 ) {
  1138. if ( moveRatio >= ratio )
  1139. adjustToY();
  1140. else
  1141. adjustToX();
  1142. }
  1143. // Case: I.
  1144. else {
  1145. adjustToY();
  1146. }
  1147. } else {
  1148. // Case: III.
  1149. if ( moveDiffY <= 0 )
  1150. adjustToX();
  1151. // Case: II.
  1152. else {
  1153. if ( moveRatio >= ratio ) {
  1154. adjustToX();
  1155. } else {
  1156. adjustToY();
  1157. }
  1158. }
  1159. }
  1160. }
  1161. // Don't update attributes if less than 10.
  1162. // This is to prevent images to visually disappear.
  1163. if ( newWidth >= 15 && newHeight >= 15 ) {
  1164. image.setAttributes( { width: newWidth, height: newHeight } );
  1165. updateData = true;
  1166. } else {
  1167. updateData = false;
  1168. }
  1169. }
  1170. function onMouseUp() {
  1171. var l;
  1172. while ( ( l = listeners.pop() ) )
  1173. l.removeListener();
  1174. // Restore default cursor by removing special class.
  1175. editable.removeClass( cursorClass );
  1176. // This is to bring back the regular behaviour of the resizer.
  1177. resizer.removeClass( 'cke_image_resizing' );
  1178. if ( updateData ) {
  1179. widget.setData( { width: newWidth, height: newHeight } );
  1180. // Save another undo snapshot: after resizing.
  1181. editor.fire( 'saveSnapshot' );
  1182. }
  1183. // Don't update data twice or more.
  1184. updateData = false;
  1185. }
  1186. } );
  1187. // Change the position of the widget resizer when data changes.
  1188. widget.on( 'data', function() {
  1189. resizer[ widget.data.align == 'right' ? 'addClass' : 'removeClass' ]( 'cke_image_resizer_left' );
  1190. } );
  1191. }
  1192. // Integrates widget alignment setting with justify
  1193. // plugin's commands (execution and refreshment).
  1194. // @param {CKEDITOR.editor} editor
  1195. // @param {String} value 'left', 'right', 'center' or 'block'
  1196. function alignCommandIntegrator( editor ) {
  1197. var execCallbacks = [],
  1198. enabled;
  1199. return function( value ) {
  1200. var command = editor.getCommand( 'justify' + value );
  1201. // Most likely, the justify plugin isn't loaded.
  1202. if ( !command )
  1203. return;
  1204. // This command will be manually refreshed along with
  1205. // other commands after exec.
  1206. execCallbacks.push( function() {
  1207. command.refresh( editor, editor.elementPath() );
  1208. } );
  1209. if ( value in { right: 1, left: 1, center: 1 } ) {
  1210. command.on( 'exec', function( evt ) {
  1211. var widget = getFocusedWidget( editor );
  1212. if ( widget ) {
  1213. widget.setData( 'align', value );
  1214. // Once the widget changed its align, all the align commands
  1215. // must be refreshed: the event is to be cancelled.
  1216. for ( var i = execCallbacks.length; i--; )
  1217. execCallbacks[ i ]();
  1218. evt.cancel();
  1219. }
  1220. } );
  1221. }
  1222. command.on( 'refresh', function( evt ) {
  1223. var widget = getFocusedWidget( editor ),
  1224. allowed = { right: 1, left: 1, center: 1 };
  1225. if ( !widget )
  1226. return;
  1227. // Cache "enabled" on first use. This is because filter#checkFeature may
  1228. // not be available during plugin's afterInit in the future — a moment when
  1229. // alignCommandIntegrator is called.
  1230. if ( enabled === undefined )
  1231. enabled = editor.filter.checkFeature( editor.widgets.registered.image.features.align );
  1232. // Don't allow justify commands when widget alignment is disabled (#11004).
  1233. if ( !enabled )
  1234. this.setState( CKEDITOR.TRISTATE_DISABLED );
  1235. else {
  1236. this.setState(
  1237. ( widget.data.align == value ) ? (
  1238. CKEDITOR.TRISTATE_ON
  1239. ) : (
  1240. ( value in allowed ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED
  1241. )
  1242. );
  1243. }
  1244. evt.cancel();
  1245. } );
  1246. };
  1247. }
  1248. function linkCommandIntegrator( editor ) {
  1249. // Nothing to integrate with if link is not loaded.
  1250. if ( !editor.plugins.link )
  1251. return;
  1252. CKEDITOR.on( 'dialogDefinition', function( evt ) {
  1253. var dialog = evt.data;
  1254. if ( dialog.name == 'link' ) {
  1255. var def = dialog.definition;
  1256. var onShow = def.onShow,
  1257. onOk = def.onOk;
  1258. def.onShow = function() {
  1259. var widget = getFocusedWidget( editor ),
  1260. displayTextField = this.getContentElement( 'info', 'linkDisplayText' ).getElement().getParent().getParent();
  1261. // Widget cannot be enclosed in a link, i.e.
  1262. // <a>foo<inline widget/>bar</a>
  1263. if ( widget && ( widget.inline ? !widget.wrapper.getAscendant( 'a' ) : 1 ) ) {
  1264. this.setupContent( widget.data.link || {} );
  1265. // Hide the display text in case of linking image2_chamilo widget.
  1266. displayTextField.hide();
  1267. } else {
  1268. // Make sure that display text is visible, as it might be hidden by image2_chamilo integration
  1269. // before.
  1270. displayTextField.show();
  1271. onShow.apply( this, arguments );
  1272. }
  1273. };
  1274. // Set widget data if linking the widget using
  1275. // link dialog (instead of default action).
  1276. // State shifter handles data change and takes
  1277. // care of internal DOM structure of linked widget.
  1278. def.onOk = function() {
  1279. var widget = getFocusedWidget( editor );
  1280. // Widget cannot be enclosed in a link, i.e.
  1281. // <a>foo<inline widget/>bar</a>
  1282. if ( widget && ( widget.inline ? !widget.wrapper.getAscendant( 'a' ) : 1 ) ) {
  1283. var data = {};
  1284. // Collect data from fields.
  1285. this.commitContent( data );
  1286. // Set collected data to widget.
  1287. widget.setData( 'link', data );
  1288. } else {
  1289. onOk.apply( this, arguments );
  1290. }
  1291. };
  1292. }
  1293. } );
  1294. // Overwrite default behaviour of unlink command.
  1295. editor.getCommand( 'unlink' ).on( 'exec', function( evt ) {
  1296. var widget = getFocusedWidget( editor );
  1297. // Override unlink only when link truly belongs to the widget.
  1298. // If wrapped inline widget in a link, let default unlink work (#11814).
  1299. if ( !widget || !widget.parts.link )
  1300. return;
  1301. widget.setData( 'link', null );
  1302. // Selection (which is fake) may not change if unlinked image in focused widget,
  1303. // i.e. if captioned image. Let's refresh command state manually here.
  1304. this.refresh( editor, editor.elementPath() );
  1305. evt.cancel();
  1306. } );
  1307. // Overwrite default refresh of unlink command.
  1308. editor.getCommand( 'unlink' ).on( 'refresh', function( evt ) {
  1309. var widget = getFocusedWidget( editor );
  1310. if ( !widget )
  1311. return;
  1312. // Note that widget may be wrapped in a link, which
  1313. // does not belong to that widget (#11814).
  1314. this.setState( widget.data.link || widget.wrapper.getAscendant( 'a' ) ?
  1315. CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED );
  1316. evt.cancel();
  1317. } );
  1318. }
  1319. // Returns the focused widget, if of the type specific for this plugin.
  1320. // If no widget is focused, `null` is returned.
  1321. //
  1322. // @param {CKEDITOR.editor}
  1323. // @returns {CKEDITOR.plugins.widget}
  1324. function getFocusedWidget( editor ) {
  1325. var widget = editor.widgets.focused;
  1326. if ( widget && widget.name == 'image' )
  1327. return widget;
  1328. return null;
  1329. }
  1330. // Returns a set of widget allowedContent rules, depending
  1331. // on configurations like config#image2_chamilo_alignClasses or
  1332. // config#image2_chamilo_captionedClass.
  1333. //
  1334. // @param {CKEDITOR.editor}
  1335. // @returns {Object}
  1336. function getWidgetAllowedContent( editor ) {
  1337. var alignClasses = editor.config.image2_chamilo_alignClasses,
  1338. rules = {
  1339. // Widget may need <div> or <p> centering wrapper.
  1340. div: {
  1341. match: centerWrapperChecker( editor )
  1342. },
  1343. p: {
  1344. match: centerWrapperChecker( editor )
  1345. },
  1346. img: {
  1347. attributes: '!src,alt,width,height'
  1348. },
  1349. figure: {
  1350. classes: '!' + editor.config.image2_chamilo_captionedClass
  1351. },
  1352. figcaption: true
  1353. };
  1354. if ( alignClasses ) {
  1355. // Centering class from the config.
  1356. rules.div.classes = alignClasses[ 1 ];
  1357. rules.p.classes = rules.div.classes;
  1358. // Left/right classes from the config.
  1359. rules.img.classes = alignClasses[ 0 ];
  1360. for (var classI = 2; classI <= 11; classI++) {
  1361. rules.img.classes += ',' + alignClasses[ classI ];
  1362. }
  1363. rules.figure.classes += ',' + rules.img.classes;
  1364. } else {
  1365. // Centering with text-align.
  1366. rules.div.styles = 'text-align';
  1367. rules.p.styles = 'text-align';
  1368. rules.img.styles = 'float,vertical-align';
  1369. rules.figure.styles = 'float,display';
  1370. }
  1371. return rules;
  1372. }
  1373. // Returns a set of widget feature rules, depending
  1374. // on editor configuration. Note that the following may not cover
  1375. // all the possible cases since requiredContent supports a single
  1376. // tag only.
  1377. //
  1378. // @param {CKEDITOR.editor}
  1379. // @returns {Object}
  1380. function getWidgetFeatures( editor ) {
  1381. var alignClasses = editor.config.image2_chamilo_alignClasses,
  1382. features = {
  1383. dimension: {
  1384. requiredContent: 'img[width,height]'
  1385. },
  1386. align: {
  1387. requiredContent: 'img' +
  1388. ( alignClasses ? '(' + alignClasses[ 0 ] + ')' : '{float,vertica-align}' )
  1389. },
  1390. caption: {
  1391. requiredContent: 'figcaption'
  1392. },
  1393. responsive: {
  1394. requiredContent: 'img'
  1395. }
  1396. };
  1397. return features;
  1398. }
  1399. // Returns element which is styled, considering current
  1400. // state of the widget.
  1401. //
  1402. // @see CKEDITOR.plugins.widget#applyStyle
  1403. // @param {CKEDITOR.plugins.widget} widget
  1404. // @returns {CKEDITOR.dom.element}
  1405. function getStyleableElement( widget ) {
  1406. return widget.data.hasCaption ? widget.element : widget.parts.image;
  1407. }
  1408. } )();
  1409. /**
  1410. * A CSS class applied to the `<figure>` element of a captioned image.
  1411. *
  1412. * Read more in the [documentation](#!/guide/dev_captionedimage) and see the
  1413. * [SDK sample](http://sdk.ckeditor.com/samples/captionedimage.html).
  1414. *
  1415. * // Changes the class to "captionedImage".
  1416. * config.image2_chamilo_captionedClass = 'captionedImage';
  1417. *
  1418. * @cfg {String} [image2_chamilo_captionedClass='image']
  1419. * @member CKEDITOR.config
  1420. */
  1421. CKEDITOR.config.image2_chamilo_captionedClass = 'image';
  1422. /**
  1423. * Determines whether dimension inputs should be automatically filled when the image URL changes in the Enhanced Image
  1424. * plugin dialog window.
  1425. *
  1426. * Read more in the [documentation](#!/guide/dev_captionedimage) and see the
  1427. * [SDK sample](http://sdk.ckeditor.com/samples/captionedimage.html).
  1428. *
  1429. * config.image2_chamilo_prefillDimensions = false;
  1430. *
  1431. * @since 4.5
  1432. * @cfg {Boolean} [image2_chamilo_prefillDimensions=true]
  1433. * @member CKEDITOR.config
  1434. */
  1435. /**
  1436. * Disables the image resizer. By default the resizer is enabled.
  1437. *
  1438. * Read more in the [documentation](#!/guide/dev_captionedimage) and see the
  1439. * [SDK sample](http://sdk.ckeditor.com/samples/captionedimage.html).
  1440. *
  1441. * config.image2_chamilo_disableResizer = true;
  1442. *
  1443. * @since 4.5
  1444. * @cfg {Boolean} [image2_chamilo_disableResizer=false]
  1445. * @member CKEDITOR.config
  1446. */
  1447. /**
  1448. * CSS classes applied to aligned images. Useful to take control over the way
  1449. * the images are aligned, i.e. to customize output HTML and integrate external stylesheets.
  1450. *
  1451. * Classes should be defined in an array of three elements, containing left, center, and right
  1452. * alignment classes, respectively. For example:
  1453. *
  1454. * config.image2_chamilo_alignClasses = [ 'align-left', 'align-center', 'align-right' ];
  1455. *
  1456. * **Note**: Once this configuration option is set, the plugin will no longer produce inline
  1457. * styles for alignment. It means that e.g. the following HTML will be produced:
  1458. *
  1459. * <img alt="My image" class="custom-center-class" src="foo.png" />
  1460. *
  1461. * instead of:
  1462. *
  1463. * <img alt="My image" style="float:left" src="foo.png" />
  1464. *
  1465. * **Note**: Once this configuration option is set, corresponding style definitions
  1466. * must be supplied to the editor:
  1467. *
  1468. * * For [classic editor](#!/guide/dev_framed) it can be done by defining additional
  1469. * styles in the {@link CKEDITOR.config#contentsCss stylesheets loaded by the editor}. The same
  1470. * styles must be provided on the target page where the content will be loaded.
  1471. * * For [inline editor](#!/guide/dev_inline) the styles can be defined directly
  1472. * with `<style> ... <style>` or `<link href="..." rel="stylesheet">`, i.e. within the `<head>`
  1473. * of the page.
  1474. *
  1475. * For example, considering the following configuration:
  1476. *
  1477. * config.image2_chamilo_alignClasses = [ 'align-left', 'align-center', 'align-right' ];
  1478. *
  1479. * CSS rules can be defined as follows:
  1480. *
  1481. * .align-left {
  1482. * float: left;
  1483. * }
  1484. *
  1485. * .align-right {
  1486. * float: right;
  1487. * }
  1488. *
  1489. * .align-center {
  1490. * text-align: center;
  1491. * }
  1492. *
  1493. * .align-center > figure {
  1494. * display: inline-block;
  1495. * }
  1496. *
  1497. * Read more in the [documentation](#!/guide/dev_captionedimage) and see the
  1498. * [SDK sample](http://sdk.ckeditor.com/samples/captionedimage.html).
  1499. *
  1500. * @since 4.4
  1501. * @cfg {String[]} [image2_chamilo_alignClasses=null]
  1502. * @member CKEDITOR.config
  1503. */
  1504. /**
  1505. * Determines whether alternative text is required for the captioned image.
  1506. *
  1507. * config.image2_chamilo_altRequired = true;
  1508. *
  1509. * Read more in the [documentation](#!/guide/dev_captionedimage) and see the
  1510. * [SDK sample](http://sdk.ckeditor.com/samples/captionedimage.html).
  1511. *
  1512. * @since 4.6.0
  1513. * @cfg {Boolean} [image2_chamilo_altRequired=false]
  1514. * @member CKEDITOR.config
  1515. */