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

Source: ui.js

/**
 * ui.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 ui
 */
define([
	'dom',
	'maps',
	'html',
	'arrays',
	'editing',
	'editables',
	'overrides',
	'boundaries',
	'selections',
	'functions'
], function (
	Dom,
	Maps,
	Html,
	Arrays,
	Editing,
	Editables,
	Overrides,
	Boundaries,
	Selections,
	Fn
) {
	'use strict';

	/**
	 * For the given command, determines if the command state is true for the
	 * specified node.
	 *
	 * @private
	 * @param  {Node}    node
	 * @param  {Command} command
	 * @return {boolean}
	 */
	function commandState(node, command) {
		if (command.state) {
			return command.state(node, command);
		}
		if (!(command.node || command.style || command.classes)) {
			return false;
		}
		if (command.node && node.nodeName !== command.node.toUpperCase()) {
			return false;
		}
		var result;
		if (command.style) {
			result = 1;
			Maps.forEach(command.style, function (value, prop) {
				result &= (value === Dom.getStyle(node, prop));
			});
			if (!result) {
				return false;
			}
		}
		if (command.classes) {
			result = 1;
			Maps.forEach(command.classes, function (value, prop) {
				result &= Dom.hasClass(node, prop);
			});
			if (!result) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Determines the states of the commands for the given event.
	 *
	 * @param  {Object.<string, command>} commands
	 * @param  {Event}                    event
	 * @return {Object.<string, boolean>}
	 * @memberOf ui
	 */
	function states(commands, event) {
		var values = {};
		var selection = event.selection;
		var container = Boundaries.container(
			selection.boundaries['start' === selection.focus ? 0 : 1]
		);
		var overrides = Overrides.map(Overrides.joinToSet(
			selection.formatting,
			Overrides.harvest(container),
			selection.overrides
		));
		var nodes = Dom.childAndParentsUntil(
			container,
			Dom.isEditingHost
		).filter(Dom.isElementNode);
		Maps.forEach(commands, function (command, key) {
			if (command.override && command.override in overrides) {
				values[key] = !!overrides[command.override];
			} else {
				var value = 0;
				nodes.forEach(function (node) {
					value |= commandState(node, command);
				});
				values[key] = !!value;
			}
		});
		return values;
	}

	/**
	 * Applies the given formatting command for blocks inside the given
	 * boundaries.
	 *
	 * @private
	 * @param  {Array.<Boundary>} boundaries
	 * @param  {Selection}        selection
	 * @param  {Command}          command
	 * @return {Array.<Boundary>}
	 */
	function formatBlock(boundaries, selection, command) {
		var style = command.style;
		var formatting = command.node.toUpperCase();
		boundaries = Editing.format(boundaries[0], boundaries[1], formatting);
		if (!style) {
			return boundaries;
		}
		Dom.nodesAndSiblingsBetween(
			Boundaries.container(boundaries[0]),
			Boundaries.container(boundaries[1])
		).forEach(function (node) {
			if (formatting === node.nodeName) {
				Maps.forEach(style, function (value, prop) {
					Dom.setStyle(node, prop, value);
				});
			}
		});
		return boundaries;
	}

	/**
	 * Applies the given formatting command for inline elements inside the given
	 * boundaries.
	 *
	 * @private
	 * @param  {Array.<Boundary>} boundaries
	 * @param  {Selection}        selection
	 * @param  {Command}          command
	 * @return {Array.<Boundary>}
	 */
	function formatInline(boundaries, selection, command) {
		var formatting = command.node.toUpperCase();
		if (!Boundaries.equals(boundaries[0], boundaries[1])) {
			return Editing.format(boundaries[0], boundaries[1], formatting);
		}
		var override = Overrides.nodeToState[formatting];
		if (!override) {
			return boundaries;
		}
		var overrides = Overrides.joinToSet(
			selection.formatting,
			Overrides.harvest(Boundaries.container(boundaries[0])),
			selection.overrides
		);
		selection.overrides = Overrides.toggle(overrides, override, true);
		return boundaries;
	}

	/**
	 * Removes formatting between the given boundaries according to the given command.
	 *
	 * @private
	 * @param  {Array.<Boundary>} boundaries
	 * @param  {Selection}        selection
	 * @param  {Command}          command
	 * @return {Array.<Boundary>}
	 */
	function removeFormatting(boundaries, selection, command) {
		command['nodes'].map(function (node) {
			return node.toUpperCase();
		}).forEach(function (formatting) {
			boundaries = Editing.wrap(
				formatting,
				boundaries[0],
				boundaries[1],
				true
			);
		});
		return boundaries;
	}

	/**
	 * Executes the given command
	 *
	 * @private
	 * @param {Command}          command
	 * @param {Array.<Boundary>} boundaries
	 * @param {Editor}           editor
	 * @param {AlohaEvent}       event
	 */
	function execute(command, boundaries, editor, event) {
		if (command.action) {
			boundaries = command.action(
				boundaries,
				editor.selection,
				command,
				event
			) || boundaries;
		} else {
			var action = Html.isBlockNode({nodeName: command.node.toUpperCase()})
			           ? formatBlock
			           : formatInline;
			boundaries = action(boundaries, editor.selection, command);
		}
		editor.selection = Selections.select(
			editor.selection,
			boundaries[0],
			boundaries[1]
		);
		var editable = Editables.fromBoundary(editor, boundaries[0]);
		if (editable) {
			// TODO: Rely on nodechange event instead
			Fn.comp.apply(editor.stack, editor.stack)({
				preventSelection : false,
				type             : 'nodechange',
				nativeEvent      : event,
				editable         : editable,
				selection        : editor.selection,
				dnd              : editor.dnd
			});
		}
	}

	/**
	 * Binds the given function to only be executed if the current document
	 * selection is inside the given editable(s).
	 *
	 * @param {Editable|Array.<Editable>} editables
	 * @param {function(Boundaries, Editor, AlohaEvent)}
	 * @memberOf ui
	 */
	function bind(editables, fn) {
		var check = 0 === editables.length
		          ? Fn.returnTrue
		          : Fn.partial(Arrays.contains, editables);
		// TODO: I'm sorry. I'll fix this!
		var editor = (editables[0] || window['aloha'])['editor'];
		return function (event) {
			var selection = editor.selection;
			if (selection && selection.boundaries) {
				var boundaries = Boundaries.get(Boundaries.document(selection.boundaries[0]));
				if (boundaries && check(Editables.fromBoundary(editor, boundaries[0]))) {
					fn(boundaries, editor, event);
				}
			}
		};
	}

	/**
	 * Binds the given command.
	 *
	 * @param  {Command}           cmd
	 * @param  {Array.<Editable>=} editables
	 * @return {function(Boundaries, Editor, AlohaEvent)}
	 * @memberOf ui
	 */
	function command(editables, cmd) {
		if (Editables.is(editables)) {
			editables = [editables];
		}
		if (!Arrays.is(editables)) {
			cmd = editables;
			editables = [];
		}
		return bind(editables, Fn.partial(execute, cmd));
	}

	/**
	 * UI Commands.
	 *
	 * <pre>
	 * Commands:
	 *     p
	 *     h1
	 *     h2
	 *     h3
	 *     h4
	 *     ol
	 *     ul
	 *     pre
	 *     bold
	 *     italic
	 *     underline
	 *     unformat
	 * </pre>
	 *
	 * @type {Object}
	 * @memberOf ui
	 */
	var commands = {
		'p'         : { 'node' : 'p'                           },
		'h1'        : { 'node' : 'h1'                          },
		'h2'        : { 'node' : 'h2'                          },
		'h3'        : { 'node' : 'h3'                          },
		'h4'        : { 'node' : 'h4'                          },
		'ol'        : { 'node' : 'ol'                          },
		'ul'        : { 'node' : 'ul'                          },
		'pre'       : { 'node' : 'pre'                         },
		'bold'      : { 'node' : 'b', 'override' : 'bold'      },
		'italic'    : { 'node' : 'i', 'override' : 'italic'    },
		'underline' : { 'node' : 'u', 'override' : 'underline' },

		/**
		 * Unformat command.
		 *
		 * @type {Object}
		 * @memberOf ui.commands
		 */
		'unformat'  : {
			'state'  : Fn.returnFalse,
			'action' : removeFormatting,
			'nodes'  : ['b', 'i', 'u', 'em', 'strong', 'sub', 'sup', 'del', 'small', 'code']
		}
	};

	return {
		bind     : bind,
		states   : states,
		command  : command,
		commands : commands
	};

});
comments powered by Disqus