We use cookies to make our website more effective. By using our website you agree to our privacy policy.

Source: traversing.js

/**
 * traversing.js is part of Aloha Editor project http://www.alohaeditor.org
 *
 * Aloha Editor ● JavaScript Content Editing Library
 * Copyright (c) 2010-2015 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://www.alohaeditor.org/docs/contributing.html
 * @namespace traversing
 */
define([
	'dom',
	'html',
	'arrays',
	'strings',
	'boundaries'
], function (
	Dom,
	Html,
	Arrays,
	Strings,
	Boundaries
) {
	'use strict';

	/**
	 * Moves the given boundary forward (if needed) to encapsulate all adjacent
	 * unrendered characters.
	 *
	 * This operation should therefore never cause the visual representation of
	 * the boundary to change.
	 *
	 * Since it is impossible to place a boundary immediately behind an
	 * invisible character, this function will only ever need to expand a
	 * range's end position.
	 *
	 * @param  {!Boundary} boundary
	 * @return {Boundary}
	 */
	function envelopeInvisibleCharacters(boundary) {
		if (Boundaries.isNodeBoundary(boundary)) {
			return boundary;
		}
		var offset = Html.nextSignificantOffset(boundary);
		var container = Boundaries.container(boundary);
		return (-1 === offset)
		     ? Boundaries.fromEndOfNode(container)
		     : Boundaries.create(container, offset);
	}

	/**
	 * Return the character immediately following the given boundary.
	 * If not character exists, an empty string is returned.
	 *
	 * @private
	 * @param  {Boundary} boundary
	 * @return {string}
	 */
	function nextCharacter(boundary) {
		var ahead = Html.next(boundary, 'char');
		if (ahead && Boundaries.isTextBoundary(ahead)) {
			var node = Boundaries.container(ahead);
			var offset = Boundaries.offset(ahead);
			return node.data.substr(offset - 1, 1);
		}
		return '';
	}

	/**
	 * Moves the boundary backwards by a unit measure.
	 *
	 * The second parameter `unit` specifies the unit with which to move the
	 * boundary. This value may be one of the following strings:
	 *
	 * "char" -- Move behind the previous visible character.
	 *
	 * "word" -- Move behind the previous word.
	 *
	 *		A word is the smallest semantic unit. It is a contigious sequence of
	 *		visible characters terminated by a space or puncuation character or
	 *		a word-breaker (in languages that do not use space to delimit word
	 *		boundaries).
	 *
	 * "boundary" -- Move in behind of the previous boundary and skip over void
	 *               elements.
	 *
	 * "offset" -- Move behind the previous visual offset.
	 *
	 *		A visual offset is the smallest unit of consumed space. This can be
	 *		a line break, or a visible character.
	 *
	 * "node" -- Move in front of the previous visible node.
	 *
	 * @param  {Boundary} boundary
	 * @param  {string=}  unit Defaults to "offset"
	 * @return {?Boundary}
	 * @memberOf traversing
	 */
	function prev(boundary, unit) {
		var behind = Html.prev(boundary, unit);
		if ('word' === unit) {
			if (Strings.WORD_BOUNDARY.test(nextCharacter(behind))) {
				return prev(behind, unit);
			}
		}
		return behind;
	}

	/**
	 * Moves the boundary forward by a unit measure.
	 *
	 * The second parameter `unit` specifies the unit with which to move the
	 * boundary. This value may be one of the following strings:
	 *
	 * "char" -- Move in front of the next visible character.
	 *
	 * "word" -- Move in front of the next word.
	 *
	 *		A word is the smallest semantic unit. It is a contigious sequence of
	 *		visible characters terminated by a space or puncuation character or
	 *		a word-breaker (in languages that do not use space to delimit word
	 *		boundaries).
	 *
	 * "boundary" -- Move in front of the next boundary and skip over void
	 *               elements.
	 *
	 * "offset" -- Move in front of the next visual offset.
	 *
	 *		A visual offset is the smallest unit of consumed space. This can be
	 *		a line break, or a visible character.
	 *
	 * "node" -- Move in front of the next visible node.
	 *
	 * @param  {Boundary} boundary
	 * @param  {string=}  unit Defaults to "offset"
	 * @return {?Boundary}
	 * @memberOf traversing
	 */
	function next(boundary, unit) {
		if ('word' === unit) {
			if (Strings.WORD_BOUNDARY.test(nextCharacter(boundary))) {
				return next(Html.next(boundary, 'char'), unit);
			}
		}
		return Html.next(boundary, unit);
	}

	/**
	 * Expands two boundaries to contain a word.
	 *
	 * The boundaries represent the start and end containers of a range.
	 *
	 * A word is a collection of visible characters terminated by a space or
	 * punctuation character or a word-breaker (in languages that do not use
	 * space to delimit word boundaries).
	 *
	 * foo b[a]r baz → foo [bar] baz
	 *
	 * @private
	 * @param  {Boundary} start
	 * @param  {Boundary} end
	 * @return {Array.<Boundary>}
	 */
	function expandToWord(start, end) {
		return [
			prev(start, 'word') || start,
			next(end,   'word') || end
		];
	}

	/**
	 * Expands two boundaries to contain a block.
	 *
	 * The boundaries represent the start and end containers of a range.
	 *
	 * [,] = start,end boundary
	 *
	 *  +-------+     [ +-------+
	 *  | block |       | block |
	 *  |       |   →   |       |
	 *  | [ ]   |       |       |
	 *  +-------+       +-------+ ]
	 *
	 * @private
	 * @param  {Boundary} start
	 * @param  {Boundary} end
	 * @return {Array.<Boundary>}
	 */
	function expandToBlock(start, end) {
		var cac = Boundaries.commonContainer(start, end);
		var ancestors = Dom.childAndParentsUntilIncl(cac, function (node) {
			return Html.hasLinebreakingStyle(node) || Dom.isEditingHost(node);
		});
		var node = Arrays.last(ancestors);
		var len = Dom.nodeLength(node);
		return [Boundaries.create(node, 0), next(Boundaries.create(node, len))];
	}

	/**
	 * Expands the given boundaries to contain the given unit.
	 *
	 * The second parameter `unit` specifies the unit with which to expand.
	 * This value may be one of the following strings:
	 *
	 * "word" -- Expand to completely contain a word.
	 *
	 *		A word is the smallest semantic unit.  It is a contigious sequence
	 *		of characters terminated by a space or puncuation character or a
	 *		word-breaker (in languages that do not use space to delimit word
	 *		boundaries).
	 *
	 * "block" -- Expand to completely contain a block.
	 *
	 * @param  {Boundary} start
	 * @param  {Boundary} end
	 * @param  {unit}     unit
	 * @return {Array.<Boundary>}
	 * @memberOf traversing
	 */
	function expand(start, end, unit) {
		switch (unit) {
		case 'word':
			return expandToWord(start, end);
		case 'block':
			return expandToBlock(start, end);
		default:
			throw '"' + unit + '"? what\'s that?';
		}
	}

	return {
		expand                      : expand,
		next                        : next,
		prev                        : prev,
		backward                    : Html.stepBackward,
		forward                     : Html.stepForward,
		isAtStart                   : Html.isAtStart,
		isAtEnd                     : Html.isAtEnd,
		isBoundaryEqual             : Html.isBoundaryEqual,
		envelopeInvisibleCharacters : envelopeInvisibleCharacters
	};
});
comments powered by Disqus