plugin.js 61 KB

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