plugin.js 64 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813
  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 (this.data.hasCaption) {
  314. this.parts.image.setStyle('border-width', null);
  315. this.parts.image.setStyle('border-style', null);
  316. this.parts.image.setStyle('border-color', null);
  317. this.parts.image.setStyle('border-radius', null);
  318. this.parts.image.setStyle('background-color', null);
  319. } else {
  320. this.parts.image.setStyle('border-width', this.data.borderWidth);
  321. this.parts.image.setStyle('border-style', this.data.borderStyle);
  322. this.parts.image.setStyle('border-color', this.data.borderColor);
  323. this.parts.image.setStyle('border-radius', this.data.borderRadius);
  324. this.parts.image.setStyle('background-color', this.data.backgroundColor);
  325. }
  326. // If shifting non-captioned -> captioned, remove classes
  327. // related to styles from <img/>.
  328. if ( this.oldData && !this.oldData.hasCaption && this.data.hasCaption ) {
  329. for ( var c in this.data.classes )
  330. this.parts.image.removeClass( c );
  331. }
  332. // Set dimensions of the image according to gathered data.
  333. // Do it only when the attributes are allowed (#11004).
  334. if ( editor.filter.checkFeature( features.dimension ) )
  335. setDimensions( this );
  336. // Cache current data.
  337. this.oldData = CKEDITOR.tools.extend( {}, this.data );
  338. },
  339. init: function() {
  340. var helpers = CKEDITOR.plugins.image2_chamilo,
  341. image = this.parts.image,
  342. data = {
  343. hasCaption: !!this.parts.caption,
  344. src: image.getAttribute( 'src' ),
  345. alt: image.getAttribute( 'alt' ) || '',
  346. width: image.getAttribute( 'width' ) || '',
  347. height: image.getAttribute( 'height' ) || '',
  348. isResponsive: !!image.$.className.match(/img-responsive/i),
  349. // Lock ratio is on by default (#10833).
  350. lock: this.ready ? helpers.checkHasNaturalRatio( image ) : true,
  351. borderWidth: !this.parts.caption ? image.getStyle('border-width') : '',
  352. borderStyle: !this.parts.caption ? image.getStyle('border-style') : 'solid',
  353. borderColor: !this.parts.caption ? image.getStyle('border-color') : '',
  354. borderRadius: !this.parts.caption ? image.getStyle('border-radius') : '',
  355. backgroundColor: !this.parts.caption ? image.getStyle('background-color') : '',
  356. };
  357. // If we used 'a' in widget#parts definition, it could happen that
  358. // selected element is a child of widget.parts#caption. Since there's no clever
  359. // way to solve it with CSS selectors, it's done like that. (#11783).
  360. var link = image.getAscendant( 'a' );
  361. if ( link && this.wrapper.contains( link ) )
  362. this.parts.link = link;
  363. // Depending on configuration, read style/class from element and
  364. // then remove it. Removed style/class will be set on wrapper in #data listener.
  365. // Note: Center alignment is detected during upcast, so only left/right cases
  366. // are checked below.
  367. if ( !data.align ) {
  368. var alignElement = data.hasCaption ? this.element : image;
  369. // Read the initial left/right alignment from the class set on element.
  370. if ( alignClasses ) {
  371. if ( alignElement.hasClass( alignClasses[ 0 ] ) ) {
  372. data.align = 'left';
  373. } else if ( alignElement.hasClass( alignClasses[ 2 ] ) ) {
  374. data.align = 'right';
  375. } else if ( alignElement.hasClass( alignClasses[ 3 ] ) ) {
  376. data.align = 'baseline';
  377. } else if ( alignElement.hasClass( alignClasses[ 4 ] ) ) {
  378. data.align = 'top';
  379. } else if ( alignElement.hasClass( alignClasses[ 5 ] ) ) {
  380. data.align = 'bottom';
  381. } else if ( alignElement.hasClass( alignClasses[ 6 ] ) ) {
  382. data.align = 'middle';
  383. } else if ( alignElement.hasClass( alignClasses[ 7 ] ) ) {
  384. data.align = 'super';
  385. } else if ( alignElement.hasClass( alignClasses[ 8 ] ) ) {
  386. data.align = 'sub';
  387. } else if ( alignElement.hasClass( alignClasses[ 9 ] ) ) {
  388. data.align = 'text-top';
  389. } else if ( alignElement.hasClass( alignClasses[ 10 ] ) ) {
  390. data.align = 'text-bottom';
  391. }
  392. if ( data.align ) {
  393. alignElement.removeClass( alignClasses[ alignmentsObj[ data.align ] ] );
  394. } else {
  395. data.align = 'none';
  396. }
  397. }
  398. // Read initial float style from figure/image and then remove it.
  399. else {
  400. data.align = alignElement.getStyle( 'float' );
  401. if ( data.align !== 'left' && data.align !== 'right' ) {
  402. data.align = alignElement.getStyle('vertical-align');
  403. } else {
  404. data.align = 'none';
  405. }
  406. alignElement.removeStyle( 'float' );
  407. alignElement.removeStyle( 'vertical-align' );
  408. }
  409. }
  410. // Update data.link object with attributes if the link has been discovered.
  411. if ( editor.plugins.link && this.parts.link ) {
  412. data.link = helpers.getLinkAttributesParser()( editor, this.parts.link );
  413. // Get rid of cke_widget_* classes in data. Otherwise
  414. // they might appear in link dialog.
  415. var advanced = data.link.advanced;
  416. if ( advanced && advanced.advCSSClasses ) {
  417. advanced.advCSSClasses = CKEDITOR.tools.trim( advanced.advCSSClasses.replace( /cke_\S+/, '' ) );
  418. }
  419. }
  420. // Get rid of extra vertical space when there's no caption.
  421. // It will improve the look of the resizer.
  422. this.wrapper[ ( data.hasCaption ? 'remove' : 'add' ) + 'Class' ]( 'cke_image_nocaption' );
  423. this.setData( data );
  424. // Setup dynamic image resizing with mouse.
  425. // Don't initialize resizer when dimensions are disallowed (#11004).
  426. if ( editor.filter.checkFeature( this.features.dimension ) && editor.config.image2_chamilo_disableResizer !== true )
  427. setupResizer( this );
  428. this.shiftState = helpers.stateShifter( this.editor );
  429. // Add widget editing option to its context menu.
  430. this.on( 'contextMenu', function( evt ) {
  431. evt.data.image = CKEDITOR.TRISTATE_OFF;
  432. // Integrate context menu items for link.
  433. // Note that widget may be wrapped in a link, which
  434. // does not belong to that widget (#11814).
  435. if ( this.parts.link || this.wrapper.getAscendant( 'a' ) )
  436. evt.data.link = evt.data.unlink = CKEDITOR.TRISTATE_OFF;
  437. } );
  438. // Pass the reference to this widget to the dialog.
  439. this.on( 'dialog', function( evt ) {
  440. evt.data.widget = this;
  441. }, this );
  442. },
  443. // Overrides default method to handle internal mutability of Image2Chamilo.
  444. // @see CKEDITOR.plugins.widget#addClass
  445. addClass: function( className ) {
  446. getStyleableElement( this ).addClass( className );
  447. },
  448. // Overrides default method to handle internal mutability of Image2Chamilo.
  449. // @see CKEDITOR.plugins.widget#hasClass
  450. hasClass: function( className ) {
  451. return getStyleableElement( this ).hasClass( className );
  452. },
  453. // Overrides default method to handle internal mutability of Image2Chamilo.
  454. // @see CKEDITOR.plugins.widget#removeClass
  455. removeClass: function( className ) {
  456. getStyleableElement( this ).removeClass( className );
  457. },
  458. // Overrides default method to handle internal mutability of Image2Chamilo.
  459. // @see CKEDITOR.plugins.widget#getClasses
  460. getClasses: ( function() {
  461. var classRegex = new RegExp( '^(' + [].concat( captionedClass, alignClasses ).join( '|' ) + ')$' );
  462. return function() {
  463. var classes = this.repository.parseElementClasses( getStyleableElement( this ).getAttribute( 'class' ) );
  464. // Neither config.image2_chamilo_captionedClass nor config.image2_chamilo_alignClasses
  465. // do not belong to style classes.
  466. for ( var c in classes ) {
  467. if ( classRegex.test( c ) )
  468. delete classes[ c ];
  469. }
  470. return classes;
  471. };
  472. } )(),
  473. upcast: upcastWidgetElement( editor ),
  474. downcast: downcastWidgetElement( editor ),
  475. getLabel: function() {
  476. var label = ( this.data.alt || '' ) + ' ' + this.pathName;
  477. return this.editor.lang.widget.label.replace( /%1/, label );
  478. }
  479. };
  480. }
  481. /**
  482. * A set of Enhanced Image (image2_chamilo) plugin helpers.
  483. *
  484. * @class
  485. * @singleton
  486. */
  487. CKEDITOR.plugins.image2_chamilo = {
  488. stateShifter: function( editor ) {
  489. // Tag name used for centering non-captioned widgets.
  490. var doc = editor.document,
  491. alignClasses = editor.config.image2_chamilo_alignClasses,
  492. captionedClass = editor.config.image2_chamilo_captionedClass,
  493. editable = editor.editable(),
  494. // The order that stateActions get executed. It matters!
  495. shiftables = [ 'hasCaption', 'align', 'link' ];
  496. // Atomic procedures, one per state variable.
  497. var stateActions = {
  498. align: function( shift, oldValue, newValue ) {
  499. var el = shift.element;
  500. // Alignment changed.
  501. if ( shift.changed.align ) {
  502. // No caption in the new state.
  503. if ( !shift.newData.hasCaption ) {
  504. // Changed to "center" (non-captioned).
  505. if ( newValue == 'center' ) {
  506. shift.deflate();
  507. shift.element = wrapInCentering( editor, el );
  508. }
  509. // Changed to "non-center" from "center" while caption removed.
  510. if ( !shift.changed.hasCaption && oldValue == 'center' && newValue != 'center' ) {
  511. shift.deflate();
  512. shift.element = unwrapFromCentering( el );
  513. }
  514. }
  515. }
  516. // Alignment remains and "center" removed caption.
  517. else if ( newValue == 'center' && shift.changed.hasCaption && !shift.newData.hasCaption ) {
  518. shift.deflate();
  519. shift.element = wrapInCentering( editor, el );
  520. }
  521. // Finally set display for figure.
  522. // if ( !alignClasses && el.is( 'figure' ) ) {
  523. if ( el.is( 'figure' ) ) { // Force set display inline-block for figure regardless class
  524. if ( newValue == 'center' )
  525. el.setStyle( 'display', 'inline-block' );
  526. else
  527. el.removeStyle( 'display' );
  528. }
  529. },
  530. hasCaption: function( shift, oldValue, newValue ) {
  531. // This action is for real state change only.
  532. if ( !shift.changed.hasCaption )
  533. return;
  534. // Get <img/> or <a><img/></a> from widget. Note that widget element might itself
  535. // be what we're looking for. Also element can be <p style="text-align:center"><a>...</a></p>.
  536. var imageOrLink;
  537. if ( shift.element.is( { img: 1, a: 1 } ) )
  538. imageOrLink = shift.element;
  539. else
  540. imageOrLink = shift.element.findOne( 'a,img' );
  541. // Switching hasCaption always destroys the widget.
  542. shift.deflate();
  543. // There was no caption, but the caption is to be added.
  544. if ( newValue ) {
  545. // Create new <figure> from widget template.
  546. var figure = CKEDITOR.dom.element.createFromHtml( templateBlock.output( {
  547. captionedClass: captionedClass,
  548. captionPlaceholder: editor.lang.image2_chamilo.captionPlaceholder
  549. } ), doc );
  550. // Replace element with <figure>.
  551. replaceSafely( figure, shift.element );
  552. // Use old <img/> or <a><img/></a> instead of the one from the template,
  553. // so we won't lose additional attributes.
  554. imageOrLink.replace( figure.findOne( 'img' ) );
  555. // Update widget's element.
  556. shift.element = figure;
  557. }
  558. // The caption was present, but now it's to be removed.
  559. else {
  560. // Unwrap <img/> or <a><img/></a> from figure.
  561. imageOrLink.replace( shift.element );
  562. // Update widget's element.
  563. shift.element = imageOrLink;
  564. }
  565. },
  566. link: function( shift, oldValue, newValue ) {
  567. if ( shift.changed.link ) {
  568. var img = shift.element.is( 'img' ) ?
  569. shift.element : shift.element.findOne( 'img' ),
  570. link = shift.element.is( 'a' ) ?
  571. shift.element : shift.element.findOne( 'a' ),
  572. // Why deflate:
  573. // If element is <img/>, it will be wrapped into <a>,
  574. // which becomes a new widget.element.
  575. // If element is <a><img/></a>, it will be unlinked
  576. // so <img/> becomes a new widget.element.
  577. needsDeflate = ( shift.element.is( 'a' ) && !newValue ) || ( shift.element.is( 'img' ) && newValue ),
  578. newEl;
  579. if ( needsDeflate )
  580. shift.deflate();
  581. // If unlinked the image, returned element is <img>.
  582. if ( !newValue )
  583. newEl = unwrapFromLink( link );
  584. else {
  585. // If linked the image, returned element is <a>.
  586. if ( !oldValue )
  587. newEl = wrapInLink( img, shift.newData.link );
  588. // Set and remove all attributes associated with this state.
  589. var attributes = CKEDITOR.plugins.image2_chamilo.getLinkAttributesGetter()( editor, newValue );
  590. if ( !CKEDITOR.tools.isEmpty( attributes.set ) )
  591. ( newEl || link ).setAttributes( attributes.set );
  592. if ( attributes.removed.length )
  593. ( newEl || link ).removeAttributes( attributes.removed );
  594. }
  595. if ( needsDeflate )
  596. shift.element = newEl;
  597. }
  598. },
  599. isResponsive: function (shift, oldValue, newValue) {
  600. if (!shift.changed.isResponsive) {
  601. return;
  602. }
  603. var image = shift.element.find('img');
  604. }
  605. };
  606. function wrapInCentering( editor, element ) {
  607. var attribsAndStyles = {};
  608. if ( alignClasses )
  609. attribsAndStyles.attributes = { 'class': alignClasses[ 1 ] };
  610. else
  611. attribsAndStyles.styles = { 'text-align': 'center' };
  612. // There's no gentle way to center inline element with CSS, so create p/div
  613. // that wraps widget contents and does the trick either with style or class.
  614. var center = doc.createElement(
  615. editor.activeEnterMode == CKEDITOR.ENTER_P ? 'p' : 'div', attribsAndStyles );
  616. // Replace element with centering wrapper.
  617. replaceSafely( center, element );
  618. element.move( center );
  619. return center;
  620. }
  621. function unwrapFromCentering( element ) {
  622. var imageOrLink = element.findOne( 'a,img' );
  623. imageOrLink.replace( element );
  624. return imageOrLink;
  625. }
  626. // Wraps <img/> -> <a><img/></a>.
  627. // Returns reference to <a>.
  628. //
  629. // @param {CKEDITOR.dom.element} img
  630. // @param {Object} linkData
  631. // @returns {CKEDITOR.dom.element}
  632. function wrapInLink( img, linkData ) {
  633. var link = doc.createElement( 'a', {
  634. attributes: {
  635. href: linkData.url
  636. }
  637. } );
  638. link.replace( img );
  639. img.move( link );
  640. return link;
  641. }
  642. // De-wraps <a><img/></a> -> <img/>.
  643. // Returns the reference to <img/>
  644. //
  645. // @param {CKEDITOR.dom.element} link
  646. // @returns {CKEDITOR.dom.element}
  647. function unwrapFromLink( link ) {
  648. var img = link.findOne( 'img' );
  649. img.replace( link );
  650. return img;
  651. }
  652. function replaceSafely( replacing, replaced ) {
  653. if ( replaced.getParent() ) {
  654. var range = editor.createRange();
  655. range.moveToPosition( replaced, CKEDITOR.POSITION_BEFORE_START );
  656. // Remove old element. Do it before insertion to avoid a case when
  657. // element is moved from 'replaced' element before it, what creates
  658. // a tricky case which insertElementIntorRange does not handle.
  659. replaced.remove();
  660. editable.insertElementIntoRange( replacing, range );
  661. }
  662. else {
  663. replacing.replace( replaced );
  664. }
  665. }
  666. return function( shift ) {
  667. var name, i;
  668. shift.changed = {};
  669. for ( i = 0; i < shiftables.length; i++ ) {
  670. name = shiftables[ i ];
  671. shift.changed[ name ] = shift.oldData ?
  672. shift.oldData[ name ] !== shift.newData[ name ] : false;
  673. }
  674. // Iterate over possible state variables.
  675. for ( i = 0; i < shiftables.length; i++ ) {
  676. name = shiftables[ i ];
  677. stateActions[ name ]( shift,
  678. shift.oldData ? shift.oldData[ name ] : null,
  679. shift.newData[ name ] );
  680. }
  681. shift.inflate();
  682. };
  683. },
  684. /**
  685. * Checks whether the current image ratio matches the natural one
  686. * by comparing dimensions.
  687. *
  688. * @param {CKEDITOR.dom.element} image
  689. * @returns {Boolean}
  690. */
  691. checkHasNaturalRatio: function( image ) {
  692. var $ = image.$,
  693. natural = this.getNatural( image );
  694. // The reason for two alternative comparisons is that the rounding can come from
  695. // both dimensions, e.g. there are two cases:
  696. // 1. height is computed as a rounded relation of the real height and the value of width,
  697. // 2. width is computed as a rounded relation of the real width and the value of heigh.
  698. return Math.round( $.clientWidth / natural.width * natural.height ) == $.clientHeight ||
  699. Math.round( $.clientHeight / natural.height * natural.width ) == $.clientWidth;
  700. },
  701. /**
  702. * Returns natural dimensions of the image. For modern browsers
  703. * it uses natural(Width|Height). For old ones (IE8) it creates
  704. * a new image and reads the dimensions.
  705. *
  706. * @param {CKEDITOR.dom.element} image
  707. * @returns {Object}
  708. */
  709. getNatural: function( image ) {
  710. var dimensions;
  711. if ( image.$.naturalWidth ) {
  712. dimensions = {
  713. width: image.$.naturalWidth,
  714. height: image.$.naturalHeight
  715. };
  716. } else {
  717. var img = new Image();
  718. img.src = image.getAttribute( 'src' );
  719. dimensions = {
  720. width: img.width,
  721. height: img.height
  722. };
  723. }
  724. return dimensions;
  725. },
  726. /**
  727. * Returns an attribute getter function. Default getter comes from the Link plugin
  728. * and is documented by {@link CKEDITOR.plugins.link#getLinkAttributes}.
  729. *
  730. * **Note:** It is possible to override this method and use a custom getter e.g.
  731. * in the absence of the Link plugin.
  732. *
  733. * **Note:** If a custom getter is used, a data model format it produces
  734. * must be compatible with {@link CKEDITOR.plugins.link#getLinkAttributes}.
  735. *
  736. * **Note:** A custom getter must understand the data model format produced by
  737. * {@link #getLinkAttributesParser} to work correctly.
  738. *
  739. * @returns {Function} A function that gets (composes) link attributes.
  740. * @since 4.5.5
  741. */
  742. getLinkAttributesGetter: function() {
  743. // #13885
  744. return CKEDITOR.plugins.link.getLinkAttributes;
  745. },
  746. /**
  747. * Returns an attribute parser function. Default parser comes from the Link plugin
  748. * and is documented by {@link CKEDITOR.plugins.link#parseLinkAttributes}.
  749. *
  750. * **Note:** It is possible to override this method and use a custom parser e.g.
  751. * in the absence of the Link plugin.
  752. *
  753. * **Note:** If a custom parser is used, a data model format produced by the parser
  754. * must be compatible with {@link #getLinkAttributesGetter}.
  755. *
  756. * **Note:** If a custom parser is used, it should be compatible with the
  757. * {@link CKEDITOR.plugins.link#parseLinkAttributes} data model format. Otherwise the
  758. * Link plugin dialog may not be populated correctly with parsed data. However
  759. * as long as Enhanced Image is **not** used with the Link plugin dialog, any custom data model
  760. * will work, being stored as an internal property of Enhanced Image widget's data only.
  761. *
  762. * @returns {Function} A function that parses attributes.
  763. * @since 4.5.5
  764. */
  765. getLinkAttributesParser: function() {
  766. // #13885
  767. return CKEDITOR.plugins.link.parseLinkAttributes;
  768. }
  769. };
  770. function setWrapperAlign( widget, alignClasses ) {
  771. var wrapper = widget.wrapper,
  772. align = widget.data.align,
  773. hasCaption = widget.data.hasCaption;
  774. if ( alignClasses ) {
  775. // Remove all align classes first.
  776. for ( var i = 11; i--; )
  777. wrapper.removeClass( alignClasses[ i ] );
  778. if ( align == 'center' ) {
  779. // Avoid touching non-captioned, centered widgets because
  780. // they have the class set on the element instead of wrapper:
  781. //
  782. // <div class="cke_widget_wrapper">
  783. // <p class="center-class">
  784. // <img />
  785. // </p>
  786. // </div>
  787. if ( hasCaption ) {
  788. wrapper.addClass( alignClasses[ 1 ] );
  789. }
  790. } else if ( align != 'none' ) {
  791. wrapper.addClass( alignClasses[ alignmentsObj[ align ] ] );
  792. }
  793. } else {
  794. if ( align == 'center' ) {
  795. if ( hasCaption )
  796. wrapper.setStyle( 'text-align', 'center' );
  797. else
  798. wrapper.removeStyle( 'text-align' );
  799. wrapper.removeStyle( 'float' );
  800. }
  801. else {
  802. if ( align == 'none' )
  803. wrapper.removeStyle( 'float' );
  804. else if (align == 'left' || align == 'right')
  805. wrapper.setStyle( 'float', align );
  806. else
  807. wrapper.setStyle( 'vertical-align', align );
  808. wrapper.removeStyle( 'text-align' );
  809. }
  810. }
  811. }
  812. // Returns a function that creates widgets from all <img> and
  813. // <figure class="{config.image2_chamilo_captionedClass}"> elements.
  814. //
  815. // @param {CKEDITOR.editor} editor
  816. // @returns {Function}
  817. function upcastWidgetElement( editor ) {
  818. var isCenterWrapper = centerWrapperChecker( editor ),
  819. captionedClass = editor.config.image2_chamilo_captionedClass;
  820. // @param {CKEDITOR.htmlParser.element} el
  821. // @param {Object} data
  822. return function( el, data ) {
  823. var dimensions = { width: 1, height: 1 },
  824. name = el.name,
  825. image;
  826. // #11110 Don't initialize on pasted fake objects.
  827. if ( el.attributes[ 'data-cke-realelement' ] )
  828. return;
  829. // If a center wrapper is found, there are 3 possible cases:
  830. //
  831. // 1. <div style="text-align:center"><figure>...</figure></div>.
  832. // In this case centering is done with a class set on widget.wrapper.
  833. // Simply replace centering wrapper with figure (it's no longer necessary).
  834. //
  835. // 2. <p style="text-align:center"><img/></p>.
  836. // Nothing to do here: <p> remains for styling purposes.
  837. //
  838. // 3. <div style="text-align:center"><img/></div>.
  839. // Nothing to do here (2.) but that case is only possible in enterMode different
  840. // than ENTER_P.
  841. if ( isCenterWrapper( el ) ) {
  842. if ( name == 'div' ) {
  843. var figure = el.getFirst( 'figure' );
  844. // Case #1.
  845. if ( figure ) {
  846. el.replaceWith( figure );
  847. el = figure;
  848. }
  849. }
  850. // Cases #2 and #3 (handled transparently)
  851. // If there's a centering wrapper, save it in data.
  852. data.align = 'center';
  853. // Image can be wrapped in link <a><img/></a>.
  854. image = el.getFirst( 'img' ) || el.getFirst( 'a' ).getFirst( 'img' );
  855. }
  856. // No center wrapper has been found.
  857. else if ( name == 'figure' && el.hasClass( captionedClass ) ) {
  858. image = el.getFirst( 'img' ) || el.getFirst( 'a' ).getFirst( 'img' );
  859. // Upcast linked image like <a><img/></a>.
  860. } else if ( isLinkedOrStandaloneImage( el ) ) {
  861. image = el.name == 'a' ? el.children[ 0 ] : el;
  862. }
  863. if ( !image )
  864. return;
  865. // If there's an image, then cool, we got a widget.
  866. // Now just remove dimension attributes expressed with %.
  867. for ( var d in dimensions ) {
  868. var dimension = image.attributes[ d ];
  869. if ( dimension && dimension.match( regexPercent ) )
  870. delete image.attributes[ d ];
  871. }
  872. return el;
  873. };
  874. }
  875. // Returns a function which transforms the widget to the external format
  876. // according to the current configuration.
  877. //
  878. // @param {CKEDITOR.editor}
  879. function downcastWidgetElement( editor ) {
  880. var alignClasses = editor.config.image2_chamilo_alignClasses;
  881. // @param {CKEDITOR.htmlParser.element} el
  882. return function( el ) {
  883. // In case of <a><img/></a>, <img/> is the element to hold
  884. // inline styles or classes (image2_chamilo_alignClasses).
  885. var attrsHolder = el.name == 'a' ? el.getFirst() : el,
  886. attrs = attrsHolder.attributes,
  887. align = this.data.align;
  888. // De-wrap the image from resize handle wrapper.
  889. // Only block widgets have one.
  890. if ( !this.inline ) {
  891. var resizeWrapper = el.getFirst( 'span' );
  892. if ( resizeWrapper )
  893. resizeWrapper.replaceWith( resizeWrapper.getFirst( { img: 1, a: 1 } ) );
  894. }
  895. if ( align && align != 'none' ) {
  896. var styles = CKEDITOR.tools.parseCssText( attrs.style || '' );
  897. var allowedAllignments = {
  898. 'left': 1,
  899. 'right': 1,
  900. 'baseline': 1,
  901. 'top': 1,
  902. 'bottom': 1,
  903. 'middle': 1,
  904. 'super': 1,
  905. 'sub': 1,
  906. 'text-top': 1,
  907. 'text-bottom': 1
  908. };
  909. // When the widget is captioned (<figure>) and internally centering is done
  910. // with widget's wrapper style/class, in the external data representation,
  911. // <figure> must be wrapped with an element holding an style/class:
  912. //
  913. // <div style="text-align:center">
  914. // <figure class="image" style="display:inline-block">...</figure>
  915. // </div>
  916. // or
  917. // <div class="some-center-class">
  918. // <figure class="image">...</figure>
  919. // </div>
  920. //
  921. if ( align == 'center' && el.name == 'figure' ) {
  922. el = el.wrapWith( new CKEDITOR.htmlParser.element( 'div',
  923. alignClasses ? { 'class': alignClasses[ 1 ] } : { style: 'text-align:center' } ) );
  924. }
  925. // If left/right, add float style to the downcasted element.
  926. else if ( align in allowedAllignments ) {
  927. if ( alignClasses )
  928. attrsHolder.addClass( alignClasses[ alignmentsObj[ align ] ] );
  929. else if (align == 'left' || align == 'right')
  930. styles[ 'float' ] = align;
  931. else
  932. styles['vertical-align'] = align;
  933. }
  934. // Update element styles.
  935. if ( !alignClasses && !CKEDITOR.tools.isEmpty( styles ) )
  936. attrs.style = CKEDITOR.tools.writeCssText( styles );
  937. }
  938. return el;
  939. };
  940. }
  941. // Returns a function that checks if an element is a centering wrapper.
  942. //
  943. // @param {CKEDITOR.editor} editor
  944. // @returns {Function}
  945. function centerWrapperChecker( editor ) {
  946. var captionedClass = editor.config.image2_chamilo_captionedClass,
  947. alignClasses = editor.config.image2_chamilo_alignClasses,
  948. validChildren = { figure: 1, a: 1, img: 1 };
  949. return function( el ) {
  950. // Wrapper must be either <div> or <p>.
  951. if ( !( el.name in { div: 1, p: 1 } ) )
  952. return false;
  953. var children = el.children;
  954. // Centering wrapper can have only one child.
  955. if ( children.length !== 1 )
  956. return false;
  957. var child = children[ 0 ];
  958. // Only <figure> or <img /> can be first (only) child of centering wrapper,
  959. // regardless of its type.
  960. if ( !( child.name in validChildren ) )
  961. return false;
  962. // If centering wrapper is <p>, only <img /> can be the child.
  963. // <p style="text-align:center"><img /></p>
  964. if ( el.name == 'p' ) {
  965. if ( !isLinkedOrStandaloneImage( child ) )
  966. return false;
  967. }
  968. // Centering <div> can hold <img/> or <figure>, depending on enterMode.
  969. else {
  970. // If a <figure> is the first (only) child, it must have a class.
  971. // <div style="text-align:center"><figure>...</figure><div>
  972. if ( child.name == 'figure' ) {
  973. if ( !child.hasClass( captionedClass ) )
  974. return false;
  975. } else {
  976. // Centering <div> can hold <img/> or <a><img/></a> only when enterMode
  977. // is ENTER_(BR|DIV).
  978. // <div style="text-align:center"><img /></div>
  979. // <div style="text-align:center"><a><img /></a></div>
  980. if ( editor.enterMode == CKEDITOR.ENTER_P )
  981. return false;
  982. // Regardless of enterMode, a child which is not <figure> must be
  983. // either <img/> or <a><img/></a>.
  984. if ( !isLinkedOrStandaloneImage( child ) )
  985. return false;
  986. }
  987. }
  988. // Centering wrapper got to be... centering. If image2_chamilo_alignClasses are defined,
  989. // check for centering class. Otherwise, check the style.
  990. if ( alignClasses ? el.hasClass( alignClasses[ 1 ] ) :
  991. CKEDITOR.tools.parseCssText( el.attributes.style || '', true )[ 'text-align' ] == 'center' )
  992. return true;
  993. return false;
  994. };
  995. }
  996. // Checks whether element is <img/> or <a><img/></a>.
  997. //
  998. // @param {CKEDITOR.htmlParser.element}
  999. function isLinkedOrStandaloneImage( el ) {
  1000. if ( el.name == 'img' )
  1001. return true;
  1002. else if ( el.name == 'a' )
  1003. return el.children.length == 1 && el.getFirst( 'img' );
  1004. return false;
  1005. }
  1006. // Sets width and height of the widget image according to current widget data.
  1007. //
  1008. // @param {CKEDITOR.plugins.widget} widget
  1009. function setDimensions( widget ) {
  1010. var data = widget.data,
  1011. dimensions = { width: data.width, height: data.height },
  1012. image = widget.parts.image;
  1013. for ( var d in dimensions ) {
  1014. if ( dimensions[ d ] )
  1015. image.setAttribute( d, dimensions[ d ] );
  1016. else
  1017. image.removeAttribute( d );
  1018. }
  1019. }
  1020. // Defines all features related to drag-driven image resizing.
  1021. //
  1022. // @param {CKEDITOR.plugins.widget} widget
  1023. function setupResizer( widget ) {
  1024. var editor = widget.editor,
  1025. editable = editor.editable(),
  1026. doc = editor.document,
  1027. // Store the resizer in a widget for testing (#11004).
  1028. resizer = widget.resizer = doc.createElement( 'span' );
  1029. resizer.addClass( 'cke_image_resizer' );
  1030. resizer.setAttribute( 'title', editor.lang.image2_chamilo.resizer );
  1031. resizer.append( new CKEDITOR.dom.text( '\u200b', doc ) );
  1032. // Inline widgets don't need a resizer wrapper as an image spans the entire widget.
  1033. if ( !widget.inline ) {
  1034. var imageOrLink = widget.parts.link || widget.parts.image,
  1035. oldResizeWrapper = imageOrLink.getParent(),
  1036. resizeWrapper = doc.createElement( 'span' );
  1037. resizeWrapper.addClass( 'cke_image_resizer_wrapper' );
  1038. resizeWrapper.append( imageOrLink );
  1039. resizeWrapper.append( resizer );
  1040. widget.element.append( resizeWrapper, true );
  1041. // Remove the old wrapper which could came from e.g. pasted HTML
  1042. // and which could be corrupted (e.g. resizer span has been lost).
  1043. if ( oldResizeWrapper.is( 'span' ) )
  1044. oldResizeWrapper.remove();
  1045. } else {
  1046. widget.wrapper.append( resizer );
  1047. }
  1048. // Calculate values of size variables and mouse offsets.
  1049. resizer.on( 'mousedown', function( evt ) {
  1050. var image = widget.parts.image,
  1051. // "factor" can be either 1 or -1. I.e.: For right-aligned images, we need to
  1052. // subtract the difference to get proper width, etc. Without "factor",
  1053. // resizer starts working the opposite way.
  1054. factor = widget.data.align == 'right' ? -1 : 1,
  1055. // The x-coordinate of the mouse relative to the screen
  1056. // when button gets pressed.
  1057. startX = evt.data.$.screenX,
  1058. startY = evt.data.$.screenY,
  1059. // The initial dimensions and aspect ratio of the image.
  1060. startWidth = image.$.clientWidth,
  1061. startHeight = image.$.clientHeight,
  1062. ratio = startWidth / startHeight,
  1063. listeners = [],
  1064. // A class applied to editable during resizing.
  1065. cursorClass = 'cke_image_s' + ( !~factor ? 'w' : 'e' ),
  1066. nativeEvt, newWidth, newHeight, updateData,
  1067. moveDiffX, moveDiffY, moveRatio;
  1068. // Save the undo snapshot first: before resizing.
  1069. editor.fire( 'saveSnapshot' );
  1070. // Mousemove listeners are removed on mouseup.
  1071. attachToDocuments( 'mousemove', onMouseMove, listeners );
  1072. // Clean up the mousemove listener. Update widget data if valid.
  1073. attachToDocuments( 'mouseup', onMouseUp, listeners );
  1074. // The entire editable will have the special cursor while resizing goes on.
  1075. editable.addClass( cursorClass );
  1076. // This is to always keep the resizer element visible while resizing.
  1077. resizer.addClass( 'cke_image_resizing' );
  1078. // Attaches an event to a global document if inline editor.
  1079. // Additionally, if classic (`iframe`-based) editor, also attaches the same event to `iframe`'s document.
  1080. function attachToDocuments( name, callback, collection ) {
  1081. var globalDoc = CKEDITOR.document,
  1082. listeners = [];
  1083. if ( !doc.equals( globalDoc ) )
  1084. listeners.push( globalDoc.on( name, callback ) );
  1085. listeners.push( doc.on( name, callback ) );
  1086. if ( collection ) {
  1087. for ( var i = listeners.length; i--; )
  1088. collection.push( listeners.pop() );
  1089. }
  1090. }
  1091. // Calculate with first, and then adjust height, preserving ratio.
  1092. function adjustToX() {
  1093. newWidth = startWidth + factor * moveDiffX;
  1094. newHeight = Math.round( newWidth / ratio );
  1095. }
  1096. // Calculate height first, and then adjust width, preserving ratio.
  1097. function adjustToY() {
  1098. newHeight = startHeight - moveDiffY;
  1099. newWidth = Math.round( newHeight * ratio );
  1100. }
  1101. // This is how variables refer to the geometry.
  1102. // Note: x corresponds to moveOffset, this is the position of mouse
  1103. // Note: o corresponds to [startX, startY].
  1104. //
  1105. // +--------------+--------------+
  1106. // | | |
  1107. // | I | II |
  1108. // | | |
  1109. // +------------- o -------------+ _ _ _
  1110. // | | | ^
  1111. // | VI | III | | moveDiffY
  1112. // | | x _ _ _ _ _ v
  1113. // +--------------+---------|----+
  1114. // | |
  1115. // <------->
  1116. // moveDiffX
  1117. function onMouseMove( evt ) {
  1118. nativeEvt = evt.data.$;
  1119. // This is how far the mouse is from the point the button was pressed.
  1120. moveDiffX = nativeEvt.screenX - startX;
  1121. moveDiffY = startY - nativeEvt.screenY;
  1122. // This is the aspect ratio of the move difference.
  1123. moveRatio = Math.abs( moveDiffX / moveDiffY );
  1124. // Left, center or none-aligned widget.
  1125. if ( factor == 1 ) {
  1126. if ( moveDiffX <= 0 ) {
  1127. // Case: IV.
  1128. if ( moveDiffY <= 0 )
  1129. adjustToX();
  1130. // Case: I.
  1131. else {
  1132. if ( moveRatio >= ratio )
  1133. adjustToX();
  1134. else
  1135. adjustToY();
  1136. }
  1137. } else {
  1138. // Case: III.
  1139. if ( moveDiffY <= 0 ) {
  1140. if ( moveRatio >= ratio )
  1141. adjustToY();
  1142. else
  1143. adjustToX();
  1144. }
  1145. // Case: II.
  1146. else {
  1147. adjustToY();
  1148. }
  1149. }
  1150. }
  1151. // Right-aligned widget. It mirrors behaviours, so I becomes II,
  1152. // IV becomes III and vice-versa.
  1153. else {
  1154. if ( moveDiffX <= 0 ) {
  1155. // Case: IV.
  1156. if ( moveDiffY <= 0 ) {
  1157. if ( moveRatio >= ratio )
  1158. adjustToY();
  1159. else
  1160. adjustToX();
  1161. }
  1162. // Case: I.
  1163. else {
  1164. adjustToY();
  1165. }
  1166. } else {
  1167. // Case: III.
  1168. if ( moveDiffY <= 0 )
  1169. adjustToX();
  1170. // Case: II.
  1171. else {
  1172. if ( moveRatio >= ratio ) {
  1173. adjustToX();
  1174. } else {
  1175. adjustToY();
  1176. }
  1177. }
  1178. }
  1179. }
  1180. // Don't update attributes if less than 10.
  1181. // This is to prevent images to visually disappear.
  1182. if ( newWidth >= 15 && newHeight >= 15 ) {
  1183. image.setAttributes( { width: newWidth, height: newHeight } );
  1184. updateData = true;
  1185. } else {
  1186. updateData = false;
  1187. }
  1188. }
  1189. function onMouseUp() {
  1190. var l;
  1191. while ( ( l = listeners.pop() ) )
  1192. l.removeListener();
  1193. // Restore default cursor by removing special class.
  1194. editable.removeClass( cursorClass );
  1195. // This is to bring back the regular behaviour of the resizer.
  1196. resizer.removeClass( 'cke_image_resizing' );
  1197. if ( updateData ) {
  1198. widget.setData( { width: newWidth, height: newHeight } );
  1199. // Save another undo snapshot: after resizing.
  1200. editor.fire( 'saveSnapshot' );
  1201. }
  1202. // Don't update data twice or more.
  1203. updateData = false;
  1204. }
  1205. } );
  1206. // Change the position of the widget resizer when data changes.
  1207. widget.on( 'data', function() {
  1208. resizer[ widget.data.align == 'right' ? 'addClass' : 'removeClass' ]( 'cke_image_resizer_left' );
  1209. } );
  1210. }
  1211. // Integrates widget alignment setting with justify
  1212. // plugin's commands (execution and refreshment).
  1213. // @param {CKEDITOR.editor} editor
  1214. // @param {String} value 'left', 'right', 'center' or 'block'
  1215. function alignCommandIntegrator( editor ) {
  1216. var execCallbacks = [],
  1217. enabled;
  1218. return function( value ) {
  1219. var command = editor.getCommand( 'justify' + value );
  1220. // Most likely, the justify plugin isn't loaded.
  1221. if ( !command )
  1222. return;
  1223. // This command will be manually refreshed along with
  1224. // other commands after exec.
  1225. execCallbacks.push( function() {
  1226. command.refresh( editor, editor.elementPath() );
  1227. } );
  1228. if ( value in { right: 1, left: 1, center: 1 } ) {
  1229. command.on( 'exec', function( evt ) {
  1230. var widget = getFocusedWidget( editor );
  1231. if ( widget ) {
  1232. widget.setData( 'align', value );
  1233. // Once the widget changed its align, all the align commands
  1234. // must be refreshed: the event is to be cancelled.
  1235. for ( var i = execCallbacks.length; i--; )
  1236. execCallbacks[ i ]();
  1237. evt.cancel();
  1238. }
  1239. } );
  1240. }
  1241. command.on( 'refresh', function( evt ) {
  1242. var widget = getFocusedWidget( editor ),
  1243. allowed = { right: 1, left: 1, center: 1 };
  1244. if ( !widget )
  1245. return;
  1246. // Cache "enabled" on first use. This is because filter#checkFeature may
  1247. // not be available during plugin's afterInit in the future — a moment when
  1248. // alignCommandIntegrator is called.
  1249. if ( enabled === undefined )
  1250. enabled = editor.filter.checkFeature( editor.widgets.registered.image.features.align );
  1251. // Don't allow justify commands when widget alignment is disabled (#11004).
  1252. if ( !enabled )
  1253. this.setState( CKEDITOR.TRISTATE_DISABLED );
  1254. else {
  1255. this.setState(
  1256. ( widget.data.align == value ) ? (
  1257. CKEDITOR.TRISTATE_ON
  1258. ) : (
  1259. ( value in allowed ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED
  1260. )
  1261. );
  1262. }
  1263. evt.cancel();
  1264. } );
  1265. };
  1266. }
  1267. function linkCommandIntegrator( editor ) {
  1268. // Nothing to integrate with if link is not loaded.
  1269. if ( !editor.plugins.link )
  1270. return;
  1271. CKEDITOR.on( 'dialogDefinition', function( evt ) {
  1272. var dialog = evt.data;
  1273. if ( dialog.name == 'link' ) {
  1274. var def = dialog.definition;
  1275. var onShow = def.onShow,
  1276. onOk = def.onOk;
  1277. def.onShow = function() {
  1278. var widget = getFocusedWidget( editor ),
  1279. displayTextField = this.getContentElement( 'info', 'linkDisplayText' ).getElement().getParent().getParent();
  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. this.setupContent( widget.data.link || {} );
  1284. // Hide the display text in case of linking image2_chamilo widget.
  1285. displayTextField.hide();
  1286. } else {
  1287. // Make sure that display text is visible, as it might be hidden by image2_chamilo integration
  1288. // before.
  1289. displayTextField.show();
  1290. onShow.apply( this, arguments );
  1291. }
  1292. };
  1293. // Set widget data if linking the widget using
  1294. // link dialog (instead of default action).
  1295. // State shifter handles data change and takes
  1296. // care of internal DOM structure of linked widget.
  1297. def.onOk = function() {
  1298. var widget = getFocusedWidget( editor );
  1299. // Widget cannot be enclosed in a link, i.e.
  1300. // <a>foo<inline widget/>bar</a>
  1301. if ( widget && ( widget.inline ? !widget.wrapper.getAscendant( 'a' ) : 1 ) ) {
  1302. var data = {};
  1303. // Collect data from fields.
  1304. this.commitContent( data );
  1305. // Set collected data to widget.
  1306. widget.setData( 'link', data );
  1307. } else {
  1308. onOk.apply( this, arguments );
  1309. }
  1310. };
  1311. }
  1312. } );
  1313. // Overwrite default behaviour of unlink command.
  1314. editor.getCommand( 'unlink' ).on( 'exec', function( evt ) {
  1315. var widget = getFocusedWidget( editor );
  1316. // Override unlink only when link truly belongs to the widget.
  1317. // If wrapped inline widget in a link, let default unlink work (#11814).
  1318. if ( !widget || !widget.parts.link )
  1319. return;
  1320. widget.setData( 'link', null );
  1321. // Selection (which is fake) may not change if unlinked image in focused widget,
  1322. // i.e. if captioned image. Let's refresh command state manually here.
  1323. this.refresh( editor, editor.elementPath() );
  1324. evt.cancel();
  1325. } );
  1326. // Overwrite default refresh of unlink command.
  1327. editor.getCommand( 'unlink' ).on( 'refresh', function( evt ) {
  1328. var widget = getFocusedWidget( editor );
  1329. if ( !widget )
  1330. return;
  1331. // Note that widget may be wrapped in a link, which
  1332. // does not belong to that widget (#11814).
  1333. this.setState( widget.data.link || widget.wrapper.getAscendant( 'a' ) ?
  1334. CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED );
  1335. evt.cancel();
  1336. } );
  1337. }
  1338. // Returns the focused widget, if of the type specific for this plugin.
  1339. // If no widget is focused, `null` is returned.
  1340. //
  1341. // @param {CKEDITOR.editor}
  1342. // @returns {CKEDITOR.plugins.widget}
  1343. function getFocusedWidget( editor ) {
  1344. var widget = editor.widgets.focused;
  1345. if ( widget && widget.name == 'image' )
  1346. return widget;
  1347. return null;
  1348. }
  1349. // Returns a set of widget allowedContent rules, depending
  1350. // on configurations like config#image2_chamilo_alignClasses or
  1351. // config#image2_chamilo_captionedClass.
  1352. //
  1353. // @param {CKEDITOR.editor}
  1354. // @returns {Object}
  1355. function getWidgetAllowedContent( editor ) {
  1356. var alignClasses = editor.config.image2_chamilo_alignClasses,
  1357. rules = {
  1358. // Widget may need <div> or <p> centering wrapper.
  1359. div: {
  1360. match: centerWrapperChecker( editor )
  1361. },
  1362. p: {
  1363. match: centerWrapperChecker( editor )
  1364. },
  1365. img: {
  1366. attributes: '!src,alt,width,height',
  1367. styles: 'border-width,border-style,border-color,border-radius,background-color'
  1368. },
  1369. figure: {
  1370. classes: '!' + editor.config.image2_chamilo_captionedClass
  1371. },
  1372. figcaption: true
  1373. };
  1374. if ( alignClasses ) {
  1375. // Centering class from the config.
  1376. rules.div.classes = alignClasses[ 1 ];
  1377. rules.p.classes = rules.div.classes;
  1378. // Left/right classes from the config.
  1379. rules.img.classes = alignClasses[ 0 ];
  1380. for (var classI = 2; classI <= 11; classI++) {
  1381. rules.img.classes += ',' + alignClasses[ classI ];
  1382. }
  1383. rules.figure.classes += ',' + rules.img.classes;
  1384. } else {
  1385. // Centering with text-align.
  1386. rules.div.styles = 'text-align';
  1387. rules.p.styles = 'text-align';
  1388. rules.img.styles = 'float,vertical-align';
  1389. rules.figure.styles = 'float,display';
  1390. }
  1391. return rules;
  1392. }
  1393. // Returns a set of widget feature rules, depending
  1394. // on editor configuration. Note that the following may not cover
  1395. // all the possible cases since requiredContent supports a single
  1396. // tag only.
  1397. //
  1398. // @param {CKEDITOR.editor}
  1399. // @returns {Object}
  1400. function getWidgetFeatures( editor ) {
  1401. var alignClasses = editor.config.image2_chamilo_alignClasses,
  1402. features = {
  1403. dimension: {
  1404. requiredContent: 'img[width,height]'
  1405. },
  1406. align: {
  1407. requiredContent: 'img' +
  1408. ( alignClasses ? '(' + alignClasses[ 0 ] + ')' : '{float,vertica-align}' )
  1409. },
  1410. caption: {
  1411. requiredContent: 'figcaption'
  1412. },
  1413. responsive: {
  1414. requiredContent: 'img'
  1415. },
  1416. borderWidth: {
  1417. requiredContent: 'img{border-width}'
  1418. },
  1419. borderStyle: {
  1420. requiredContent: 'img{border-style}'
  1421. },
  1422. borderColor: {
  1423. requiredContent: 'img{border-color}'
  1424. },
  1425. borderRadius: {
  1426. requiredContent: 'img{border-radius}'
  1427. },
  1428. backgroundColor: {
  1429. requiredContent: 'img{background-color}'
  1430. }
  1431. };
  1432. return features;
  1433. }
  1434. // Returns element which is styled, considering current
  1435. // state of the widget.
  1436. //
  1437. // @see CKEDITOR.plugins.widget#applyStyle
  1438. // @param {CKEDITOR.plugins.widget} widget
  1439. // @returns {CKEDITOR.dom.element}
  1440. function getStyleableElement( widget ) {
  1441. return widget.data.hasCaption ? widget.element : widget.parts.image;
  1442. }
  1443. } )();
  1444. /**
  1445. * A CSS class applied to the `<figure>` element of a captioned image.
  1446. *
  1447. * Read more in the [documentation](#!/guide/dev_captionedimage) and see the
  1448. * [SDK sample](http://sdk.ckeditor.com/samples/captionedimage.html).
  1449. *
  1450. * // Changes the class to "captionedImage".
  1451. * config.image2_chamilo_captionedClass = 'captionedImage';
  1452. *
  1453. * @cfg {String} [image2_chamilo_captionedClass='image']
  1454. * @member CKEDITOR.config
  1455. */
  1456. CKEDITOR.config.image2_chamilo_captionedClass = 'image';
  1457. /**
  1458. * Determines whether dimension inputs should be automatically filled when the image URL changes in the Enhanced Image
  1459. * plugin dialog window.
  1460. *
  1461. * Read more in the [documentation](#!/guide/dev_captionedimage) and see the
  1462. * [SDK sample](http://sdk.ckeditor.com/samples/captionedimage.html).
  1463. *
  1464. * config.image2_chamilo_prefillDimensions = false;
  1465. *
  1466. * @since 4.5
  1467. * @cfg {Boolean} [image2_chamilo_prefillDimensions=true]
  1468. * @member CKEDITOR.config
  1469. */
  1470. /**
  1471. * Disables the image resizer. By default the resizer is enabled.
  1472. *
  1473. * Read more in the [documentation](#!/guide/dev_captionedimage) and see the
  1474. * [SDK sample](http://sdk.ckeditor.com/samples/captionedimage.html).
  1475. *
  1476. * config.image2_chamilo_disableResizer = true;
  1477. *
  1478. * @since 4.5
  1479. * @cfg {Boolean} [image2_chamilo_disableResizer=false]
  1480. * @member CKEDITOR.config
  1481. */
  1482. /**
  1483. * CSS classes applied to aligned images. Useful to take control over the way
  1484. * the images are aligned, i.e. to customize output HTML and integrate external stylesheets.
  1485. *
  1486. * Classes should be defined in an array of three elements, containing left, center, and right
  1487. * alignment classes, respectively. For example:
  1488. *
  1489. * config.image2_chamilo_alignClasses = [ 'align-left', 'align-center', 'align-right' ];
  1490. *
  1491. * **Note**: Once this configuration option is set, the plugin will no longer produce inline
  1492. * styles for alignment. It means that e.g. the following HTML will be produced:
  1493. *
  1494. * <img alt="My image" class="custom-center-class" src="foo.png" />
  1495. *
  1496. * instead of:
  1497. *
  1498. * <img alt="My image" style="float:left" src="foo.png" />
  1499. *
  1500. * **Note**: Once this configuration option is set, corresponding style definitions
  1501. * must be supplied to the editor:
  1502. *
  1503. * * For [classic editor](#!/guide/dev_framed) it can be done by defining additional
  1504. * styles in the {@link CKEDITOR.config#contentsCss stylesheets loaded by the editor}. The same
  1505. * styles must be provided on the target page where the content will be loaded.
  1506. * * For [inline editor](#!/guide/dev_inline) the styles can be defined directly
  1507. * with `<style> ... <style>` or `<link href="..." rel="stylesheet">`, i.e. within the `<head>`
  1508. * of the page.
  1509. *
  1510. * For example, considering the following configuration:
  1511. *
  1512. * config.image2_chamilo_alignClasses = [ 'align-left', 'align-center', 'align-right' ];
  1513. *
  1514. * CSS rules can be defined as follows:
  1515. *
  1516. * .align-left {
  1517. * float: left;
  1518. * }
  1519. *
  1520. * .align-right {
  1521. * float: right;
  1522. * }
  1523. *
  1524. * .align-center {
  1525. * text-align: center;
  1526. * }
  1527. *
  1528. * .align-center > figure {
  1529. * display: inline-block;
  1530. * }
  1531. *
  1532. * Read more in the [documentation](#!/guide/dev_captionedimage) and see the
  1533. * [SDK sample](http://sdk.ckeditor.com/samples/captionedimage.html).
  1534. *
  1535. * @since 4.4
  1536. * @cfg {String[]} [image2_chamilo_alignClasses=null]
  1537. * @member CKEDITOR.config
  1538. */
  1539. /**
  1540. * Determines whether alternative text is required for the captioned image.
  1541. *
  1542. * config.image2_chamilo_altRequired = true;
  1543. *
  1544. * Read more in the [documentation](#!/guide/dev_captionedimage) and see the
  1545. * [SDK sample](http://sdk.ckeditor.com/samples/captionedimage.html).
  1546. *
  1547. * @since 4.6.0
  1548. * @cfg {Boolean} [image2_chamilo_altRequired=false]
  1549. * @member CKEDITOR.config
  1550. */