image2_chamilo.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  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. /**
  6. * @fileOverview Image plugin based on Widgets API
  7. */
  8. 'use strict';
  9. CKEDITOR.dialog.add( 'image2_chamilo', function( editor ) {
  10. // RegExp: 123, 123px, empty string ""
  11. var regexGetSizeOrEmpty = /(^\s*(\d+)(px)?\s*$)|^$/i,
  12. lockButtonId = CKEDITOR.tools.getNextId(),
  13. resetButtonId = CKEDITOR.tools.getNextId(),
  14. lang = editor.lang.image2_chamilo,
  15. commonLang = editor.lang.common,
  16. lockResetStyle = 'margin-top:18px;width:40px;height:20px;',
  17. lockResetHtml = new CKEDITOR.template(
  18. '<div>' +
  19. '<a href="javascript:void(0)" tabindex="-1" title="' + lang.lockRatio + '" class="cke_btn_locked" id="{lockButtonId}" role="checkbox">' +
  20. '<span class="cke_icon"></span>' +
  21. '<span class="cke_label">' + lang.lockRatio + '</span>' +
  22. '</a>' +
  23. '<a href="javascript:void(0)" tabindex="-1" title="' + lang.resetSize + '" class="cke_btn_reset" id="{resetButtonId}" role="button">' +
  24. '<span class="cke_label">' + lang.resetSize + '</span>' +
  25. '</a>' +
  26. '</div>' ).output( {
  27. lockButtonId: lockButtonId,
  28. resetButtonId: resetButtonId
  29. } ),
  30. helpers = CKEDITOR.plugins.image2_chamilo,
  31. // Editor instance configuration.
  32. config = editor.config,
  33. hasFileBrowser = !!( config.filebrowserImageBrowseUrl || config.filebrowserBrowseUrl ),
  34. // Content restrictions defined by the widget which
  35. // impact on dialog structure and presence of fields.
  36. features = editor.widgets.registered.image.features,
  37. // Functions inherited from image2_chamilo plugin.
  38. getNatural = helpers.getNatural,
  39. // Global variables referring to the dialog's context.
  40. doc, widget, image,
  41. // Global variable referring to this dialog's image pre-loader.
  42. preLoader,
  43. // Global variables holding the original size of the image.
  44. domWidth, domHeight,
  45. // Global variables related to image pre-loading.
  46. preLoadedWidth, preLoadedHeight, srcChanged,
  47. // Global variables related to size locking.
  48. lockRatio, userDefinedLock,
  49. // Global variables referring to dialog fields and elements.
  50. lockButton, resetButton, widthField, heightField,
  51. natural;
  52. // Validates dimension. Allowed values are:
  53. // "123px", "123", "" (empty string)
  54. function validateDimension() {
  55. var match = this.getValue().match( regexGetSizeOrEmpty ),
  56. isValid = !!( match && parseInt( match[ 1 ], 10 ) !== 0 );
  57. if ( !isValid )
  58. alert( commonLang[ 'invalid' + CKEDITOR.tools.capitalize( this.id ) ] ); // jshint ignore:line
  59. return isValid;
  60. }
  61. // Creates a function that pre-loads images. The callback function passes
  62. // [image, width, height] or null if loading failed.
  63. //
  64. // @returns {Function}
  65. function createPreLoader() {
  66. var image = doc.createElement( 'img' ),
  67. listeners = [];
  68. function addListener( event, callback ) {
  69. listeners.push( image.once( event, function( evt ) {
  70. removeListeners();
  71. callback( evt );
  72. } ) );
  73. }
  74. function removeListeners() {
  75. var l;
  76. while ( ( l = listeners.pop() ) )
  77. l.removeListener();
  78. }
  79. // @param {String} src.
  80. // @param {Function} callback.
  81. return function( src, callback, scope ) {
  82. addListener( 'load', function() {
  83. // Don't use image.$.(width|height) since it's buggy in IE9-10 (#11159)
  84. var dimensions = getNatural( image );
  85. callback.call( scope, image, dimensions.width, dimensions.height );
  86. } );
  87. addListener( 'error', function() {
  88. callback( null );
  89. } );
  90. addListener( 'abort', function() {
  91. callback( null );
  92. } );
  93. image.setAttribute( 'src',
  94. ( config.baseHref || '' ) + src + '?' + Math.random().toString( 16 ).substring( 2 ) );
  95. };
  96. }
  97. // This function updates width and height fields once the
  98. // "src" field is altered. Along with dimensions, also the
  99. // dimensions lock is adjusted.
  100. function onChangeSrc() {
  101. var value = this.getValue();
  102. toggleDimensions( false );
  103. // Remember that src is different than default.
  104. if ( value !== widget.data.src ) {
  105. // Update dimensions of the image once it's preloaded.
  106. preLoader( value, function( image, width, height ) {
  107. // Re-enable width and height fields.
  108. toggleDimensions( true );
  109. // There was problem loading the image. Unlock ratio.
  110. if ( !image )
  111. return toggleLockRatio( false );
  112. // Fill width field with the width of the new image.
  113. widthField.setValue( editor.config.image2_chamilo_prefillDimensions === false ? 0 : width );
  114. // Fill height field with the height of the new image.
  115. heightField.setValue( editor.config.image2_chamilo_prefillDimensions === false ? 0 : height );
  116. // Cache the new width.
  117. preLoadedWidth = width;
  118. // Cache the new height.
  119. preLoadedHeight = height;
  120. // Check for new lock value if image exist.
  121. toggleLockRatio( helpers.checkHasNaturalRatio( image ) );
  122. } );
  123. srcChanged = true;
  124. }
  125. // Value is the same as in widget data but is was
  126. // modified back in time. Roll back dimensions when restoring
  127. // default src.
  128. else if ( srcChanged ) {
  129. // Re-enable width and height fields.
  130. toggleDimensions( true );
  131. // Restore width field with cached width.
  132. widthField.setValue( domWidth );
  133. // Restore height field with cached height.
  134. heightField.setValue( domHeight );
  135. // Src equals default one back again.
  136. srcChanged = false;
  137. }
  138. // Value is the same as in widget data and it hadn't
  139. // been modified.
  140. else {
  141. // Re-enable width and height fields.
  142. toggleDimensions( true );
  143. }
  144. }
  145. function onChangeDimension() {
  146. // If ratio is un-locked, then we don't care what's next.
  147. if ( !lockRatio )
  148. return;
  149. var value = this.getValue();
  150. // No reason to auto-scale or unlock if the field is empty.
  151. if ( !value )
  152. return;
  153. // If the value of the field is invalid (e.g. with %), unlock ratio.
  154. if ( !value.match( regexGetSizeOrEmpty ) )
  155. toggleLockRatio( false );
  156. // No automatic re-scale when dimension is '0'.
  157. if ( value === '0' )
  158. return;
  159. var isWidth = this.id == 'width',
  160. // If dialog opened for the new image, domWidth and domHeight
  161. // will be empty. Use dimensions from pre-loader in such case instead.
  162. width = domWidth || preLoadedWidth,
  163. height = domHeight || preLoadedHeight;
  164. // If changing width, then auto-scale height.
  165. if ( isWidth )
  166. value = Math.round( height * ( value / width ) );
  167. // If changing height, then auto-scale width.
  168. else
  169. value = Math.round( width * ( value / height ) );
  170. // If the value is a number, apply it to the other field.
  171. if ( !isNaN( value ) )
  172. ( isWidth ? heightField : widthField ).setValue( value );
  173. }
  174. // Set-up function for lock and reset buttons:
  175. // * Adds lock and reset buttons to focusables. Check if button exist first
  176. // because it may be disabled e.g. due to ACF restrictions.
  177. // * Register mouseover and mouseout event listeners for UI manipulations.
  178. // * Register click event listeners for buttons.
  179. function onLoadLockReset() {
  180. var dialog = this.getDialog();
  181. function setupMouseClasses( el ) {
  182. el.on( 'mouseover', function() {
  183. this.addClass( 'cke_btn_over' );
  184. }, el );
  185. el.on( 'mouseout', function() {
  186. this.removeClass( 'cke_btn_over' );
  187. }, el );
  188. }
  189. // Create references to lock and reset buttons for this dialog instance.
  190. lockButton = doc.getById( lockButtonId );
  191. resetButton = doc.getById( resetButtonId );
  192. // Activate (Un)LockRatio button
  193. if ( lockButton ) {
  194. // Consider that there's an additional focusable field
  195. // in the dialog when the "browse" button is visible.
  196. dialog.addFocusable( lockButton, 4 + hasFileBrowser );
  197. lockButton.on( 'click', function( evt ) {
  198. toggleLockRatio();
  199. evt.data && evt.data.preventDefault();
  200. }, this.getDialog() );
  201. setupMouseClasses( lockButton );
  202. }
  203. // Activate the reset size button.
  204. if ( resetButton ) {
  205. // Consider that there's an additional focusable field
  206. // in the dialog when the "browse" button is visible.
  207. dialog.addFocusable( resetButton, 5 + hasFileBrowser );
  208. // Fills width and height fields with the original dimensions of the
  209. // image (stored in widget#data since widget#init).
  210. resetButton.on( 'click', function( evt ) {
  211. // If there's a new image loaded, reset button should revert
  212. // cached dimensions of pre-loaded DOM element.
  213. if ( srcChanged ) {
  214. widthField.setValue( preLoadedWidth );
  215. heightField.setValue( preLoadedHeight );
  216. }
  217. // If the old image remains, reset button should revert
  218. // dimensions as loaded when the dialog was first shown.
  219. else {
  220. widthField.setValue( domWidth );
  221. heightField.setValue( domHeight );
  222. }
  223. evt.data && evt.data.preventDefault();
  224. }, this );
  225. setupMouseClasses( resetButton );
  226. }
  227. }
  228. function toggleLockRatio( enable ) {
  229. // No locking if there's no radio (i.e. due to ACF).
  230. if ( !lockButton )
  231. return;
  232. if ( typeof enable == 'boolean' ) {
  233. // If user explicitly wants to decide whether
  234. // to lock or not, don't do anything.
  235. if ( userDefinedLock )
  236. return;
  237. lockRatio = enable;
  238. }
  239. // Undefined. User changed lock value.
  240. else {
  241. var width = widthField.getValue(),
  242. height;
  243. userDefinedLock = true;
  244. lockRatio = !lockRatio;
  245. // Automatically adjust height to width to match
  246. // the original ratio (based on dom- dimensions).
  247. if ( lockRatio && width ) {
  248. height = domHeight / domWidth * width;
  249. if ( !isNaN( height ) )
  250. heightField.setValue( Math.round( height ) );
  251. }
  252. }
  253. lockButton[ lockRatio ? 'removeClass' : 'addClass' ]( 'cke_btn_unlocked' );
  254. lockButton.setAttribute( 'aria-checked', lockRatio );
  255. // Ratio button hc presentation - WHITE SQUARE / BLACK SQUARE
  256. if ( CKEDITOR.env.hc ) {
  257. var icon = lockButton.getChild( 0 );
  258. icon.setHtml( lockRatio ? CKEDITOR.env.ie ? '\u25A0' : '\u25A3' : CKEDITOR.env.ie ? '\u25A1' : '\u25A2' );
  259. }
  260. }
  261. function toggleDimensions( enable ) {
  262. var method = enable ? 'enable' : 'disable';
  263. widthField[ method ]();
  264. heightField[ method ]();
  265. }
  266. var srcBoxChildren = [
  267. {
  268. id: 'src',
  269. type: 'text',
  270. label: commonLang.url,
  271. onKeyup: onChangeSrc,
  272. onChange: onChangeSrc,
  273. setup: function( widget ) {
  274. this.setValue( widget.data.src );
  275. },
  276. commit: function( widget ) {
  277. widget.setData( 'src', this.getValue() );
  278. },
  279. validate: CKEDITOR.dialog.validate.notEmpty( lang.urlMissing )
  280. }
  281. ];
  282. // Render the "Browse" button on demand to avoid an "empty" (hidden child)
  283. // space in dialog layout that distorts the UI.
  284. if ( hasFileBrowser ) {
  285. srcBoxChildren.push( {
  286. type: 'button',
  287. id: 'browse',
  288. // v-align with the 'txtUrl' field.
  289. // TODO: We need something better than a fixed size here.
  290. style: 'display:inline-block;margin-top:14px;',
  291. align: 'center',
  292. label: editor.lang.common.browseServer,
  293. hidden: true,
  294. filebrowser: 'info:src'
  295. } );
  296. }
  297. return {
  298. title: lang.title,
  299. minWidth: 250,
  300. minHeight: 100,
  301. onLoad: function() {
  302. // Create a "global" reference to the document for this dialog instance.
  303. doc = this._.element.getDocument();
  304. // Create a pre-loader used for determining dimensions of new images.
  305. preLoader = createPreLoader();
  306. },
  307. onShow: function() {
  308. // Create a "global" reference to edited widget.
  309. widget = this.widget;
  310. // Create a "global" reference to widget's image.
  311. image = widget.parts.image;
  312. // Reset global variables.
  313. srcChanged = userDefinedLock = lockRatio = false;
  314. // Natural dimensions of the image.
  315. natural = getNatural( image );
  316. // Get the natural width of the image.
  317. preLoadedWidth = domWidth = natural.width;
  318. // Get the natural height of the image.
  319. preLoadedHeight = domHeight = natural.height;
  320. },
  321. contents: [
  322. {
  323. id: 'info',
  324. label: lang.infoTab,
  325. elements: [
  326. {
  327. type: 'vbox',
  328. padding: 0,
  329. children: [
  330. {
  331. type: 'hbox',
  332. widths: [ '100%' ],
  333. className: 'cke_dialog_image_url',
  334. children: srcBoxChildren
  335. }
  336. ]
  337. },
  338. {
  339. id: 'alt',
  340. type: 'text',
  341. label: lang.alt,
  342. setup: function( widget ) {
  343. this.setValue( widget.data.alt );
  344. },
  345. commit: function( widget ) {
  346. widget.setData( 'alt', this.getValue() );
  347. },
  348. validate: editor.config.image2_chamilo_altRequired === true ? CKEDITOR.dialog.validate.notEmpty( lang.altMissing ) : null
  349. },
  350. {
  351. type: 'hbox',
  352. widths: [ '25%', '25%', '50%' ],
  353. requiredContent: features.dimension.requiredContent,
  354. children: [
  355. {
  356. type: 'text',
  357. width: '45px',
  358. id: 'width',
  359. label: commonLang.width,
  360. validate: validateDimension,
  361. onKeyUp: onChangeDimension,
  362. onLoad: function() {
  363. widthField = this;
  364. },
  365. setup: function( widget ) {
  366. this.setValue( widget.data.width );
  367. },
  368. commit: function( widget ) {
  369. widget.setData( 'width', this.getValue() );
  370. }
  371. },
  372. {
  373. type: 'text',
  374. id: 'height',
  375. width: '45px',
  376. label: commonLang.height,
  377. validate: validateDimension,
  378. onKeyUp: onChangeDimension,
  379. onLoad: function() {
  380. heightField = this;
  381. },
  382. setup: function( widget ) {
  383. this.setValue( widget.data.height );
  384. },
  385. commit: function( widget ) {
  386. widget.setData( 'height', this.getValue() );
  387. }
  388. },
  389. {
  390. id: 'lock',
  391. type: 'html',
  392. style: lockResetStyle,
  393. onLoad: onLoadLockReset,
  394. setup: function( widget ) {
  395. toggleLockRatio( widget.data.lock );
  396. },
  397. commit: function( widget ) {
  398. widget.setData( 'lock', lockRatio );
  399. },
  400. html: lockResetHtml
  401. }
  402. ]
  403. },
  404. {
  405. type: 'hbox',
  406. id: 'alignment',
  407. requiredContent: features.align.requiredContent,
  408. children: [
  409. {
  410. id: 'align',
  411. type: 'select',
  412. items: [
  413. [ commonLang.alignNone, 'none' ],
  414. [ commonLang.alignLeft, 'left' ],
  415. [ commonLang.alignCenter, 'center' ],
  416. [ commonLang.alignRight, 'right' ],
  417. [ lang.alignBaseline, 'baseline'],
  418. [ lang.alignTop, 'top'],
  419. [ lang.alignBottom, 'bottom'],
  420. [ lang.alignMiddle, 'middle'],
  421. [ lang.alignSuper, 'super'],
  422. [ lang.alignSub, 'sub'],
  423. [ lang.alignTextTop, 'text-top'],
  424. [ lang.alignTextBottom, 'text-bottom'],
  425. ],
  426. label: commonLang.align,
  427. setup: function( widget ) {
  428. this.setValue( widget.data.align );
  429. },
  430. commit: function( widget ) {
  431. widget.setData( 'align', this.getValue() );
  432. }
  433. }
  434. ]
  435. },
  436. {
  437. id: 'hasCaption',
  438. type: 'checkbox',
  439. label: lang.captioned,
  440. requiredContent: features.caption.requiredContent,
  441. setup: function( widget ) {
  442. this.setValue( widget.data.hasCaption );
  443. },
  444. commit: function( widget ) {
  445. widget.setData( 'hasCaption', this.getValue() );
  446. }
  447. },
  448. {
  449. id: 'isResponsive',
  450. type: 'checkbox',
  451. label: lang.responsive,
  452. requiredContent: features.responsive.requiredContent,
  453. setup: function ( widget ) {
  454. this.setValue( widget.data.isResponsive );
  455. },
  456. commit: function ( widget ) {
  457. var img = widget;
  458. if (widget.element.$.tagName === 'FIGURE') {
  459. img = widget.element.$.firstChild;
  460. }
  461. img.className += ' img-responsive ';
  462. widget.setData( 'isResponsive', this.getValue() );
  463. }
  464. }
  465. ]
  466. },
  467. {
  468. id: 'Upload',
  469. hidden: true,
  470. filebrowser: 'uploadButton',
  471. label: lang.uploadTab,
  472. elements: [
  473. {
  474. type: 'file',
  475. id: 'upload',
  476. label: lang.btnUpload,
  477. style: 'height:40px'
  478. },
  479. {
  480. type: 'fileButton',
  481. id: 'uploadButton',
  482. filebrowser: 'info:src',
  483. label: lang.btnUpload,
  484. 'for': [ 'Upload', 'upload' ]
  485. }
  486. ]
  487. }
  488. ]
  489. };
  490. } );