1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798 |
- /**
- * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
- * CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
- */
-
- 'use strict';
-
- ( function() {
-
- var template = '<img alt="" src="" />',
- templateBlock = new CKEDITOR.template(
- '<figure class="{captionedClass}">' +
- template +
- '<figcaption>{captionPlaceholder}</figcaption>' +
- '</figure>' ),
- alignmentsObj = { left: 0, center: 1, right: 2 },
- regexPercent = /^\s*(\d+\%)\s*$/i;
-
- CKEDITOR.plugins.add( 'image2', {
- // jscs:disable maximumLineLength
- lang: 'af,ar,az,bg,bn,bs,ca,cs,cy,da,de,de-ch,el,en,en-au,en-ca,en-gb,eo,es,es-mx,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%
- // jscs:enable maximumLineLength
- requires: 'widget,dialog',
- icons: 'image',
- hidpi: true,
-
- onLoad: function() {
- CKEDITOR.addCss(
- '.cke_image_nocaption{' +
- // This is to remove unwanted space so resize
- // wrapper is displayed property.
- 'line-height:0' +
- '}' +
- '.cke_editable.cke_image_sw, .cke_editable.cke_image_sw *{cursor:sw-resize !important}' +
- '.cke_editable.cke_image_se, .cke_editable.cke_image_se *{cursor:se-resize !important}' +
- '.cke_image_resizer{' +
- 'display:none;' +
- 'position:absolute;' +
- 'width:10px;' +
- 'height:10px;' +
- 'bottom:-5px;' +
- 'right:-5px;' +
- 'background:#000;' +
- 'outline:1px solid #fff;' +
- // Prevent drag handler from being misplaced (https://dev.ckeditor.com/ticket/11207).
- 'line-height:0;' +
- 'cursor:se-resize;' +
- '}' +
- '.cke_image_resizer_wrapper{' +
- 'position:relative;' +
- 'display:inline-block;' +
- 'line-height:0;' +
- '}' +
- // Bottom-left corner style of the resizer.
- '.cke_image_resizer.cke_image_resizer_left{' +
- 'right:auto;' +
- 'left:-5px;' +
- 'cursor:sw-resize;' +
- '}' +
- '.cke_widget_wrapper:hover .cke_image_resizer,' +
- '.cke_image_resizer.cke_image_resizing{' +
- 'display:block' +
- '}' +
- // Hide resizer in read only mode (#2816).
- '.cke_editable[contenteditable="false"] .cke_image_resizer{' +
- 'display:none;' +
- '}' +
- // Expand widget wrapper when linked inline image.
- '.cke_widget_wrapper>a{' +
- 'display:inline-block' +
- '}' );
- },
-
- init: function( editor ) {
- // Abort when Easyimage is to be loaded since this plugins
- // share the same functionality (#1791).
- if ( editor.plugins.detectConflict( 'image2', [ 'easyimage' ] ) ) {
- return;
- }
-
- // Adapts configuration from original image plugin. Should be removed
- // when we'll rename image2 to image.
- var config = editor.config,
- lang = editor.lang.image2,
- image = widgetDef( editor );
-
- // Since filebrowser plugin discovers config properties by dialog (plugin?)
- // names (sic!), this hack will be necessary as long as Image2 is not named
- // Image. And since Image2 will never be Image, for sure some filebrowser logic
- // got to be refined.
- config.filebrowserImage2BrowseUrl = config.filebrowserImageBrowseUrl;
- config.filebrowserImage2UploadUrl = config.filebrowserImageUploadUrl;
-
- // Add custom elementspath names to widget definition.
- image.pathName = lang.pathName;
- image.editables.caption.pathName = lang.pathNameCaption;
-
- // Register the widget.
- editor.widgets.add( 'image', image );
-
- // Add toolbar button for this plugin.
- editor.ui.addButton && editor.ui.addButton( 'Image', {
- label: editor.lang.common.image,
- command: 'image',
- toolbar: 'insert,10'
- } );
-
- // Register context menu option for editing widget.
- if ( editor.contextMenu ) {
- editor.addMenuGroup( 'image', 10 );
-
- editor.addMenuItem( 'image', {
- label: lang.menu,
- command: 'image',
- group: 'image'
- } );
- }
-
- CKEDITOR.dialog.add( 'image2', this.path + 'dialogs/image2.js' );
- },
-
- afterInit: function( editor ) {
- // Integrate with align commands (justify plugin).
- var align = { left: 1, right: 1, center: 1, block: 1 },
- integrate = alignCommandIntegrator( editor );
-
- for ( var value in align )
- integrate( value );
-
- // Integrate with link commands (link plugin).
- linkCommandIntegrator( editor );
- }
- } );
-
- // Wiget states (forms) depending on alignment and configuration.
- //
- // Non-captioned widget (inline styles)
- // ┌──────┬───────────────────────────────┬─────────────────────────────┐
- // │Align │Internal form │Data │
- // ├──────┼───────────────────────────────┼─────────────────────────────┤
- // │none │<wrapper> │<img /> │
- // │ │ <img /> │ │
- // │ │</wrapper> │ │
- // ├──────┼───────────────────────────────┼─────────────────────────────┤
- // │left │<wrapper style=”float:left”> │<img style=”float:left” /> │
- // │ │ <img /> │ │
- // │ │</wrapper> │ │
- // ├──────┼───────────────────────────────┼─────────────────────────────┤
- // │center│<wrapper> │<p style=”text-align:center”>│
- // │ │ <p style=”text-align:center”> │ <img /> │
- // │ │ <img /> │</p> │
- // │ │ </p> │ │
- // │ │</wrapper> │ │
- // ├──────┼───────────────────────────────┼─────────────────────────────┤
- // │right │<wrapper style=”float:right”> │<img style=”float:right” /> │
- // │ │ <img /> │ │
- // │ │</wrapper> │ │
- // └──────┴───────────────────────────────┴─────────────────────────────┘
- //
- // Non-captioned widget (config.image2_alignClasses defined)
- // ┌──────┬───────────────────────────────┬─────────────────────────────┐
- // │Align │Internal form │Data │
- // ├──────┼───────────────────────────────┼─────────────────────────────┤
- // │none │<wrapper> │<img /> │
- // │ │ <img /> │ │
- // │ │</wrapper> │ │
- // ├──────┼───────────────────────────────┼─────────────────────────────┤
- // │left │<wrapper class=”left”> │<img class=”left” /> │
- // │ │ <img /> │ │
- // │ │</wrapper> │ │
- // ├──────┼───────────────────────────────┼─────────────────────────────┤
- // │center│<wrapper> │<p class=”center”> │
- // │ │ <p class=”center”> │ <img /> │
- // │ │ <img /> │</p> │
- // │ │ </p> │ │
- // │ │</wrapper> │ │
- // ├──────┼───────────────────────────────┼─────────────────────────────┤
- // │right │<wrapper class=”right”> │<img class=”right” /> │
- // │ │ <img /> │ │
- // │ │</wrapper> │ │
- // └──────┴───────────────────────────────┴─────────────────────────────┘
- //
- // Captioned widget (inline styles)
- // ┌──────┬────────────────────────────────────────┬────────────────────────────────────────┐
- // │Align │Internal form │Data │
- // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
- // │none │<wrapper> │<figure /> │
- // │ │ <figure /> │ │
- // │ │</wrapper> │ │
- // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
- // │left │<wrapper style=”float:left”> │<figure style=”float:left” /> │
- // │ │ <figure /> │ │
- // │ │</wrapper> │ │
- // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
- // │center│<wrapper style=”text-align:center”> │<div style=”text-align:center”> │
- // │ │ <figure style=”display:inline-block” />│ <figure style=”display:inline-block” />│
- // │ │</wrapper> │</p> │
- // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
- // │right │<wrapper style=”float:right”> │<figure style=”float:right” /> │
- // │ │ <figure /> │ │
- // │ │</wrapper> │ │
- // └──────┴────────────────────────────────────────┴────────────────────────────────────────┘
- //
- // Captioned widget (config.image2_alignClasses defined)
- // ┌──────┬────────────────────────────────────────┬────────────────────────────────────────┐
- // │Align │Internal form │Data │
- // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
- // │none │<wrapper> │<figure /> │
- // │ │ <figure /> │ │
- // │ │</wrapper> │ │
- // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
- // │left │<wrapper class=”left”> │<figure class=”left” /> │
- // │ │ <figure /> │ │
- // │ │</wrapper> │ │
- // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
- // │center│<wrapper class=”center”> │<div class=”center”> │
- // │ │ <figure /> │ <figure /> │
- // │ │</wrapper> │</p> │
- // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
- // │right │<wrapper class=”right”> │<figure class=”right” /> │
- // │ │ <figure /> │ │
- // │ │</wrapper> │ │
- // └──────┴────────────────────────────────────────┴────────────────────────────────────────┘
- //
- // @param {CKEDITOR.editor}
- // @returns {Object}
- function widgetDef( editor ) {
- var alignClasses = editor.config.image2_alignClasses,
- captionedClass = editor.config.image2_captionedClass;
-
- function deflate() {
- if ( this.deflated )
- return;
-
- // Remember whether widget was focused before destroyed.
- if ( editor.widgets.focused == this.widget )
- this.focused = true;
-
- editor.widgets.destroy( this.widget );
-
- // Mark widget was destroyed.
- this.deflated = true;
- }
-
- function inflate() {
- var editable = editor.editable(),
- doc = editor.document;
-
- // Create a new widget. This widget will be either captioned
- // non-captioned, block or inline according to what is the
- // new state of the widget.
- if ( this.deflated ) {
- this.widget = editor.widgets.initOn( this.element, 'image', this.widget.data );
-
- // Once widget was re-created, it may become an inline element without
- // block wrapper (i.e. when unaligned, end not captioned). Let's do some
- // sort of autoparagraphing here (https://dev.ckeditor.com/ticket/10853).
- if ( this.widget.inline && !( new CKEDITOR.dom.elementPath( this.widget.wrapper, editable ).block ) ) {
- var block = doc.createElement( editor.activeEnterMode == CKEDITOR.ENTER_P ? 'p' : 'div' );
- block.replace( this.widget.wrapper );
- this.widget.wrapper.move( block );
- }
-
- // The focus must be transferred from the old one (destroyed)
- // to the new one (just created).
- if ( this.focused ) {
- this.widget.focus();
- delete this.focused;
- }
-
- delete this.deflated;
- }
-
- // If now widget was destroyed just update wrapper's alignment.
- // According to the new state.
- else {
- setWrapperAlign( this.widget, alignClasses );
- }
- }
-
- return {
- allowedContent: getWidgetAllowedContent( editor ),
-
- requiredContent: 'img[src,alt]',
-
- features: getWidgetFeatures( editor ),
-
- styleableElements: 'img figure',
-
- // This widget converts style-driven dimensions to attributes.
- contentTransformations: [
- [ 'img[width]: sizeToAttribute' ]
- ],
-
- // This widget has an editable caption.
- editables: {
- caption: {
- selector: 'figcaption',
- allowedContent: 'br em strong sub sup u s; a[!href,target]'
- }
- },
-
- parts: {
- image: 'img',
- caption: 'figcaption'
- // parts#link defined in widget#init
- },
-
- // The name of this widget's dialog.
- dialog: 'image2',
-
- // Template of the widget: plain image.
- template: template,
-
- data: function() {
- var features = this.features;
-
- // Image can't be captioned when figcaption is disallowed (https://dev.ckeditor.com/ticket/11004).
- if ( this.data.hasCaption && !editor.filter.checkFeature( features.caption ) )
- this.data.hasCaption = false;
-
- // Image can't be aligned when floating is disallowed (https://dev.ckeditor.com/ticket/11004).
- if ( this.data.align != 'none' && !editor.filter.checkFeature( features.align ) )
- this.data.align = 'none';
-
- // Convert the internal form of the widget from the old state to the new one.
- this.shiftState( {
- widget: this,
- element: this.element,
- oldData: this.oldData,
- newData: this.data,
- deflate: deflate,
- inflate: inflate
- } );
-
- // Update widget.parts.link since it will not auto-update unless widget
- // is destroyed and re-inited.
- if ( !this.data.link ) {
- if ( this.parts.link )
- delete this.parts.link;
- } else {
- if ( !this.parts.link )
- this.parts.link = this.parts.image.getParent();
- }
-
- this.parts.image.setAttributes( {
- src: this.data.src,
-
- // This internal is required by the editor.
- 'data-cke-saved-src': this.data.src,
-
- alt: this.data.alt
- } );
-
- // If shifting non-captioned -> captioned, remove classes
- // related to styles from <img/>.
- if ( this.oldData && !this.oldData.hasCaption && this.data.hasCaption ) {
- for ( var c in this.data.classes )
- this.parts.image.removeClass( c );
- }
-
- // Set dimensions of the image according to gathered data.
- // Do it only when the attributes are allowed (https://dev.ckeditor.com/ticket/11004).
- if ( editor.filter.checkFeature( features.dimension ) )
- setDimensions( this );
-
- // Cache current data.
- this.oldData = CKEDITOR.tools.extend( {}, this.data );
- },
-
- init: function() {
- var helpers = CKEDITOR.plugins.image2,
- image = this.parts.image,
- legacyLockBehavior = this.ready ? helpers.checkHasNaturalRatio( image ) : true,
- data = {
- hasCaption: !!this.parts.caption,
- src: image.getAttribute( 'src' ),
- alt: image.getAttribute( 'alt' ) || '',
- width: image.getAttribute( 'width' ) || '',
- height: image.getAttribute( 'height' ) || '',
-
- // Lock ratio should respect the value of the config.image2_defaultLockRatio.
- // If the variable is not set, then it fallback to the legacy one
- // (#5219, https://dev.ckeditor.com/ticket/10833).
- lock: editor.config.image2_defaultLockRatio !== undefined ?
- editor.config.image2_defaultLockRatio : legacyLockBehavior
- };
-
- // If we used 'a' in widget#parts definition, it could happen that
- // selected element is a child of widget.parts#caption. Since there's no clever
- // way to solve it with CSS selectors, it's done like that. (https://dev.ckeditor.com/ticket/11783).
- var link = image.getAscendant( 'a' );
-
- if ( link && this.wrapper.contains( link ) )
- this.parts.link = link;
-
- // Depending on configuration, read style/class from element and
- // then remove it. Removed style/class will be set on wrapper in #data listener.
- // Note: Center alignment is detected during upcast, so only left/right cases
- // are checked below.
- if ( !data.align ) {
- var alignElement = data.hasCaption ? this.element : image;
-
- // Read the initial left/right alignment from the class set on element.
- if ( alignClasses ) {
- if ( alignElement.hasClass( alignClasses[ 0 ] ) ) {
- data.align = 'left';
- } else if ( alignElement.hasClass( alignClasses[ 2 ] ) ) {
- data.align = 'right';
- }
-
- if ( data.align ) {
- alignElement.removeClass( alignClasses[ alignmentsObj[ data.align ] ] );
- } else {
- data.align = 'none';
- }
- }
- // Read initial float style from figure/image and then remove it.
- else {
- data.align = alignElement.getStyle( 'float' ) || 'none';
- alignElement.removeStyle( 'float' );
- }
- }
-
- // Update data.link object with attributes if the link has been discovered.
- if ( editor.plugins.link && this.parts.link ) {
- data.link = helpers.getLinkAttributesParser()( editor, this.parts.link );
-
- // Get rid of cke_widget_* classes in data. Otherwise
- // they might appear in link dialog.
- var advanced = data.link.advanced;
- if ( advanced && advanced.advCSSClasses ) {
- advanced.advCSSClasses = CKEDITOR.tools.trim( advanced.advCSSClasses.replace( /cke_\S+/, '' ) );
- }
- }
-
- // Get rid of extra vertical space when there's no caption.
- // It will improve the look of the resizer.
- this.wrapper[ ( data.hasCaption ? 'remove' : 'add' ) + 'Class' ]( 'cke_image_nocaption' );
-
- this.setData( data );
-
- // Setup dynamic image resizing with mouse.
- // Don't initialize resizer when dimensions are disallowed (https://dev.ckeditor.com/ticket/11004).
- if ( editor.filter.checkFeature( this.features.dimension ) && editor.config.image2_disableResizer !== true ) {
- setupResizer( this );
- }
-
- this.shiftState = helpers.stateShifter( this.editor );
-
- // Add widget editing option to its context menu.
- this.on( 'contextMenu', function( evt ) {
- evt.data.image = CKEDITOR.TRISTATE_OFF;
-
- // Integrate context menu items for link.
- // Note that widget may be wrapped in a link, which
- // does not belong to that widget (https://dev.ckeditor.com/ticket/11814).
- if ( this.parts.link || this.wrapper.getAscendant( 'a' ) )
- evt.data.link = evt.data.unlink = CKEDITOR.TRISTATE_OFF;
- } );
- },
-
- // Overrides default method to handle internal mutability of Image2.
- // @see CKEDITOR.plugins.widget#addClass
- addClass: function( className ) {
- getStyleableElement( this ).addClass( className );
- },
-
- // Overrides default method to handle internal mutability of Image2.
- // @see CKEDITOR.plugins.widget#hasClass
- hasClass: function( className ) {
- return getStyleableElement( this ).hasClass( className );
- },
-
- // Overrides default method to handle internal mutability of Image2.
- // @see CKEDITOR.plugins.widget#removeClass
- removeClass: function( className ) {
- getStyleableElement( this ).removeClass( className );
- },
-
- // Overrides default method to handle internal mutability of Image2.
- // @see CKEDITOR.plugins.widget#getClasses
- getClasses: ( function() {
- var classRegex = new RegExp( '^(' + [].concat( captionedClass, alignClasses ).join( '|' ) + ')$' );
-
- return function() {
- var classes = this.repository.parseElementClasses( getStyleableElement( this ).getAttribute( 'class' ) );
-
- // Neither config.image2_captionedClass nor config.image2_alignClasses
- // do not belong to style classes.
- for ( var c in classes ) {
- if ( classRegex.test( c ) )
- delete classes[ c ];
- }
-
- return classes;
- };
- } )(),
-
- upcast: upcastWidgetElement( editor ),
- downcast: downcastWidgetElement( editor ),
-
- getLabel: function() {
- var label = ( this.data.alt || '' ) + ' ' + this.pathName;
-
- return this.editor.lang.widget.label.replace( /%1/, label );
- }
- };
- }
-
- /**
- * A set of Enhanced Image (image2) plugin helpers.
- *
- * @class
- * @singleton
- */
- CKEDITOR.plugins.image2 = {
- stateShifter: function( editor ) {
- // Tag name used for centering non-captioned widgets.
- var doc = editor.document,
- alignClasses = editor.config.image2_alignClasses,
- captionedClass = editor.config.image2_captionedClass,
- editable = editor.editable(),
-
- // The order that stateActions get executed. It matters!
- shiftables = [ 'hasCaption', 'align', 'link' ];
-
- // Atomic procedures, one per state variable.
- var stateActions = {
- align: function( shift, oldValue, newValue ) {
- var el = shift.element;
-
- // Alignment changed.
- if ( shift.changed.align ) {
- // No caption in the new state.
- if ( !shift.newData.hasCaption ) {
- // Changed to "center" (non-captioned).
- if ( newValue == 'center' ) {
- shift.deflate();
- shift.element = wrapInCentering( editor, el );
- }
-
- // Changed to "non-center" from "center" while caption removed.
- if ( !shift.changed.hasCaption && oldValue == 'center' && newValue != 'center' ) {
- shift.deflate();
- shift.element = unwrapFromCentering( el );
- }
- }
- }
-
- // Alignment remains and "center" removed caption.
- else if ( newValue == 'center' && shift.changed.hasCaption && !shift.newData.hasCaption ) {
- shift.deflate();
- shift.element = wrapInCentering( editor, el );
- }
-
- // Finally set display for figure.
- if ( !alignClasses && el.is( 'figure' ) ) {
- if ( newValue == 'center' )
- el.setStyle( 'display', 'inline-block' );
- else
- el.removeStyle( 'display' );
- }
- },
-
- hasCaption: function( shift, oldValue, newValue ) {
- // This action is for real state change only.
- if ( !shift.changed.hasCaption )
- return;
-
- // Get <img/> or <a><img/></a> from widget. Note that widget element might itself
- // be what we're looking for. Also element can be <p style="text-align:center"><a>...</a></p>.
- var imageOrLink;
- if ( shift.element.is( { img: 1, a: 1 } ) )
- imageOrLink = shift.element;
- else
- imageOrLink = shift.element.findOne( 'a,img' );
-
- // Switching hasCaption always destroys the widget.
- shift.deflate();
-
- // There was no caption, but the caption is to be added.
- if ( newValue ) {
- // Create new <figure> from widget template.
- var figure = CKEDITOR.dom.element.createFromHtml( templateBlock.output( {
- captionedClass: captionedClass,
- captionPlaceholder: editor.lang.image2.captionPlaceholder
- } ), doc );
-
- // Replace element with <figure>.
- replaceSafely( figure, shift.element );
-
- // Use old <img/> or <a><img/></a> instead of the one from the template,
- // so we won't lose additional attributes.
- imageOrLink.replace( figure.findOne( 'img' ) );
-
- // Update widget's element.
- shift.element = figure;
- }
-
- // The caption was present, but now it's to be removed.
- else {
- // Unwrap <img/> or <a><img/></a> from figure.
- imageOrLink.replace( shift.element );
-
- // Update widget's element.
- shift.element = imageOrLink;
- }
- },
-
- link: function( shift, oldValue, newValue ) {
- if ( shift.changed.link ) {
- var img = shift.element.is( 'img' ) ?
- shift.element : shift.element.findOne( 'img' ),
- link = shift.element.is( 'a' ) ?
- shift.element : shift.element.findOne( 'a' ),
- // Why deflate:
- // If element is <img/>, it will be wrapped into <a>,
- // which becomes a new widget.element.
- // If element is <a><img/></a>, it will be unlinked
- // so <img/> becomes a new widget.element.
- needsDeflate = ( shift.element.is( 'a' ) && !newValue ) || ( shift.element.is( 'img' ) && newValue ),
- newEl;
-
- if ( needsDeflate )
- shift.deflate();
-
- // If unlinked the image, returned element is <img>.
- if ( !newValue )
- newEl = unwrapFromLink( link );
- else {
- // If linked the image, returned element is <a>.
- if ( !oldValue )
- newEl = wrapInLink( img, shift.newData.link );
-
- // Set and remove all attributes associated with this state.
- var attributes = CKEDITOR.plugins.image2.getLinkAttributesGetter()( editor, newValue );
-
- if ( !CKEDITOR.tools.isEmpty( attributes.set ) )
- ( newEl || link ).setAttributes( attributes.set );
-
- if ( attributes.removed.length )
- ( newEl || link ).removeAttributes( attributes.removed );
- }
-
- if ( needsDeflate )
- shift.element = newEl;
- }
- }
- };
-
- function wrapInCentering( editor, element ) {
- var attribsAndStyles = {};
-
- if ( alignClasses )
- attribsAndStyles.attributes = { 'class': alignClasses[ 1 ] };
- else
- attribsAndStyles.styles = { 'text-align': 'center' };
-
- // There's no gentle way to center inline element with CSS, so create p/div
- // that wraps widget contents and does the trick either with style or class.
- var center = doc.createElement(
- editor.activeEnterMode == CKEDITOR.ENTER_P ? 'p' : 'div', attribsAndStyles );
-
- // Replace element with centering wrapper.
- replaceSafely( center, element );
- element.move( center );
-
- return center;
- }
-
- function unwrapFromCentering( element ) {
- var imageOrLink = element.findOne( 'a,img' );
-
- imageOrLink.replace( element );
-
- return imageOrLink;
- }
-
- // Wraps <img/> -> <a><img/></a>.
- // Returns reference to <a>.
- //
- // @param {CKEDITOR.dom.element} img
- // @param {Object} linkData
- // @returns {CKEDITOR.dom.element}
- function wrapInLink( img, linkData ) {
- var link = doc.createElement( 'a', {
- attributes: {
- href: linkData.url
- }
- } );
-
- link.replace( img );
- img.move( link );
-
- return link;
- }
-
- // De-wraps <a><img/></a> -> <img/>.
- // Returns the reference to <img/>
- //
- // @param {CKEDITOR.dom.element} link
- // @returns {CKEDITOR.dom.element}
- function unwrapFromLink( link ) {
- var img = link.findOne( 'img' );
-
- img.replace( link );
-
- return img;
- }
-
- function replaceSafely( replacing, replaced ) {
- if ( replaced.getParent() ) {
- var range = editor.createRange();
-
- range.moveToPosition( replaced, CKEDITOR.POSITION_BEFORE_START );
-
- // Remove old element. Do it before insertion to avoid a case when
- // element is moved from 'replaced' element before it, what creates
- // a tricky case which insertElementIntorRange does not handle.
- replaced.remove();
-
- editable.insertElementIntoRange( replacing, range );
- }
- else {
- replacing.replace( replaced );
- }
- }
-
- return function( shift ) {
- var name, i;
-
- shift.changed = {};
-
- for ( i = 0; i < shiftables.length; i++ ) {
- name = shiftables[ i ];
-
- shift.changed[ name ] = shift.oldData ?
- shift.oldData[ name ] !== shift.newData[ name ] : false;
- }
-
- // Iterate over possible state variables.
- for ( i = 0; i < shiftables.length; i++ ) {
- name = shiftables[ i ];
-
- stateActions[ name ]( shift,
- shift.oldData ? shift.oldData[ name ] : null,
- shift.newData[ name ] );
- }
-
- shift.inflate();
- };
- },
-
- /**
- * Checks whether the current image ratio matches the natural one
- * by comparing dimensions.
- *
- * @param {CKEDITOR.dom.element} image
- * @returns {Boolean}
- */
- checkHasNaturalRatio: function( image ) {
- var $ = image.$,
- natural = this.getNatural( image );
-
- // The reason for two alternative comparisons is that the rounding can come from
- // both dimensions, e.g. there are two cases:
- // 1. height is computed as a rounded relation of the real height and the value of width,
- // 2. width is computed as a rounded relation of the real width and the value of heigh.
- return Math.round( $.clientWidth / natural.width * natural.height ) == $.clientHeight ||
- Math.round( $.clientHeight / natural.height * natural.width ) == $.clientWidth;
- },
-
- /**
- * Returns natural dimensions of the image. For modern browsers
- * it uses natural(Width|Height). For old ones (IE8) it creates
- * a new image and reads the dimensions.
- *
- * @param {CKEDITOR.dom.element} image
- * @returns {Object}
- */
- getNatural: function( image ) {
- var dimensions;
-
- if ( image.$.naturalWidth ) {
- dimensions = {
- width: image.$.naturalWidth,
- height: image.$.naturalHeight
- };
- } else {
- var img = new Image();
- img.src = image.getAttribute( 'src' );
-
- dimensions = {
- width: img.width,
- height: img.height
- };
- }
-
- return dimensions;
- },
-
- /**
- * Returns an attribute getter function. Default getter comes from the Link plugin
- * and is documented by {@link CKEDITOR.plugins.link#getLinkAttributes}.
- *
- * **Note:** It is possible to override this method and use a custom getter e.g.
- * in the absence of the Link plugin.
- *
- * **Note:** If a custom getter is used, a data model format it produces
- * must be compatible with {@link CKEDITOR.plugins.link#getLinkAttributes}.
- *
- * **Note:** A custom getter must understand the data model format produced by
- * {@link #getLinkAttributesParser} to work correctly.
- *
- * @returns {Function} A function that gets (composes) link attributes.
- * @since 4.5.5
- */
- getLinkAttributesGetter: function() {
- // https://dev.ckeditor.com/ticket/13885
- return CKEDITOR.plugins.link.getLinkAttributes;
- },
-
- /**
- * Returns an attribute parser function. Default parser comes from the Link plugin
- * and is documented by {@link CKEDITOR.plugins.link#parseLinkAttributes}.
- *
- * **Note:** It is possible to override this method and use a custom parser e.g.
- * in the absence of the Link plugin.
- *
- * **Note:** If a custom parser is used, a data model format produced by the parser
- * must be compatible with {@link #getLinkAttributesGetter}.
- *
- * **Note:** If a custom parser is used, it should be compatible with the
- * {@link CKEDITOR.plugins.link#parseLinkAttributes} data model format. Otherwise the
- * Link plugin dialog may not be populated correctly with parsed data. However
- * as long as Enhanced Image is **not** used with the Link plugin dialog, any custom data model
- * will work, being stored as an internal property of Enhanced Image widget's data only.
- *
- * @returns {Function} A function that parses attributes.
- * @since 4.5.5
- */
- getLinkAttributesParser: function() {
- // https://dev.ckeditor.com/ticket/13885
- return CKEDITOR.plugins.link.parseLinkAttributes;
- }
- };
-
- function setWrapperAlign( widget, alignClasses ) {
- var wrapper = widget.wrapper,
- align = widget.data.align,
- hasCaption = widget.data.hasCaption;
-
- if ( alignClasses ) {
- // Remove all align classes first.
- for ( var i = 3; i--; )
- wrapper.removeClass( alignClasses[ i ] );
-
- if ( align == 'center' ) {
- // Avoid touching non-captioned, centered widgets because
- // they have the class set on the element instead of wrapper:
- //
- // <div class="cke_widget_wrapper">
- // <p class="center-class">
- // <img />
- // </p>
- // </div>
- if ( hasCaption ) {
- wrapper.addClass( alignClasses[ 1 ] );
- }
- } else if ( align != 'none' ) {
- wrapper.addClass( alignClasses[ alignmentsObj[ align ] ] );
- }
- } else {
- if ( align == 'center' ) {
- if ( hasCaption )
- wrapper.setStyle( 'text-align', 'center' );
- else
- wrapper.removeStyle( 'text-align' );
-
- wrapper.removeStyle( 'float' );
- }
- else {
- if ( align == 'none' )
- wrapper.removeStyle( 'float' );
- else
- wrapper.setStyle( 'float', align );
-
- wrapper.removeStyle( 'text-align' );
- }
- }
- }
-
- // Returns a function that creates widgets from all <img> and
- // <figure class="{config.image2_captionedClass}"> elements.
- //
- // @param {CKEDITOR.editor} editor
- // @returns {Function}
- function upcastWidgetElement( editor ) {
- var isCenterWrapper = centerWrapperChecker( editor ),
- captionedClass = editor.config.image2_captionedClass;
-
- // @param {CKEDITOR.htmlParser.element} el
- // @param {Object} data
- return function( el, data ) {
- var dimensions = { width: 1, height: 1 },
- name = el.name,
- image;
-
- // https://dev.ckeditor.com/ticket/11110 Don't initialize on pasted fake objects.
- if ( el.attributes[ 'data-cke-realelement' ] )
- return;
-
- // If a center wrapper is found, there are 3 possible cases:
- //
- // 1. <div style="text-align:center"><figure>...</figure></div>.
- // In this case centering is done with a class set on widget.wrapper.
- // Simply replace centering wrapper with figure (it's no longer necessary).
- //
- // 2. <p style="text-align:center"><img/></p>.
- // Nothing to do here: <p> remains for styling purposes.
- //
- // 3. <div style="text-align:center"><img/></div>.
- // Nothing to do here (2.) but that case is only possible in enterMode different
- // than ENTER_P.
- if ( isCenterWrapper( el ) ) {
- if ( name == 'div' ) {
- var figure = el.getFirst( 'figure' );
-
- // Case #1.
- if ( figure ) {
- el.replaceWith( figure );
- el = figure;
- }
- }
- // Cases #2 and #3 (handled transparently)
-
- // If there's a centering wrapper, save it in data.
- data.align = 'center';
-
- // Image can be wrapped in link <a><img/></a>.
- image = el.getFirst( 'img' ) || el.getFirst( 'a' ).getFirst( 'img' );
- }
-
- // No center wrapper has been found.
- else if ( name == 'figure' && el.hasClass( captionedClass ) ) {
- image = el.find( function( child ) {
- return child.name === 'img' &&
- CKEDITOR.tools.array.indexOf( [ 'figure', 'a' ], child.parent.name ) !== -1;
- }, true )[ 0 ];
-
- // Upcast linked image like <a><img/></a>.
- } else if ( isLinkedOrStandaloneImage( el ) ) {
- image = el.name == 'a' ? el.children[ 0 ] : el;
- }
-
- if ( !image )
- return;
-
- // If there's an image, then cool, we got a widget.
- // Now just remove dimension attributes expressed with %.
- for ( var d in dimensions ) {
- var dimension = image.attributes[ d ];
-
- if ( dimension && dimension.match( regexPercent ) )
- delete image.attributes[ d ];
- }
-
- return el;
- };
- }
-
- // Returns a function which transforms the widget to the external format
- // according to the current configuration.
- //
- // @param {CKEDITOR.editor}
- function downcastWidgetElement( editor ) {
- var alignClasses = editor.config.image2_alignClasses;
-
- // @param {CKEDITOR.htmlParser.element} el
- return function( el ) {
- // In case of <a><img/></a>, <img/> is the element to hold
- // inline styles or classes (image2_alignClasses).
- var attrsHolder = el.name == 'a' ? el.getFirst() : el,
- attrs = attrsHolder.attributes,
- align = this.data.align;
-
- // De-wrap the image from resize handle wrapper.
- // Only block widgets have one.
- if ( !this.inline ) {
- var resizeWrapper = el.getFirst( 'span' );
-
- if ( resizeWrapper )
- resizeWrapper.replaceWith( resizeWrapper.getFirst( { img: 1, a: 1 } ) );
- }
-
- if ( align && align != 'none' ) {
- var styles = CKEDITOR.tools.parseCssText( attrs.style || '' );
-
- // When the widget is captioned (<figure>) and internally centering is done
- // with widget's wrapper style/class, in the external data representation,
- // <figure> must be wrapped with an element holding an style/class:
- //
- // <div style="text-align:center">
- // <figure class="image" style="display:inline-block">...</figure>
- // </div>
- // or
- // <div class="some-center-class">
- // <figure class="image">...</figure>
- // </div>
- //
- if ( align == 'center' && el.name == 'figure' ) {
- el = el.wrapWith( new CKEDITOR.htmlParser.element( 'div',
- alignClasses ? { 'class': alignClasses[ 1 ] } : { style: 'text-align:center' } ) );
- }
-
- // If left/right, add float style to the downcasted element.
- else if ( align in { left: 1, right: 1 } ) {
- if ( alignClasses )
- attrsHolder.addClass( alignClasses[ alignmentsObj[ align ] ] );
- else
- styles[ 'float' ] = align;
- }
-
- // Update element styles.
- if ( !alignClasses && !CKEDITOR.tools.isEmpty( styles ) )
- attrs.style = CKEDITOR.tools.writeCssText( styles );
- }
-
- return el;
- };
- }
-
- // Returns a function that checks if an element is a centering wrapper.
- //
- // @param {CKEDITOR.editor} editor
- // @returns {Function}
- function centerWrapperChecker( editor ) {
- var captionedClass = editor.config.image2_captionedClass,
- alignClasses = editor.config.image2_alignClasses,
- validChildren = { figure: 1, a: 1, img: 1 };
-
- return function( el ) {
- // Wrapper must be either <div> or <p>.
- if ( !( el.name in { div: 1, p: 1 } ) )
- return false;
-
- var children = el.children;
-
- // Centering wrapper can have only one child.
- if ( children.length !== 1 )
- return false;
-
- var child = children[ 0 ];
-
- // Only <figure> or <img /> can be first (only) child of centering wrapper,
- // regardless of its type.
- if ( !( child.name in validChildren ) )
- return false;
-
- // If centering wrapper is <p>, only <img /> can be the child.
- // <p style="text-align:center"><img /></p>
- if ( el.name == 'p' ) {
- if ( !isLinkedOrStandaloneImage( child ) )
- return false;
- }
- // Centering <div> can hold <img/> or <figure>, depending on enterMode.
- else {
- // If a <figure> is the first (only) child, it must have a class.
- // <div style="text-align:center"><figure>...</figure><div>
- if ( child.name == 'figure' ) {
- if ( !child.hasClass( captionedClass ) )
- return false;
- } else {
- // Centering <div> can hold <img/> or <a><img/></a> only when enterMode
- // is ENTER_(BR|DIV).
- // <div style="text-align:center"><img /></div>
- // <div style="text-align:center"><a><img /></a></div>
- if ( editor.enterMode == CKEDITOR.ENTER_P )
- return false;
-
- // Regardless of enterMode, a child which is not <figure> must be
- // either <img/> or <a><img/></a>.
- if ( !isLinkedOrStandaloneImage( child ) )
- return false;
- }
- }
-
- // Centering wrapper got to be... centering. If image2_alignClasses are defined,
- // check for centering class. Otherwise, check the style.
- if ( alignClasses ? el.hasClass( alignClasses[ 1 ] ) :
- CKEDITOR.tools.parseCssText( el.attributes.style || '', true )[ 'text-align' ] == 'center' )
- return true;
-
- return false;
- };
- }
-
- // Checks whether element is <img/> or <a><img/></a>.
- //
- // @param {CKEDITOR.htmlParser.element}
- function isLinkedOrStandaloneImage( el ) {
- if ( el.name == 'img' )
- return true;
- else if ( el.name == 'a' )
- return el.children.length == 1 && el.getFirst( 'img' );
-
- return false;
- }
-
- // Sets width and height of the widget image according to current widget data.
- //
- // @param {CKEDITOR.plugins.widget} widget
- function setDimensions( widget ) {
- var data = widget.data,
- dimensions = { width: data.width, height: data.height },
- image = widget.parts.image;
-
- for ( var d in dimensions ) {
- if ( dimensions[ d ] )
- image.setAttribute( d, dimensions[ d ] );
- else
- image.removeAttribute( d );
- }
- }
-
- // Defines all features related to drag-driven image resizing.
- //
- // @param {CKEDITOR.plugins.widget} widget
- function setupResizer( widget ) {
- var editor = widget.editor,
- editable = editor.editable(),
- doc = editor.document,
-
- // Store the resizer in a widget for testing (https://dev.ckeditor.com/ticket/11004).
- resizer = widget.resizer = doc.createElement( 'span' );
-
- resizer.addClass( 'cke_image_resizer' );
- resizer.setAttribute( 'title', editor.lang.image2.resizer );
- resizer.append( new CKEDITOR.dom.text( '\u200b', doc ) );
-
- // Inline widgets don't need a resizer wrapper as an image spans the entire widget.
- if ( !widget.inline ) {
- var imageOrLink = widget.parts.link || widget.parts.image,
- oldResizeWrapper = imageOrLink.getParent(),
- resizeWrapper = doc.createElement( 'span' );
-
- resizeWrapper.addClass( 'cke_image_resizer_wrapper' );
- resizeWrapper.append( imageOrLink );
- resizeWrapper.append( resizer );
- widget.element.append( resizeWrapper, true );
-
- // Remove the old wrapper which could came from e.g. pasted HTML
- // and which could be corrupted (e.g. resizer span has been lost).
- if ( oldResizeWrapper.is( 'span' ) )
- oldResizeWrapper.remove();
- } else {
- widget.wrapper.append( resizer );
- }
-
- // Calculate values of size variables and mouse offsets.
- resizer.on( 'mousedown', function( evt ) {
- var image = widget.parts.image,
-
- // Don't update attributes if less than 15.
- // This is to prevent images to visually disappear.
- min = {
- width: 15,
- height: 15
- },
-
- max = getMaxSize(),
-
- // "factor" can be either 1 or -1. I.e.: For right-aligned images, we need to
- // subtract the difference to get proper width, etc. Without "factor",
- // resizer starts working the opposite way.
- factor = widget.data.align == 'right' ? -1 : 1,
-
- // The x-coordinate of the mouse relative to the screen
- // when button gets pressed.
- startX = evt.data.$.screenX,
- startY = evt.data.$.screenY,
-
- // The initial dimensions and aspect ratio of the image.
- startWidth = image.$.clientWidth,
- startHeight = image.$.clientHeight,
- ratio = startWidth / startHeight,
-
- listeners = [],
-
- // A class applied to editable during resizing.
- cursorClass = 'cke_image_s' + ( !~factor ? 'w' : 'e' ),
-
- nativeEvt, newWidth, newHeight, updateData,
- moveDiffX, moveDiffY, moveRatio;
-
- // Save the undo snapshot first: before resizing.
- editor.fire( 'saveSnapshot' );
-
- // Mousemove listeners are removed on mouseup.
- attachToDocuments( 'mousemove', onMouseMove, listeners );
-
- // Clean up the mousemove listener. Update widget data if valid.
- attachToDocuments( 'mouseup', onMouseUp, listeners );
-
- // The entire editable will have the special cursor while resizing goes on.
- editable.addClass( cursorClass );
-
- // This is to always keep the resizer element visible while resizing.
- resizer.addClass( 'cke_image_resizing' );
-
- // Attaches an event to a global document if inline editor.
- // Additionally, if classic (`iframe`-based) editor, also attaches the same event to `iframe`'s document.
- function attachToDocuments( name, callback, collection ) {
- var globalDoc = CKEDITOR.document,
- listeners = [];
-
- if ( !doc.equals( globalDoc ) )
- listeners.push( globalDoc.on( name, callback ) );
-
- listeners.push( doc.on( name, callback ) );
-
- if ( collection ) {
- for ( var i = listeners.length; i--; )
- collection.push( listeners.pop() );
- }
- }
-
- // Calculate with first, and then adjust height, preserving ratio.
- function adjustToX() {
- newWidth = startWidth + factor * moveDiffX;
- newHeight = Math.round( newWidth / ratio );
- }
-
- // Calculate height first, and then adjust width, preserving ratio.
- function adjustToY() {
- newHeight = startHeight - moveDiffY;
- newWidth = Math.round( newHeight * ratio );
- }
-
- // This is how variables refer to the geometry.
- // Note: x corresponds to moveOffset, this is the position of mouse
- // Note: o corresponds to [startX, startY].
- //
- // +--------------+--------------+
- // | | |
- // | I | II |
- // | | |
- // +------------- o -------------+ _ _ _
- // | | | ^
- // | VI | III | | moveDiffY
- // | | x _ _ _ _ _ v
- // +--------------+---------|----+
- // | |
- // <------->
- // moveDiffX
- function onMouseMove( evt ) {
- nativeEvt = evt.data.$;
-
- // This is how far the mouse is from the point the button was pressed.
- moveDiffX = nativeEvt.screenX - startX;
- moveDiffY = startY - nativeEvt.screenY;
-
- // This is the aspect ratio of the move difference.
- moveRatio = Math.abs( moveDiffX / moveDiffY );
-
- // Left, center or none-aligned widget.
- if ( factor == 1 ) {
- if ( moveDiffX <= 0 ) {
- // Case: IV.
- if ( moveDiffY <= 0 )
- adjustToX();
-
- // Case: I.
- else {
- if ( moveRatio >= ratio )
- adjustToX();
- else
- adjustToY();
- }
- } else {
- // Case: III.
- if ( moveDiffY <= 0 ) {
- if ( moveRatio >= ratio )
- adjustToY();
- else
- adjustToX();
- }
-
- // Case: II.
- else {
- adjustToY();
- }
- }
- }
-
- // Right-aligned widget. It mirrors behaviours, so I becomes II,
- // IV becomes III and vice-versa.
- else {
- if ( moveDiffX <= 0 ) {
- // Case: IV.
- if ( moveDiffY <= 0 ) {
- if ( moveRatio >= ratio )
- adjustToY();
- else
- adjustToX();
- }
-
- // Case: I.
- else {
- adjustToY();
- }
- } else {
- // Case: III.
- if ( moveDiffY <= 0 )
- adjustToX();
-
- // Case: II.
- else {
- if ( moveRatio >= ratio ) {
- adjustToX();
- } else {
- adjustToY();
- }
- }
- }
- }
-
- if ( isAllowedSize( newWidth, newHeight ) ) {
- updateData = { width: newWidth, height: newHeight };
- image.setAttributes( updateData );
- }
- }
-
- function onMouseUp() {
- var l;
-
- while ( ( l = listeners.pop() ) )
- l.removeListener();
-
- // Restore default cursor by removing special class.
- editable.removeClass( cursorClass );
-
- // This is to bring back the regular behaviour of the resizer.
- resizer.removeClass( 'cke_image_resizing' );
-
- if ( updateData ) {
- widget.setData( updateData );
-
- // Save another undo snapshot: after resizing.
- editor.fire( 'saveSnapshot' );
- }
-
- // Don't update data twice or more.
- updateData = false;
- }
-
- function getMaxSize() {
- var maxSize = editor.config.image2_maxSize,
- natural;
-
- if ( !maxSize ) {
- return null;
- }
-
- maxSize = CKEDITOR.tools.copy( maxSize );
- natural = CKEDITOR.plugins.image2.getNatural( image );
-
- maxSize.width = Math.max( maxSize.width === 'natural' ? natural.width : maxSize.width, min.width );
- maxSize.height = Math.max( maxSize.height === 'natural' ? natural.height : maxSize.height, min.width );
-
- return maxSize;
- }
-
- function isAllowedSize( width, height ) {
- var isTooSmall = width < min.width || height < min.height,
- isTooBig = max && ( width > max.width || height > max.height );
- return !isTooSmall && !isTooBig;
- }
- } );
-
- // Change the position of the widget resizer when data changes.
- widget.on( 'data', function() {
- resizer[ widget.data.align == 'right' ? 'addClass' : 'removeClass' ]( 'cke_image_resizer_left' );
- } );
- }
-
- // Integrates widget alignment setting with justify
- // plugin's commands (execution and refreshment).
- // @param {CKEDITOR.editor} editor
- // @param {String} value 'left', 'right', 'center' or 'block'
- function alignCommandIntegrator( editor ) {
- var execCallbacks = [],
- enabled;
-
- return function( value ) {
- var command = editor.getCommand( 'justify' + value );
-
- // Most likely, the justify plugin isn't loaded.
- if ( !command )
- return;
-
- // This command will be manually refreshed along with
- // other commands after exec.
- execCallbacks.push( function() {
- command.refresh( editor, editor.elementPath() );
- } );
-
- if ( value in { right: 1, left: 1, center: 1 } ) {
- command.on( 'exec', function( evt ) {
- var widget = getFocusedWidget( editor );
-
- if ( widget ) {
- widget.setData( 'align', value );
-
- // Once the widget changed its align, all the align commands
- // must be refreshed: the event is to be cancelled.
- for ( var i = execCallbacks.length; i--; )
- execCallbacks[ i ]();
-
- evt.cancel();
- }
- } );
- }
-
- command.on( 'refresh', function( evt ) {
- var widget = getFocusedWidget( editor ),
- allowed = { right: 1, left: 1, center: 1 };
-
- if ( !widget )
- return;
-
- // Cache "enabled" on first use. This is because filter#checkFeature may
- // not be available during plugin's afterInit in the future — a moment when
- // alignCommandIntegrator is called.
- if ( enabled === undefined )
- enabled = editor.filter.checkFeature( editor.widgets.registered.image.features.align );
-
- // Don't allow justify commands when widget alignment is disabled (https://dev.ckeditor.com/ticket/11004).
- if ( !enabled )
- this.setState( CKEDITOR.TRISTATE_DISABLED );
- else {
- this.setState(
- ( widget.data.align == value ) ? (
- CKEDITOR.TRISTATE_ON
- ) : (
- ( value in allowed ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED
- )
- );
- }
-
- evt.cancel();
- } );
- };
- }
-
- function linkCommandIntegrator( editor ) {
- // Nothing to integrate with if link is not loaded.
- if ( !editor.plugins.link )
- return;
-
- var listener = CKEDITOR.on( 'dialogDefinition', function( evt ) {
- var dialog = evt.data;
-
- if ( dialog.name == 'link' ) {
- var def = dialog.definition;
-
- var onShow = def.onShow,
- onOk = def.onOk;
-
- def.onShow = function() {
- var widget = getFocusedWidget( editor ),
- displayTextField = this.getContentElement( 'info', 'linkDisplayText' ).getElement().getParent().getParent();
-
- // Widget cannot be enclosed in a link, i.e.
- // <a>foo<inline widget/>bar</a>
- if ( widget && ( widget.inline ? !widget.wrapper.getAscendant( 'a' ) : 1 ) ) {
- this.setupContent( widget.data.link || {} );
-
- // Hide the display text in case of linking image2 widget.
- displayTextField.hide();
- } else {
- // Make sure that display text is visible, as it might be hidden by image2 integration
- // before.
- displayTextField.show();
- onShow.apply( this, arguments );
- }
- };
-
- // Set widget data if linking the widget using
- // link dialog (instead of default action).
- // State shifter handles data change and takes
- // care of internal DOM structure of linked widget.
- def.onOk = function() {
- var widget = getFocusedWidget( editor );
-
- // Widget cannot be enclosed in a link, i.e.
- // <a>foo<inline widget/>bar</a>
- if ( widget && ( widget.inline ? !widget.wrapper.getAscendant( 'a' ) : 1 ) ) {
- var data = {};
-
- // Collect data from fields.
- this.commitContent( data );
-
- // Set collected data to widget.
- widget.setData( 'link', data );
- } else {
- onOk.apply( this, arguments );
- }
- };
- }
- } );
- // Listener has to be removed due to leaking the editor reference (#589).
- editor.on( 'destroy', function() {
- listener.removeListener();
- } );
-
- // Overwrite the default behavior of unlink command.
- editor.getCommand( 'unlink' ).on( 'exec', function( evt ) {
- var widget = getFocusedWidget( editor );
-
- // Override unlink only when link truly belongs to the widget.
- // If wrapped inline widget in a link, let default unlink work (https://dev.ckeditor.com/ticket/11814).
- if ( !widget || !widget.parts.link )
- return;
-
- widget.setData( 'link', null );
-
- // Selection (which is fake) may not change if unlinked image in focused widget,
- // i.e. if captioned image. Let's refresh command state manually here.
- this.refresh( editor, editor.elementPath() );
-
- evt.cancel();
- } );
-
- // Overwrite default refresh of unlink command.
- editor.getCommand( 'unlink' ).on( 'refresh', function( evt ) {
- var widget = getFocusedWidget( editor );
-
- if ( !widget )
- return;
-
- // Note that widget may be wrapped in a link, which
- // does not belong to that widget (https://dev.ckeditor.com/ticket/11814).
- this.setState( widget.data.link || widget.wrapper.getAscendant( 'a' ) ?
- CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED );
-
- evt.cancel();
- } );
- }
-
- // Returns the focused widget, if of the type specific for this plugin.
- // If no widget is focused, `null` is returned.
- //
- // @param {CKEDITOR.editor}
- // @returns {CKEDITOR.plugins.widget}
- function getFocusedWidget( editor ) {
- var widget = editor.widgets.focused;
-
- if ( widget && widget.name == 'image' )
- return widget;
-
- return null;
- }
-
- // Returns a set of widget allowedContent rules, depending
- // on configurations like config#image2_alignClasses or
- // config#image2_captionedClass.
- //
- // @param {CKEDITOR.editor}
- // @returns {Object}
- function getWidgetAllowedContent( editor ) {
- var alignClasses = editor.config.image2_alignClasses,
- rules = {
- // Widget may need <div> or <p> centering wrapper.
- div: {
- match: centerWrapperChecker( editor )
- },
- p: {
- match: centerWrapperChecker( editor )
- },
- img: {
- attributes: '!src,alt,width,height'
- },
- figure: {
- classes: '!' + editor.config.image2_captionedClass
- },
- figcaption: true
- };
-
- if ( alignClasses ) {
- // Centering class from the config.
- rules.div.classes = alignClasses[ 1 ];
- rules.p.classes = rules.div.classes;
-
- // Left/right classes from the config.
- rules.img.classes = alignClasses[ 0 ] + ',' + alignClasses[ 2 ];
- rules.figure.classes += ',' + rules.img.classes;
- } else {
- // Centering with text-align.
- rules.div.styles = 'text-align';
- rules.p.styles = 'text-align';
-
- rules.img.styles = 'float';
- rules.figure.styles = 'float,display';
- }
-
- return rules;
- }
-
- // Returns a set of widget feature rules, depending
- // on editor configuration. Note that the following may not cover
- // all the possible cases since requiredContent supports a single
- // tag only.
- //
- // @param {CKEDITOR.editor}
- // @returns {Object}
- function getWidgetFeatures( editor ) {
- var alignClasses = editor.config.image2_alignClasses,
- features = {
- dimension: {
- requiredContent: 'img[width,height]'
- },
- align: {
- requiredContent: 'img' +
- ( alignClasses ? '(' + alignClasses[ 0 ] + ')' : '{float}' )
- },
- caption: {
- requiredContent: 'figcaption'
- }
- };
-
- return features;
- }
-
- // Returns element which is styled, considering current
- // state of the widget.
- //
- // @see CKEDITOR.plugins.widget#applyStyle
- // @param {CKEDITOR.plugins.widget} widget
- // @returns {CKEDITOR.dom.element}
- function getStyleableElement( widget ) {
- return widget.data.hasCaption ? widget.element : widget.parts.image;
- }
- } )();
-
- /**
- * A CSS class applied to the `<figure>` element of a captioned image.
- *
- * Read more in the {@glink features/image2 documentation} and see the
- * {@glink examples/image2 example}.
- *
- * // Changes the class to "captionedImage".
- * config.image2_captionedClass = 'captionedImage';
- *
- * @cfg {String} [image2_captionedClass='image']
- * @member CKEDITOR.config
- */
- CKEDITOR.config.image2_captionedClass = 'image';
-
- /**
- * Determines whether dimension inputs should be automatically filled when the image URL changes in the Enhanced Image
- * plugin dialog window.
- *
- * Read more in the {@glink features/image2 documentation} and see the
- * {@glink examples/image2 example}.
- *
- * config.image2_prefillDimensions = false;
- *
- * @since 4.5.0
- * @cfg {Boolean} [image2_prefillDimensions=true]
- * @member CKEDITOR.config
- */
-
- /**
- * Disables the image resizer. By default the resizer is enabled.
- *
- * Read more in the {@glink features/image2 documentation} and see the
- * {@glink examples/image2 example}.
- *
- * config.image2_disableResizer = true;
- *
- * @since 4.5.0
- * @cfg {Boolean} [image2_disableResizer=false]
- * @member CKEDITOR.config
- */
-
- /**
- * CSS classes applied to aligned images. Useful to take control over the way
- * the images are aligned, i.e. to customize output HTML and integrate external stylesheets.
- *
- * Classes should be defined in an array of three elements, containing left, center, and right
- * alignment classes, respectively. For example:
- *
- * config.image2_alignClasses = [ 'align-left', 'align-center', 'align-right' ];
- *
- * **Note**: Once this configuration option is set, the plugin will no longer produce inline
- * styles for alignment. It means that e.g. the following HTML will be produced:
- *
- * <img alt="My image" class="custom-center-class" src="foo.png" />
- *
- * instead of:
- *
- * <img alt="My image" style="float:left" src="foo.png" />
- *
- * **Note**: Once this configuration option is set, corresponding style definitions
- * must be supplied to the editor:
- *
- * * For {@glink guide/dev_framed classic editor} it can be done by defining additional
- * styles in the {@link CKEDITOR.config#contentsCss stylesheets loaded by the editor}. The same
- * styles must be provided on the target page where the content will be loaded.
- * * For {@glink guide/dev_inline inline editor} the styles can be defined directly
- * with `<style> ... <style>` or `<link href="..." rel="stylesheet">`, i.e. within the `<head>`
- * of the page.
- *
- * For example, considering the following configuration:
- *
- * config.image2_alignClasses = [ 'align-left', 'align-center', 'align-right' ];
- *
- * CSS rules can be defined as follows:
- *
- * .align-left {
- * float: left;
- * }
- *
- * .align-right {
- * float: right;
- * }
- *
- * .align-center {
- * text-align: center;
- * }
- *
- * .align-center > figure {
- * display: inline-block;
- * }
- *
- * Read more in the {@glink features/image2 documentation} and see the
- * {@glink examples/image2 example}.
- *
- * @since 4.4.0
- * @cfg {String[]} [image2_alignClasses=null]
- * @member CKEDITOR.config
- */
-
- /**
- * Determines whether alternative text is required for the captioned image.
- *
- * config.image2_altRequired = true;
- *
- * Read more in the {@glink features/image2 documentation} and see the
- * {@glink examples/image2 example}.
- *
- * @since 4.6.0
- * @cfg {Boolean} [image2_altRequired=false]
- * @member CKEDITOR.config
- */
-
- /**
- * Determines the maximum size that an image can be resized to with the resize handle.
- *
- * It stores two properties: `width` and `height`. They can be set with one of the two types:
- *
- * * A number representing a value that limits the maximum size in pixel units:
- *
- * ```js
- * config.image2_maxSize = {
- * height: 300,
- * width: 250
- * };
- * ```
- *
- * * A string representing the natural image size, so each image resize operation is limited to its own natural height or width:
- *
- * ```js
- * config.image2_maxSize = {
- * height: 'natural',
- * width: 'natural'
- * }
- * ```
- *
- * Note: An image can still be resized to bigger dimensions when using the image dialog.
- *
- * @since 4.12.0
- * @cfg {Object.<String, Number/String>} [image2_maxSize]
- * @member CKEDITOR.config
- */
-
- /**
- * Indicates the default state of the "Lock ratio" switch in the image dialog.
- * If set to `true`, the ratio will be locked. If set to `false`, it will be unlocked.
- * If the value is not set at all, the "Lock ratio" switch will indicate
- * if the image has preserved aspect ratio upon loading.
- *
- * @since 4.20.0
- * @cfg {Boolean} [image2_defaultLockRatio]
- * @member CKEDITOR.config
- */
|