%PDF- %PDF-
| Direktori : /www/varak.net/wiki.varak.net/extensions/VisualEditor/lib/ve/src/ce/ |
| Current File : //www/varak.net/wiki.varak.net/extensions/VisualEditor/lib/ve/src/ce/ve.ce.Document.js |
/*!
* VisualEditor ContentEditable Document class.
*
* @copyright 2011-2016 VisualEditor Team and others; see http://ve.mit-license.org
*/
/**
* ContentEditable document.
*
* @class
* @extends ve.Document
*
* @constructor
* @param {ve.dm.Document} model Model to observe
* @param {ve.ce.Surface} surface Surface document is part of
*/
ve.ce.Document = function VeCeDocument( model, surface ) {
// Parent constructor
ve.ce.Document.super.call( this, new ve.ce.DocumentNode( model.getDocumentNode(), surface ) );
this.getDocumentNode().$element.prop( {
lang: model.getLang(),
dir: model.getDir()
} );
// Properties
this.model = model;
};
/* Inheritance */
OO.inheritClass( ve.ce.Document, ve.Document );
/* Methods */
/**
* Get a slug at an offset.
*
* @method
* @param {number} offset Offset to get slug at
* @return {HTMLElement} Slug at offset
*/
ve.ce.Document.prototype.getSlugAtOffset = function ( offset ) {
var node = this.getBranchNodeFromOffset( offset );
return node ? node.getSlugAtOffset( offset ) : null;
};
/**
* Calculate the DOM position corresponding to a DM offset
*
* If there are multiple DOM locations, heuristically pick the best one for cursor placement
*
* @private
* @param {number} offset Linear model offset
* @return {Object} position
* @return {Node} return.node position node
* @return {number} return.offset position offset within the node
* @throws {Error} Offset could not be translated to a DOM element and offset
*/
ve.ce.Document.prototype.getNodeAndOffset = function ( offset ) {
var branchNode, position, count, step, node, model, steps, found, prevNode, $viewNodes,
countedNodes = [];
// 1. Step with ve.adjacentDomPosition( ..., { stop: function () { return true; } } )
// until we hit a position at the correct offset (which is guaranteed to be the first
// such position in document order).
// 2. Use ve.adjacentDomPosition( ..., { stop: ... } ) once to return all
// subsequent positions at the same offset.
// 3. Look at the possible positions and pick as follows:
// - If there is a unicorn, return just inside it
// - Else if there is a nail, return just outside it
// - Else if there is a text node, return an offset in it
// - Else return the first matching offset
//
// Offsets of DOM nodes are counted to match their model equivalents.
//
// TODO: take the following into account:
// Unfortunately, there is no way to avoid slugless block nodes with no DM length: an
// IME can remove all the text from a node at a time when it is unsafe to fixup the node
// contents. In this case, a maximally deep element gives better bounding rectangle
// coordinates than any of its containers.
branchNode = this.getBranchNodeFromOffset( offset );
position = { node: branchNode.$element[ 0 ], offset: 0 };
count = branchNode.getOffset() + ( ( branchNode.isWrapped() ) ? 1 : 0 );
function noDescend() {
return this.classList.contains( 've-ce-branchNode-blockSlug' ) ||
ve.rejectsCursor( this );
}
while ( true ) {
if ( count === offset ) {
break;
}
position = ve.adjacentDomPosition(
position,
1,
{
noDescend: noDescend,
stop: function () { return true; }
}
);
step = position.steps[ 0 ];
node = step.node;
if ( node.nodeType === Node.TEXT_NODE ) {
if ( step.type === 'leave' ) {
// skip without incrementing
continue;
}
// Else the code below always breaks or skips over the text node;
// therefore it is guaranteed that step.type === 'enter' (we just
// stepped in)
// TODO: what about zero-length text nodes?
if ( offset <= count + node.data.length ) {
// match the appropriate offset in the text node
position = { node: node, offset: offset - count };
break;
} else {
// skip over the text node
count += node.data.length;
position = { node: node, offset: node.data.length };
continue;
}
} // else is an element node (TODO: handle comment etc)
if ( !(
node.classList.contains( 've-ce-branchNode' ) ||
node.classList.contains( 've-ce-leafNode' )
) ) {
// Nodes like b, inline slug, browser-generated br that doesn't have
// class ve-ce-leafNode: continue walk without incrementing
continue;
}
if ( step.type === 'leave' ) {
// Below we'll guarantee that .ve-ce-branchNode/.ve-ce-leafNode elements
// are only entered if their open/close tags take up a model offset, so
// we can increment unconditionally here
count++;
continue;
} // else step.type === 'enter' || step.type === 'cross'
model = $.data( node, 'view' ).model;
if ( countedNodes.indexOf( model ) !== -1 ) {
// This DM node is rendered as multiple DOM elements, and we have already
// counted it as part of an earlier element. Skip past without incrementing
position = { node: node.parentNode, offset: ve.parentIndex( node ) + 1 };
continue;
}
countedNodes.push( model );
if ( offset >= count + model.getOuterLength() ) {
// Offset doesn't lie inside the node. Skip past and count length
// skip past the whole node
position = { node: node.parentNode, offset: ve.parentIndex( node ) + 1 };
count += model.getOuterLength();
} else if ( step.type === 'cross' ) {
if ( offset === count + 1 ) {
// The offset lies inside the crossed node
position = { node: node, offset: 0 };
break;
}
count += 2;
} else {
count += 1;
}
}
// Now "position" is the first DOM position (in document order) at the correct
// model offset.
// If the position is exactly after the first of multiple view nodes sharing a model,
// then jump to the position exactly after the final such view node.
prevNode = position.node.childNodes[ position.offset - 1 ];
if ( prevNode && prevNode.nodeType === Node.ELEMENT_NODE && (
prevNode.classList.contains( 've-ce-branchNode' ) ||
prevNode.classList.contains( 've-ce-leafNode' )
) ) {
$viewNodes = $.data( prevNode, 'view' ).$element;
if ( $viewNodes.length > 1 ) {
position.node = $viewNodes.get( -1 ).parentNode;
position.offset = 1 + ve.parentIndex( $viewNodes.get( -1 ) );
}
}
// Find all subsequent DOM positions at the same model offset
found = {};
function stop( step ) {
var model;
if ( step.node.nodeType === Node.TEXT_NODE ) {
return step.type === 'internal';
}
if (
step.node.classList.contains( 've-ce-branchNode' ) ||
step.node.classList.contains( 've-ce-leafNode' )
) {
model = $.data( step.node, 'view' ).model;
if ( countedNodes.indexOf( model ) !== -1 ) {
return false;
}
countedNodes.push( model );
return true;
}
return false;
}
steps = ve.adjacentDomPosition( position, 1, { stop: stop, noDescend: noDescend } ).steps;
steps.slice( 0, -1 ).forEach( function ( step ) {
// Step type cannot be "internal", else the offset would have incremented
var hasClass = function ( className ) {
return step.node.nodeType === Node.ELEMENT_NODE &&
step.node.classList.contains( className );
};
found.preUnicorn = found.preUnicorn || ( hasClass( 've-ce-pre-unicorn' ) && step );
found.postUnicorn = found.postUnicorn || ( hasClass( 've-ce-post-unicorn' ) && step );
found.preOpenNail = found.preOpenNail || ( hasClass( 've-ce-nail-pre-open' ) && step );
found.postOpenNail = found.postOpenNail || ( hasClass( 've-ce-nail-post-open' ) && step );
found.preCloseNail = found.preCloseNail || ( hasClass( 've-ce-nail-pre-close' ) && step );
found.postCloseNail = found.postCloseNail || ( hasClass( 've-ce-nail-post-close' ) && step );
found.focusableNode = found.focusableNode || ( hasClass( 've-ce-focusableNode' ) && step );
found.text = found.text || ( step.node.nodeType === Node.TEXT_NODE && step );
} );
// If there is a unicorn, it should be a unique pre/post-Unicorn pair containing text or
// nothing return the position just inside.
if ( found.preUnicorn ) {
return ve.ce.nextCursorOffset( found.preUnicorn.node );
}
if ( found.postUnicorn ) {
return ve.ce.previousCursorOffset( found.postUnicorn.node );
}
if ( found.preOpenNail ) {
// This will also cover the case where there is a post-open nail, as there will
// be no offset difference between them
return ve.ce.previousCursorOffset( found.preOpenNail.node );
}
if ( found.postCloseNail ) {
// This will also cover the case where there is a pre-close nail, as there will
// be no offset difference between them
return ve.ce.nextCursorOffset( found.postCloseNail.node );
}
if ( found.text ) {
if ( position.node.nodeType === Node.TEXT_NODE ) {
return position;
}
// We must either have entered or left the text node
return { node: found.text.node, offset: 0 };
}
return position;
};
/**
* Get the block directionality of some range
*
* Uses the computed CSS direction value of the current node
*
* @method
* @param {ve.Range} range Range
* @return {string} 'rtl', 'ltr'
*/
ve.ce.Document.prototype.getDirectionFromRange = function ( range ) {
var effectiveNode,
selectedNodes = this.selectNodes( range, 'covered' );
if ( selectedNodes.length > 1 ) {
// Selection of multiple nodes
// Get the common parent node
effectiveNode = this.selectNodes( range, 'siblings' )[ 0 ].node.getParent();
} else {
// selection of a single node
effectiveNode = selectedNodes[ 0 ].node;
while ( effectiveNode.isContent() ) {
// This means that we're in a leaf node, like TextNode
// those don't read the directionality properly, we will
// have to climb up the parentage chain until we find a
// wrapping node like paragraph or list item, etc.
effectiveNode = effectiveNode.parent;
}
}
return effectiveNode.$element.css( 'direction' );
};