/**
* markers.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 markers
*/
define([
'dom',
'misc',
'mutation',
'arrays',
'strings',
'ranges',
'paths',
'boundaries'
], function (
Dom,
Misc,
Mutation,
Arrays,
Strings,
Ranges,
Paths,
Boundaries
) {
'use strict';
var augmentedMarks = {
'TEXT_LEFT' : '▓[',
'TEXT_RIGHT' : ']▓',
'ELEMENT_LEFT' : '▓{',
'ELEMENT_RIGHT' : '}▓',
'TEXT_SINGLE' : '▓',
'ELEMENT_SINGLE' : '█'
};
var marks = {
'TEXT_LEFT' : '[',
'TEXT_RIGHT' : ']',
'ELEMENT_LEFT' : '{',
'ELEMENT_RIGHT' : '}',
'TEXT_SINGLE' : '¦',
'ELEMENT_SINGLE' : '|'
};
/**
* Insert boundary markers at the given boundaries.
*
* @param {!Boundary} start
* @param {!Boundary} end
* @param {boolean=} augment
* @return {Array.<Boundary>}
* @memberOf markers
*/
function insert(start, end, augment) {
var markers = augment ? augmentedMarks : marks;
var startContainer = Boundaries.container(start);
var endContainer = Boundaries.container(end);
var doc = startContainer.ownerDocument;
var startMarker = doc.createTextNode(Dom.isTextNode(endContainer)
? markers['TEXT_RIGHT']
: markers['ELEMENT_RIGHT']);
var endMarker = doc.createTextNode(Dom.isTextNode(startContainer)
? markers['TEXT_LEFT']
: markers['ELEMENT_LEFT']);
var range = Boundaries.range(start, end);
start = Mutation.splitBoundary(Boundaries.fromRangeStart(range), [range]);
end = Mutation.splitBoundary(Boundaries.fromRangeEnd(range));
Dom.insert(startMarker, Boundaries.nextNode(end), Boundaries.isAtEnd(end));
Dom.insert(endMarker, Boundaries.nextNode(start), Boundaries.isAtEnd(start));
return [start, end];
}
/**
* Insert a single boundary marker at the given boundary.
*
* @param {!Boundary} boundary
* @param {boolean=} augment
* @return {Boundary}
*/
function insertSingle(boundary, augment) {
var markers = augment ? augmentedMarks : marks;
var container = Boundaries.container(boundary);
var marker = container.ownerDocument.createTextNode(
Boundaries.isTextBoundary(boundary)
? markers['TEXT_SINGLE']
: markers['ELEMENT_SINGLE']
);
boundary = Mutation.splitBoundary(boundary);
Dom.insert(marker, Boundaries.nextNode(boundary), Boundaries.isAtEnd(boundary));
return boundary;
}
/**
* Set the selection based on selection markers found in the content inside
* of `rootElem`.
*
* @param {Element} rootElem
* @return {Array.<Boundary>}
* @memberOf markers
*/
function extract(rootElem) {
var markers = ['[', '{', '}', ']'];
var markersFound = 0;
var boundaries = [];
function setBoundaryPoint(marker, node) {
var whichBoundary;
if (0 === markersFound) {
whichBoundary = 0;
if (marker !== '[' && marker !== '{') {
throw 'end marker before start marker';
}
} else if (1 === markersFound) {
whichBoundary = 1;
if (marker !== ']' && marker !== '}') {
throw 'start marker before end marker';
}
} else {
throw 'Too many markers';
}
markersFound += 1;
if (marker === '[' || marker === ']') {
var previousSibling = node.previousSibling;
if (!previousSibling || !Dom.isTextNode(previousSibling)) {
previousSibling = node.ownerDocument.createTextNode('');
node.parentNode.insertBefore(previousSibling, node);
}
boundaries[whichBoundary] = [previousSibling, previousSibling.length];
// Because we have set a text offset.
return false;
}
boundaries[whichBoundary] = [node.parentNode, Dom.nodeIndex(node)];
// Because we have set a non-text offset.
return true;
}
function extractMarkers(node) {
if (!Dom.isTextNode(node)) {
return;
}
var text = node.nodeValue;
var parts = Strings.splitIncl(text, /[\[\{\}\]]/g);
// Because modifying every text node when there can be only two
// markers seems like too much overhead
if (!Arrays.contains(markers, parts[0]) && parts.length < 2) {
return;
}
// Because non-text boundary positions must not be joined again
var forceNextSplit = false;
parts.forEach(function (part, i) {
// Because we don't want to join text nodes we haven't split
forceNextSplit = forceNextSplit || (i === 0);
if (Arrays.contains(markers, part)) {
forceNextSplit = setBoundaryPoint(part, node);
} else if (!forceNextSplit
&& node.previousSibling
&& Dom.isTextNode(node.previousSibling)) {
node.previousSibling.insertData(
node.previousSibling.length,
part
);
} else {
node.parentNode.insertBefore(
node.ownerDocument.createTextNode(part),
node
);
}
});
node.parentNode.removeChild(node);
}
Dom.walkRec(rootElem, extractMarkers);
if (2 !== markersFound) {
throw 'Missing one or both markers';
}
return boundaries;
}
/**
* Returns a string with boundary markers inserted into the representation
* of the DOM to indicate the span of the given range.
*
* @private
* @param {!Boundary} start
* @param {Boundary=} end
* @param {augment=} augment
* @return {string}
*/
function show(start, end, augment) {
var single = !end;
end = end || start;
var cac = Boundaries.commonContainer(start, end);
var doc = Dom.Nodes.DOCUMENT === cac.nodeType
? cac
: cac.ownerDocument;
var startPath = Paths.fromBoundary(cac, start);
var endPath = Paths.fromBoundary(cac, end);
var clone;
var root;
if (cac.parentNode) {
root = Paths.fromBoundary(
cac.parentNode,
Boundaries.fromFrontOfNode(cac)
);
clone = Boundaries.container(
Paths.toBoundary(cac.parentNode.cloneNode(true), root)
);
} else {
clone = cac.cloneNode(true);
var one = doc.createDocumentFragment();
var two = doc.createDocumentFragment();
Dom.append(clone, two);
Dom.append(two, one);
root = [];
}
startPath = root.concat(startPath);
endPath = root.concat(endPath);
if (single) {
insertSingle(Paths.toBoundary(clone, startPath), augment);
} else {
insert(
Paths.toBoundary(clone, startPath),
Paths.toBoundary(clone, endPath),
augment
);
}
if (Dom.Nodes.DOCUMENT_FRAGMENT !== clone.nodeType) {
return clone.outerHTML;
}
var node = doc.createElement('div');
Dom.append(clone, node);
return node.innerHTML;
}
function rawBoundariesFromRange(range) {
return [
Boundaries.raw(range.startContainer, range.startOffset),
Boundaries.raw(range.endContainer, range.endOffset)
];
}
/**
* Returns string representation of the given boundary boundaries tuple or
* range.
*
* If the option argument augment is set to true, the markers will be
* rendered with an extra character along side it to make it easier to see
* it in the output.
*
* @param {!Boundary|Array.<Boundary>|Range} selection
* @param {boolean=} augment
* @return {string}
* @memberOf markers
*/
function hint(selection, augment) {
if (Misc.defined(selection.length)) {
return ('string' === typeof selection[0].nodeName)
? show(selection, selection, augment)
: show(selection[0], selection[1], augment);
}
var boundaries = rawBoundariesFromRange(selection);
return show(boundaries[0], boundaries[1], augment);
}
return {
hint : hint,
insert : insert,
extract : extract
};
});