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

Source: links.js

/**
 * links.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 links
 */
define([
	'dom',
	'html',
	'events',
	'ranges',
	'arrays',
	'mutation',
	'boundaries',
	'links/link-util',
	'links/link-selection'
], function (
	Dom,
	Html,
	Events,
	Ranges,
	Arrays,
	Mutation,
	Boundaries,
	LinkUtil,
	LinkSelection
) {
	'use strict';

	/**
	 * Checks if the range is valid for create a link.
	 *
	 * @param  {!Range} range
	 * @return {boolean}
	 */
	function isValidRangeForCreateLink(range) {
		return !range.collapsed && (!range.textContent || range.textContent.trim().length === 0);
	}

	/**
	 * Creates anchor elements between the given boundaries.
	 *
	 * @private
	 * @param  {!Boundary} start
	 * @param  {!Boundary} end
	 * @return {Array.<Element>}
	 */
	function createAnchors(start, end) {
		var doc = Boundaries.document(start);
		var groups = LinkSelection.collectLinkableNodeGroups(start, end);
		return groups.reduce(function (anchors, group) {
			var anchor = doc.createElement('a');
			Dom.insert(anchor, group[0]);
			Dom.move(group, anchor);
			return anchors.concat(anchor);
		}, []);
	}

	/**
	 * Creates links from the content between the given boundaries.
	 *
	 * If the boundaries represent a collapsed selection (visually equal), then
	 * the a link will be created at the boundary position with href as both the
	 * anchor text and the value of the href attribute.
	 *
	 * Will pass newly created anchor elements to optional `created` array.
	 *
	 * @todo
	 * - This function should return a list of newly created anchor elements.
	 * - This function also needs to return the modified boundaries.
	 *
	 * @param  {string}           href
	 * @param  {!Boundary}        start
	 * @param  {!Boundary}        end
	 * @param  {Array.<Element>=} created
	 * @return {Array.<Boundary>}
	 * @memberOf links
	 */
	function create(href, start, end, created) {
		var anchors;
		if (Html.isBoundariesEqual(start, end)) {
			var a = Boundaries.document(start).createElement('a');
			a.innerHTML = href;
			Mutation.insertNodeAtBoundary(a, start, true);
			anchors = [a];
		} else {
			anchors = createAnchors(start, end);
		}
		anchors.forEach(function (anchor) {Dom.setAttr(anchor, 'href', href);});
		if (created) {
			anchors.reduce(function (list, a) {list.push(a); return list;}, created);
		}
		return [
			Boundaries.fromFrontOfNode(anchors[0]),
			Boundaries.fromBehindOfNode(Arrays.last(anchors))
		];
	}

	/**
	 * Checks whether the given nodes are equal according to their type and
	 * attributes.
	 *
	 * @private
	 * @see    http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-isEqualNode
	 * @param  {!Node} src
	 * @param  {!Node} dst
	 * @return {boolean}
	 */
	function isEqualNodeShallow(src, dst) {
		return Dom.cloneShallow(src).isEqualNode(Dom.cloneShallow(dst));
	}

	/**
	 * Checks two nodes are are compatible to be joined.
	 *
	 * @private
	 * @param  {!Node} src
	 * @param  {!Node} dst
	 * @return {boolean}
	 */
	function areJoinable(src, dst) {
		return !Dom.isTextNode(src) && isEqualNodeShallow(src, dst);
	}

	/**
	 * Joins two nodes if they are compatible.
	 *
	 * @private
	 * @param {!Node} src
	 * @param {!Node} dst
	 */
	function joinNodes(src, dst) {
		var last;
		while (src && dst && areJoinable(src, dst)) {
			last = LinkUtil.nextRenderedNode(src.firstChild);
			dst.appendChild(last);
			Dom.remove(src);
			src = last;
			dst = dst.firstChild;
		}
	}

	/**
	 * Removes link anchor.
	 *
	 * @private
	 * @param {!Node} anchor
	 */
	function removeIfLink(anchor) {
		if (!LinkUtil.isAnchorNode(anchor)) {
			return;
		}
		var firstChild = LinkUtil.nextRenderedNode(anchor.firstChild);
		var prevAnchorSibling = LinkUtil.prevRenderedNode(anchor.previousSibling);
		joinNodes(firstChild, prevAnchorSibling);
		var lastChild = LinkUtil.prevRenderedNode(anchor.lastChild);
		var nextAnchorSibling = LinkUtil.nextRenderedNode(anchor.nextSibling);
		joinNodes(nextAnchorSibling, lastChild);
		Dom.removeShallow(anchor);
	}

	/**
	 * Removes children links if exists inside `node`.
	 *
	 * @private
	 * @param {!Node} node
	 */
	function removeChildrenLinks(node) {
		if (Dom.isElementNode(node)) {
			Arrays.coerce(node.querySelectorAll('a')).forEach(removeIfLink);
		}
	}

	/**
	 * Removes parent links if exists and returns the next node which should be
	 * analyze.
	 *
	 * @private
	 * @param  {Node} next
	 * @return {Node}
	 */
	function removeParentLinksAndGetNext(next) {
		var parent;
		while (!next.nextSibling && next.parentNode) {
			parent = next.parentNode;
			removeIfLink(next);
			next = parent;
		}
		var nextSibling = next.nextSibling;
		removeIfLink(next);
		return nextSibling;
	}

	/**
	 * Removes any links in the content between the given boundaries.
	 *
	 * @param {!Boundary} start
	 * @param {!Boundary} end
	 * @memberOf links
	 */
	function remove(start, end) {
		var startBoundary = LinkUtil.boundaryLinkable(
			Boundaries.container(start),
			Boundaries.offset(start)
		);
		var endBoundary = LinkUtil.boundaryLinkable(
			Boundaries.container(end),
			Boundaries.offset(end)
		);
		var sc = Boundaries.container(startBoundary);
		var ec = Boundaries.container(endBoundary);
		removeChildrenLinks(sc);
		removeChildrenLinks(ec);
		var next = sc;
		while (next && !Dom.isSameNode(next, ec)) {
			next = removeParentLinksAndGetNext(next);
			if (next) {
				removeChildrenLinks(next);
			}
		}
		while (ec && ec.parentNode && LinkUtil.isLinkable(ec)) {
			removeIfLink(ec);
			ec = ec.parentNode;
		}
	}

	function notAnchor(node) {
		return 'A' !== node.nodeName;
	}

	/**
	 * This function is missing documentation.
	 * @TODO Complete documentation.
	 *
	 * @memberOf links
	 */
	function middleware(event) {
		if ('click' !== event.type) {
			return event;
		}
		var cac = Boundaries.commonContainer(
			event.selection.boundaries[0],
			event.selection.boundaries[1]
		);
		var anchor = Dom.upWhile(cac, notAnchor);
		if (anchor) {
			Events.preventDefault(event.nativeEvent);
		}
		return event;
	}

	return {
		middleware : middleware,
		create     : create,
		remove     : remove
	};
});
comments powered by Disqus