1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018 |
- /**
- * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md or http://ckeditor.com/license
- */
- /**
- * @fileOverview A set of utilities to find and create horizontal spaces in edited content.
- */
- 'use strict';
- ( function() {
- CKEDITOR.plugins.add( 'lineutils' );
- /**
- * Determines a position relative to an element in DOM (before).
- *
- * @readonly
- * @property {Number} [=0]
- * @member CKEDITOR
- */
- CKEDITOR.LINEUTILS_BEFORE = 1;
- /**
- * Determines a position relative to an element in DOM (after).
- *
- * @readonly
- * @property {Number} [=2]
- * @member CKEDITOR
- */
- CKEDITOR.LINEUTILS_AFTER = 2;
- /**
- * Determines a position relative to an element in DOM (inside).
- *
- * @readonly
- * @property {Number} [=4]
- * @member CKEDITOR
- */
- CKEDITOR.LINEUTILS_INSIDE = 4;
- /**
- * A utility that traverses the DOM tree and discovers elements
- * (relations) matching user-defined lookups.
- *
- * @private
- * @class CKEDITOR.plugins.lineutils.finder
- * @constructor Creates a Finder class instance.
- * @param {CKEDITOR.editor} editor Editor instance that the Finder belongs to.
- * @param {Object} def Finder's definition.
- * @since 4.3
- */
- function Finder( editor, def ) {
- CKEDITOR.tools.extend( this, {
- editor: editor,
- editable: editor.editable(),
- doc: editor.document,
- win: editor.window
- }, def, true );
- this.inline = this.editable.isInline();
- if ( !this.inline ) {
- this.frame = this.win.getFrame();
- }
- this.target = this[ this.inline ? 'editable' : 'doc' ];
- }
- Finder.prototype = {
- /**
- * Initializes searching for elements with every mousemove event fired.
- * To stop searching use {@link #stop}.
- *
- * @param {Function} [callback] Function executed on every iteration.
- */
- start: function( callback ) {
- var that = this,
- editor = this.editor,
- doc = this.doc,
- el, elfp, x, y;
- var moveBuffer = CKEDITOR.tools.eventsBuffer( 50, function() {
- if ( editor.readOnly || editor.mode != 'wysiwyg' )
- return;
- that.relations = {};
- // Sometimes it happens that elementFromPoint returns null (especially on IE).
- // Any further traversal makes no sense if there's no start point. Abort.
- // Note: In IE8 elementFromPoint may return zombie nodes of undefined nodeType,
- // so rejecting those as well.
- if ( !( elfp = doc.$.elementFromPoint( x, y ) ) || !elfp.nodeType ) {
- return;
- }
- el = new CKEDITOR.dom.element( elfp );
- that.traverseSearch( el );
- if ( !isNaN( x + y ) ) {
- that.pixelSearch( el, x, y );
- }
- callback && callback( that.relations, x, y );
- } );
- // Searching starting from element from point on mousemove.
- this.listener = this.editable.attachListener( this.target, 'mousemove', function( evt ) {
- x = evt.data.$.clientX;
- y = evt.data.$.clientY;
- moveBuffer.input();
- } );
- this.editable.attachListener( this.inline ? this.editable : this.frame, 'mouseout', function() {
- moveBuffer.reset();
- } );
- },
- /**
- * Stops observing mouse events attached by {@link #start}.
- */
- stop: function() {
- if ( this.listener ) {
- this.listener.removeListener();
- }
- },
- /**
- * Returns a range representing the relation, according to its element
- * and type.
- *
- * @param {Object} location Location containing a unique identifier and type.
- * @returns {CKEDITOR.dom.range} Range representing the relation.
- */
- getRange: ( function() {
- var where = {};
- where[ CKEDITOR.LINEUTILS_BEFORE ] = CKEDITOR.POSITION_BEFORE_START;
- where[ CKEDITOR.LINEUTILS_AFTER ] = CKEDITOR.POSITION_AFTER_END;
- where[ CKEDITOR.LINEUTILS_INSIDE ] = CKEDITOR.POSITION_AFTER_START;
- return function( location ) {
- var range = this.editor.createRange();
- range.moveToPosition( this.relations[ location.uid ].element, where[ location.type ] );
- return range;
- };
- } )(),
- /**
- * Stores given relation in a {@link #relations} object. Processes the relation
- * to normalize and avoid duplicates.
- *
- * @param {CKEDITOR.dom.element} el Element of the relation.
- * @param {Number} type Relation, one of `CKEDITOR.LINEUTILS_AFTER`, `CKEDITOR.LINEUTILS_BEFORE`, `CKEDITOR.LINEUTILS_INSIDE`.
- */
- store: ( function() {
- function merge( el, type, relations ) {
- var uid = el.getUniqueId();
- if ( uid in relations ) {
- relations[ uid ].type |= type;
- } else {
- relations[ uid ] = { element: el, type: type };
- }
- }
- return function( el, type ) {
- var alt;
- // Normalization to avoid duplicates:
- // CKEDITOR.LINEUTILS_AFTER becomes CKEDITOR.LINEUTILS_BEFORE of el.getNext().
- if ( is( type, CKEDITOR.LINEUTILS_AFTER ) && isStatic( alt = el.getNext() ) && alt.isVisible() ) {
- merge( alt, CKEDITOR.LINEUTILS_BEFORE, this.relations );
- type ^= CKEDITOR.LINEUTILS_AFTER;
- }
- // Normalization to avoid duplicates:
- // CKEDITOR.LINEUTILS_INSIDE becomes CKEDITOR.LINEUTILS_BEFORE of el.getFirst().
- if ( is( type, CKEDITOR.LINEUTILS_INSIDE ) && isStatic( alt = el.getFirst() ) && alt.isVisible() ) {
- merge( alt, CKEDITOR.LINEUTILS_BEFORE, this.relations );
- type ^= CKEDITOR.LINEUTILS_INSIDE;
- }
- merge( el, type, this.relations );
- };
- } )(),
- /**
- * Traverses the DOM tree towards root, checking all ancestors
- * with lookup rules, avoiding duplicates. Stores positive relations
- * in the {@link #relations} object.
- *
- * @param {CKEDITOR.dom.element} el Element which is the starting point.
- */
- traverseSearch: function( el ) {
- var l, type, uid;
- // Go down DOM towards root (or limit).
- do {
- uid = el.$[ 'data-cke-expando' ];
- // This element was already visited and checked.
- if ( uid && uid in this.relations ) {
- continue;
- }
- if ( el.equals( this.editable ) ) {
- return;
- }
- if ( isStatic( el ) ) {
- // Collect all addresses yielded by lookups for that element.
- for ( l in this.lookups ) {
- if ( ( type = this.lookups[ l ]( el ) ) ) {
- this.store( el, type );
- }
- }
- }
- } while ( !isLimit( el ) && ( el = el.getParent() ) );
- },
- /**
- * Iterates vertically pixel-by-pixel within a given element starting
- * from given coordinates, searching for elements in the neighborhood.
- * Once an element is found it is processed by {@link #traverseSearch}.
- *
- * @param {CKEDITOR.dom.element} el Element which is the starting point.
- * @param {Number} [x] Horizontal mouse coordinate relative to the viewport.
- * @param {Number} [y] Vertical mouse coordinate relative to the viewport.
- */
- pixelSearch: ( function() {
- var contains = CKEDITOR.env.ie || CKEDITOR.env.webkit ?
- function( el, found ) {
- return el.contains( found );
- } : function( el, found ) {
- return !!( el.compareDocumentPosition( found ) & 16 );
- };
- // Iterates pixel-by-pixel from starting coordinates, moving by defined
- // step and getting elementFromPoint in every iteration. Iteration stops when:
- // * A valid element is found.
- // * Condition function returns `false` (i.e. reached boundaries of viewport).
- // * No element is found (i.e. coordinates out of viewport).
- // * Element found is ascendant of starting element.
- //
- // @param {Object} doc Native DOM document.
- // @param {Object} el Native DOM element.
- // @param {Number} xStart Horizontal starting coordinate to use.
- // @param {Number} yStart Vertical starting coordinate to use.
- // @param {Number} step Step of the algorithm.
- // @param {Function} condition A condition relative to current vertical coordinate.
- function iterate( el, xStart, yStart, step, condition ) {
- var y = yStart,
- tryouts = 0,
- found;
- while ( condition( y ) ) {
- y += step;
- // If we try and we try, and still nothing's found, let's end
- // that party.
- if ( ++tryouts == 25 ) {
- return;
- }
- found = this.doc.$.elementFromPoint( xStart, y );
- // Nothing found. This is crazy... but...
- // It might be that a line, which is in different document,
- // covers that pixel (elementFromPoint is doc-sensitive).
- // Better let's have another try.
- if ( !found ) {
- continue;
- }
- // Still in the same element.
- else if ( found == el ) {
- tryouts = 0;
- continue;
- }
- // Reached the edge of an element and found an ancestor or...
- // A line, that covers that pixel. Better let's have another try.
- else if ( !contains( el, found ) ) {
- continue;
- }
- tryouts = 0;
- // Found a valid element. Stop iterating.
- if ( isStatic( ( found = new CKEDITOR.dom.element( found ) ) ) ) {
- return found;
- }
- }
- }
- return function( el, x, y ) {
- var paneHeight = this.win.getViewPaneSize().height,
- // Try to find an element iterating *up* from the starting point.
- neg = iterate.call( this, el.$, x, y, -1, function( y ) {
- return y > 0;
- } ),
- // Try to find an element iterating *down* from the starting point.
- pos = iterate.call( this, el.$, x, y, 1, function( y ) {
- return y < paneHeight;
- } );
- if ( neg ) {
- this.traverseSearch( neg );
- // Iterate towards DOM root until neg is a direct child of el.
- while ( !neg.getParent().equals( el ) ) {
- neg = neg.getParent();
- }
- }
- if ( pos ) {
- this.traverseSearch( pos );
- // Iterate towards DOM root until pos is a direct child of el.
- while ( !pos.getParent().equals( el ) ) {
- pos = pos.getParent();
- }
- }
- // Iterate forwards starting from neg and backwards from
- // pos to harvest all children of el between those elements.
- // Stop when neg and pos meet each other or there's none of them.
- // TODO (?) reduce number of hops forwards/backwards.
- while ( neg || pos ) {
- if ( neg ) {
- neg = neg.getNext( isStatic );
- }
- if ( !neg || neg.equals( pos ) ) {
- break;
- }
- this.traverseSearch( neg );
- if ( pos ) {
- pos = pos.getPrevious( isStatic );
- }
- if ( !pos || pos.equals( neg ) ) {
- break;
- }
- this.traverseSearch( pos );
- }
- };
- } )(),
- /**
- * Unlike {@link #traverseSearch}, it collects **all** elements from editable's DOM tree
- * and runs lookups for every one of them, collecting relations.
- *
- * @returns {Object} {@link #relations}.
- */
- greedySearch: function() {
- this.relations = {};
- var all = this.editable.getElementsByTag( '*' ),
- i = 0,
- el, type, l;
- while ( ( el = all.getItem( i++ ) ) ) {
- // Don't consider editable, as it might be inline,
- // and i.e. checking it's siblings is pointless.
- if ( el.equals( this.editable ) ) {
- continue;
- }
- // On IE8 element.getElementsByTagName returns comments... sic! (#13176)
- if ( el.type != CKEDITOR.NODE_ELEMENT ) {
- continue;
- }
- // Don't visit non-editable internals, for example widget's
- // guts (above wrapper, below nested). Still check editable limits,
- // as they are siblings with editable contents.
- if ( !el.hasAttribute( 'contenteditable' ) && el.isReadOnly() ) {
- continue;
- }
- if ( isStatic( el ) && el.isVisible() ) {
- // Collect all addresses yielded by lookups for that element.
- for ( l in this.lookups ) {
- if ( ( type = this.lookups[ l ]( el ) ) ) {
- this.store( el, type );
- }
- }
- }
- }
- return this.relations;
- }
- /**
- * Relations express elements in DOM that match user-defined {@link #lookups}.
- * Every relation has its own `type` that determines whether
- * it refers to the space before, after or inside the `element`.
- * This object stores relations found by {@link #traverseSearch} or {@link #greedySearch}, structured
- * in the following way:
- *
- * relations: {
- * // Unique identifier of the element.
- * Number: {
- * // Element of this relation.
- * element: {@link CKEDITOR.dom.element}
- * // Conjunction of CKEDITOR.LINEUTILS_BEFORE, CKEDITOR.LINEUTILS_AFTER and CKEDITOR.LINEUTILS_INSIDE.
- * type: Number
- * },
- * ...
- * }
- *
- * @property {Object} relations
- * @readonly
- */
- /**
- * A set of user-defined functions used by Finder to check if an element
- * is a valid relation, belonging to {@link #relations}.
- * When the criterion is met, lookup returns a logical conjunction of `CKEDITOR.LINEUTILS_BEFORE`,
- * `CKEDITOR.LINEUTILS_AFTER` or `CKEDITOR.LINEUTILS_INSIDE`.
- *
- * Lookups are passed along with Finder's definition.
- *
- * lookups: {
- * 'some lookup': function( el ) {
- * if ( someCondition )
- * return CKEDITOR.LINEUTILS_BEFORE;
- * },
- * ...
- * }
- *
- * @property {Object} lookups
- */
- };
- /**
- * A utility that analyses relations found by
- * CKEDITOR.plugins.lineutils.finder and locates them
- * in the viewport as horizontal lines of specific coordinates.
- *
- * @private
- * @class CKEDITOR.plugins.lineutils.locator
- * @constructor Creates a Locator class instance.
- * @param {CKEDITOR.editor} editor Editor instance that Locator belongs to.
- * @since 4.3
- */
- function Locator( editor, def ) {
- CKEDITOR.tools.extend( this, def, {
- editor: editor
- }, true );
- }
- Locator.prototype = {
- /**
- * Locates the Y coordinate for all types of every single relation and stores
- * them in an object.
- *
- * @param {Object} relations {@link CKEDITOR.plugins.lineutils.finder#relations}.
- * @returns {Object} {@link #locations}.
- */
- locate: ( function() {
- function locateSibling( rel, type ) {
- var sib = rel.element[ type === CKEDITOR.LINEUTILS_BEFORE ? 'getPrevious' : 'getNext' ]();
- // Return the middle point between siblings.
- if ( sib && isStatic( sib ) ) {
- rel.siblingRect = sib.getClientRect();
- if ( type == CKEDITOR.LINEUTILS_BEFORE ) {
- return ( rel.siblingRect.bottom + rel.elementRect.top ) / 2;
- } else {
- return ( rel.elementRect.bottom + rel.siblingRect.top ) / 2;
- }
- }
- // If there's no sibling, use the edge of an element.
- else {
- if ( type == CKEDITOR.LINEUTILS_BEFORE ) {
- return rel.elementRect.top;
- } else {
- return rel.elementRect.bottom;
- }
- }
- }
- return function( relations ) {
- var rel;
- this.locations = {};
- for ( var uid in relations ) {
- rel = relations[ uid ];
- rel.elementRect = rel.element.getClientRect();
- if ( is( rel.type, CKEDITOR.LINEUTILS_BEFORE ) ) {
- this.store( uid, CKEDITOR.LINEUTILS_BEFORE, locateSibling( rel, CKEDITOR.LINEUTILS_BEFORE ) );
- }
- if ( is( rel.type, CKEDITOR.LINEUTILS_AFTER ) ) {
- this.store( uid, CKEDITOR.LINEUTILS_AFTER, locateSibling( rel, CKEDITOR.LINEUTILS_AFTER ) );
- }
- // The middle point of the element.
- if ( is( rel.type, CKEDITOR.LINEUTILS_INSIDE ) ) {
- this.store( uid, CKEDITOR.LINEUTILS_INSIDE, ( rel.elementRect.top + rel.elementRect.bottom ) / 2 );
- }
- }
- return this.locations;
- };
- } )(),
- /**
- * Calculates distances from every location to given vertical coordinate
- * and sorts locations according to that distance.
- *
- * @param {Number} y The vertical coordinate used for sorting, used as a reference.
- * @param {Number} [howMany] Determines the number of "closest locations" to be returned.
- * @returns {Array} Sorted, array representation of {@link #locations}.
- */
- sort: ( function() {
- var locations, sorted,
- dist, i;
- function distance( y, uid, type ) {
- return Math.abs( y - locations[ uid ][ type ] );
- }
- return function( y, howMany ) {
- locations = this.locations;
- sorted = [];
- for ( var uid in locations ) {
- for ( var type in locations[ uid ] ) {
- dist = distance( y, uid, type );
- // An array is empty.
- if ( !sorted.length ) {
- sorted.push( { uid: +uid, type: type, dist: dist } );
- } else {
- // Sort the array on fly when it's populated.
- for ( i = 0; i < sorted.length; i++ ) {
- if ( dist < sorted[ i ].dist ) {
- sorted.splice( i, 0, { uid: +uid, type: type, dist: dist } );
- break;
- }
- }
- // Nothing was inserted, so the distance is bigger than
- // any of already calculated: push to the end.
- if ( i == sorted.length ) {
- sorted.push( { uid: +uid, type: type, dist: dist } );
- }
- }
- }
- }
- if ( typeof howMany != 'undefined' ) {
- return sorted.slice( 0, howMany );
- } else {
- return sorted;
- }
- };
- } )(),
- /**
- * Stores the location in a collection.
- *
- * @param {Number} uid Unique identifier of the relation.
- * @param {Number} type One of `CKEDITOR.LINEUTILS_BEFORE`, `CKEDITOR.LINEUTILS_AFTER` and `CKEDITOR.LINEUTILS_INSIDE`.
- * @param {Number} y Vertical position of the relation.
- */
- store: function( uid, type, y ) {
- if ( !this.locations[ uid ] ) {
- this.locations[ uid ] = {};
- }
- this.locations[ uid ][ type ] = y;
- }
- /**
- * @readonly
- * @property {Object} locations
- */
- };
- var tipCss = {
- display: 'block',
- width: '0px',
- height: '0px',
- 'border-color': 'transparent',
- 'border-style': 'solid',
- position: 'absolute',
- top: '-6px'
- },
- lineStyle = {
- height: '0px',
- 'border-top': '1px dashed red',
- position: 'absolute',
- 'z-index': 9999
- },
- lineTpl =
- '<div data-cke-lineutils-line="1" class="cke_reset_all" style="{lineStyle}">' +
- '<span style="{tipLeftStyle}"> </span>' +
- '<span style="{tipRightStyle}"> </span>' +
- '</div>';
- /**
- * A utility that draws horizontal lines in DOM according to locations
- * returned by CKEDITOR.plugins.lineutils.locator.
- *
- * @private
- * @class CKEDITOR.plugins.lineutils.liner
- * @constructor Creates a Liner class instance.
- * @param {CKEDITOR.editor} editor Editor instance that Liner belongs to.
- * @param {Object} def Liner's definition.
- * @since 4.3
- */
- function Liner( editor, def ) {
- var editable = editor.editable();
- CKEDITOR.tools.extend( this, {
- editor: editor,
- editable: editable,
- inline: editable.isInline(),
- doc: editor.document,
- win: editor.window,
- container: CKEDITOR.document.getBody(),
- winTop: CKEDITOR.document.getWindow()
- }, def, true );
- this.hidden = {};
- this.visible = {};
- if ( !this.inline ) {
- this.frame = this.win.getFrame();
- }
- this.queryViewport();
- // Callbacks must be wrapped. Otherwise they're not attached
- // to global DOM objects (i.e. topmost window) for every editor
- // because they're treated as duplicates. They belong to the
- // same prototype shared among Liner instances.
- var queryViewport = CKEDITOR.tools.bind( this.queryViewport, this ),
- hideVisible = CKEDITOR.tools.bind( this.hideVisible, this ),
- removeAll = CKEDITOR.tools.bind( this.removeAll, this );
- editable.attachListener( this.winTop, 'resize', queryViewport );
- editable.attachListener( this.winTop, 'scroll', queryViewport );
- editable.attachListener( this.winTop, 'resize', hideVisible );
- editable.attachListener( this.win, 'scroll', hideVisible );
- editable.attachListener( this.inline ? editable : this.frame, 'mouseout', function( evt ) {
- var x = evt.data.$.clientX,
- y = evt.data.$.clientY;
- this.queryViewport();
- // Check if mouse is out of the element (iframe/editable).
- if ( x <= this.rect.left || x >= this.rect.right || y <= this.rect.top || y >= this.rect.bottom ) {
- this.hideVisible();
- }
- // Check if mouse is out of the top-window vieport.
- if ( x <= 0 || x >= this.winTopPane.width || y <= 0 || y >= this.winTopPane.height ) {
- this.hideVisible();
- }
- }, this );
- editable.attachListener( editor, 'resize', queryViewport );
- editable.attachListener( editor, 'mode', removeAll );
- editor.on( 'destroy', removeAll );
- this.lineTpl = new CKEDITOR.template( lineTpl ).output( {
- lineStyle: CKEDITOR.tools.writeCssText(
- CKEDITOR.tools.extend( {}, lineStyle, this.lineStyle, true )
- ),
- tipLeftStyle: CKEDITOR.tools.writeCssText(
- CKEDITOR.tools.extend( {}, tipCss, {
- left: '0px',
- 'border-left-color': 'red',
- 'border-width': '6px 0 6px 6px'
- }, this.tipCss, this.tipLeftStyle, true )
- ),
- tipRightStyle: CKEDITOR.tools.writeCssText(
- CKEDITOR.tools.extend( {}, tipCss, {
- right: '0px',
- 'border-right-color': 'red',
- 'border-width': '6px 6px 6px 0'
- }, this.tipCss, this.tipRightStyle, true )
- )
- } );
- }
- Liner.prototype = {
- /**
- * Permanently removes all lines (both hidden and visible) from DOM.
- */
- removeAll: function() {
- var l;
- for ( l in this.hidden ) {
- this.hidden[ l ].remove();
- delete this.hidden[ l ];
- }
- for ( l in this.visible ) {
- this.visible[ l ].remove();
- delete this.visible[ l ];
- }
- },
- /**
- * Hides a given line.
- *
- * @param {CKEDITOR.dom.element} line The line to be hidden.
- */
- hideLine: function( line ) {
- var uid = line.getUniqueId();
- line.hide();
- this.hidden[ uid ] = line;
- delete this.visible[ uid ];
- },
- /**
- * Shows a given line.
- *
- * @param {CKEDITOR.dom.element} line The line to be shown.
- */
- showLine: function( line ) {
- var uid = line.getUniqueId();
- line.show();
- this.visible[ uid ] = line;
- delete this.hidden[ uid ];
- },
- /**
- * Hides all visible lines.
- */
- hideVisible: function() {
- for ( var l in this.visible ) {
- this.hideLine( this.visible[ l ] );
- }
- },
- /**
- * Shows a line at given location.
- *
- * @param {Object} location Location object containing the unique identifier of the relation
- * and its type. Usually returned by {@link CKEDITOR.plugins.lineutils.locator#sort}.
- * @param {Function} [callback] A callback to be called once the line is shown.
- */
- placeLine: function( location, callback ) {
- var styles, line, l;
- // No style means that line would be out of viewport.
- if ( !( styles = this.getStyle( location.uid, location.type ) ) ) {
- return;
- }
- // Search for any visible line of a different hash first.
- // It's faster to re-position visible line than to show it.
- for ( l in this.visible ) {
- if ( this.visible[ l ].getCustomData( 'hash' ) !== this.hash ) {
- line = this.visible[ l ];
- break;
- }
- }
- // Search for any hidden line of a different hash.
- if ( !line ) {
- for ( l in this.hidden ) {
- if ( this.hidden[ l ].getCustomData( 'hash' ) !== this.hash ) {
- this.showLine( ( line = this.hidden[ l ] ) );
- break;
- }
- }
- }
- // If no line available, add the new one.
- if ( !line ) {
- this.showLine( ( line = this.addLine() ) );
- }
- // Mark the line with current hash.
- line.setCustomData( 'hash', this.hash );
- // Mark the line as visible.
- this.visible[ line.getUniqueId() ] = line;
- line.setStyles( styles );
- callback && callback( line );
- },
- /**
- * Creates a style set to be used by the line, representing a particular
- * relation (location).
- *
- * @param {Number} uid Unique identifier of the relation.
- * @param {Number} type Type of the relation.
- * @returns {Object} An object containing styles.
- */
- getStyle: function( uid, type ) {
- var rel = this.relations[ uid ],
- loc = this.locations[ uid ][ type ],
- styles = {},
- hdiff;
- // Line should be between two elements.
- if ( rel.siblingRect ) {
- styles.width = Math.max( rel.siblingRect.width, rel.elementRect.width );
- }
- // Line is relative to a single element.
- else {
- styles.width = rel.elementRect.width;
- }
- // Let's calculate the vertical position of the line.
- if ( this.inline ) {
- // (#13155)
- styles.top = loc + this.winTopScroll.y - this.rect.relativeY;
- } else {
- styles.top = this.rect.top + this.winTopScroll.y + loc;
- }
- // Check if line would be vertically out of the viewport.
- if ( styles.top - this.winTopScroll.y < this.rect.top || styles.top - this.winTopScroll.y > this.rect.bottom ) {
- return false;
- }
- // Now let's calculate the horizontal alignment (left and width).
- if ( this.inline ) {
- // (#13155)
- styles.left = rel.elementRect.left - this.rect.relativeX;
- } else {
- if ( rel.elementRect.left > 0 )
- styles.left = this.rect.left + rel.elementRect.left;
- // H-scroll case. Left edge of element may be out of viewport.
- else {
- styles.width += rel.elementRect.left;
- styles.left = this.rect.left;
- }
- // H-scroll case. Right edge of element may be out of viewport.
- if ( ( hdiff = styles.left + styles.width - ( this.rect.left + this.winPane.width ) ) > 0 ) {
- styles.width -= hdiff;
- }
- }
- // Finally include horizontal scroll of the global window.
- styles.left += this.winTopScroll.x;
- // Append 'px' to style values.
- for ( var style in styles ) {
- styles[ style ] = CKEDITOR.tools.cssLength( styles[ style ] );
- }
- return styles;
- },
- /**
- * Adds a new line to DOM.
- *
- * @returns {CKEDITOR.dom.element} A brand-new line.
- */
- addLine: function() {
- var line = CKEDITOR.dom.element.createFromHtml( this.lineTpl );
- line.appendTo( this.container );
- return line;
- },
- /**
- * Assigns a unique hash to the instance that is later used
- * to tell unwanted lines from new ones. This method **must** be called
- * before a new set of relations is to be visualized so {@link #cleanup}
- * eventually hides obsolete lines. This is because lines
- * are re-used between {@link #placeLine} calls and the number of
- * necessary ones may vary depending on the number of relations.
- *
- * @param {Object} relations {@link CKEDITOR.plugins.lineutils.finder#relations}.
- * @param {Object} locations {@link CKEDITOR.plugins.lineutils.locator#locations}.
- */
- prepare: function( relations, locations ) {
- this.relations = relations;
- this.locations = locations;
- this.hash = Math.random();
- },
- /**
- * Hides all visible lines that do not belong to current hash
- * and no longer represent relations (locations).
- *
- * See also: {@link #prepare}.
- */
- cleanup: function() {
- var line;
- for ( var l in this.visible ) {
- line = this.visible[ l ];
- if ( line.getCustomData( 'hash' ) !== this.hash ) {
- this.hideLine( line );
- }
- }
- },
- /**
- * Queries dimensions of the viewport, editable, frame etc.
- * that are used for correct positioning of the line.
- */
- queryViewport: function() {
- this.winPane = this.win.getViewPaneSize();
- this.winTopScroll = this.winTop.getScrollPosition();
- this.winTopPane = this.winTop.getViewPaneSize();
- // (#13155)
- this.rect = this.getClientRect( this.inline ? this.editable : this.frame );
- },
- /**
- * Returns `boundingClientRect` of an element, shifted by the position
- * of `container` when the container is not `static` (#13155).
- *
- * See also: {@link CKEDITOR.dom.element#getClientRect}.
- *
- * @param {CKEDITOR.dom.element} el A DOM element.
- * @returns {Object} A shifted rect, extended by `relativeY` and `relativeX` properties.
- */
- getClientRect: function( el ) {
- var rect = el.getClientRect(),
- relativeContainerDocPosition = this.container.getDocumentPosition(),
- relativeContainerComputedPosition = this.container.getComputedStyle( 'position' );
- // Static or not, those values are used to offset the position of the line so they cannot be undefined.
- rect.relativeX = rect.relativeY = 0;
- if ( relativeContainerComputedPosition != 'static' ) {
- // Remember the offset used to shift the clientRect.
- rect.relativeY = relativeContainerDocPosition.y;
- rect.relativeX = relativeContainerDocPosition.x;
- rect.top -= rect.relativeY;
- rect.bottom -= rect.relativeY;
- rect.left -= rect.relativeX;
- rect.right -= rect.relativeX;
- }
- return rect;
- }
- };
- function is( type, flag ) {
- return type & flag;
- }
- var floats = { left: 1, right: 1, center: 1 },
- positions = { absolute: 1, fixed: 1 };
- function isElement( node ) {
- return node && node.type == CKEDITOR.NODE_ELEMENT;
- }
- function isFloated( el ) {
- return !!( floats[ el.getComputedStyle( 'float' ) ] || floats[ el.getAttribute( 'align' ) ] );
- }
- function isPositioned( el ) {
- return !!positions[ el.getComputedStyle( 'position' ) ];
- }
- function isLimit( node ) {
- return isElement( node ) && node.getAttribute( 'contenteditable' ) == 'true';
- }
- function isStatic( node ) {
- return isElement( node ) && !isFloated( node ) && !isPositioned( node );
- }
- /**
- * Global namespace storing definitions and global helpers for the Line Utilities plugin.
- *
- * @private
- * @class
- * @singleton
- * @since 4.3
- */
- CKEDITOR.plugins.lineutils = {
- finder: Finder,
- locator: Locator,
- liner: Liner
- };
- } )();
|