vendor/assets/javascripts/aloha/lib/util/range-context.js in locomotive-aloha-rails-0.23.2.1 vs vendor/assets/javascripts/aloha/lib/util/range-context.js in locomotive-aloha-rails-0.23.2.2
- old
+ new
@@ -22,20 +22,35 @@
* non-source (e.g., minimized or compacted) forms of the Aloha-Editor
* source code without the copy of the GNU GPL normally required,
* provided you include this license notice and a URL through which
* recipients can access the Corresponding Source.
*/
+/**
+ * TODO improve restacking and joining algorithm
+ * TODO what do do about insignificant whitespace when pushing down or setting a context?
+ * TODO check contained-in rules when when pushing down or setting a context
+ * TODO formatStyle: in the following case the outer "font-family: arial" span should be removed.
+ * Can be done similar to how findReusableAncestor() works.
+ * <span style="font-family: arial">
+ * <span style="font-family: times">one</span>
+ * <span style="font-family: helvetica">two<span>
+ * </span>
+ */
define([
+ 'jquery',
'util/dom2',
'util/arrays',
'util/trees',
+ 'util/strings',
'util/functions',
'util/html'
], function (
+ $,
Dom,
Arrays,
Trees,
+ Strings,
Fn,
Html
) {
'use strict';
@@ -46,14 +61,14 @@
*/
function walkSiblings(parent, beforeAfterChild, before, at, after, arg) {
var fn = before;
Dom.walk(parent.firstChild, function (child) {
if (child !== beforeAfterChild) {
- return fn(child, arg);
+ fn(child, arg);
} else {
fn = after;
- return at(child, arg);
+ at(child, arg);
}
});
}
/**
@@ -91,10 +106,24 @@
var parent = ascendNodes[i + 1];
walkSiblings(parent, child, before, at, after, args[i + 1]);
}
}
+ function makePointNodeStep(pointNode, atEnd, stepOutsideInside, stepPartial) {
+ // Because the start node is inside the range, the end node is
+ // outside, and all ancestors of start and end are partially
+ // inside/outside (for startEnd/endEnd positions the nodes are
+ // also ancestors of the position).
+ return function (node, arg) {
+ if (node === pointNode && !atEnd) {
+ stepOutsideInside(node, arg);
+ } else {
+ stepPartial(node, arg);
+ }
+ };
+ }
+
/**
* Walks the boundary of the range.
*
* The range's boundary starts at startContainer/startOffset, goes
* up to to the commonAncestorContainer's child above or equal
@@ -116,38 +145,27 @@
var eo = liveRange.endOffset;
var start = Dom.nodeAtOffset(sc, so);
var end = Dom.nodeAtOffset(ec, eo);
var startEnd = Dom.isAtEnd(sc, so);
var endEnd = Dom.isAtEnd(ec, eo);
- var uptoCacChildStart = Dom.childAndParentsUntilNode(start, cac);
- var uptoCacChildEnd = Dom.childAndParentsUntilNode(end, cac);
- var cacChildStart = uptoCacChildStart.length ? uptoCacChildStart[uptoCacChildStart.length - 1] : null;
- var cacChildEnd = uptoCacChildEnd.length ? uptoCacChildEnd[uptoCacChildEnd.length - 1] : null;
- arg = carryDown(cac, arg) || arg;
- // Because the start node is inside the range, the end node is
- // outside, and all ancestors of start and end are partially
- // inside/outside (for startEnd/endEnd positions the nodes are
- // also ancestors of the position).
- function stepAtStart(node, arg) {
- return node === start && !startEnd
- ? stepInside(node, arg)
- : stepPartial(node, arg);
- }
- function stepAtEnd(node, arg) {
- return node === end && !endEnd
- ? stepOutside(node, arg)
- : stepPartial(node, arg);
- }
- ascendWalkSiblings(uptoCacChildStart, startEnd, carryDown, stepOutside, stepAtStart, stepInside, arg);
- ascendWalkSiblings(uptoCacChildEnd, endEnd, carryDown, stepInside, stepAtEnd, stepOutside, arg);
+ var ascStart = Dom.childAndParentsUntilNode(start, cac);
+ var ascEnd = Dom.childAndParentsUntilNode(end, cac);
+ var stepAtStart = makePointNodeStep(start, startEnd, stepInside, stepPartial);
+ var stepAtEnd = makePointNodeStep(end, endEnd, stepOutside, stepPartial);
+ ascendWalkSiblings(ascStart, startEnd, carryDown, stepOutside, stepAtStart, stepInside, arg);
+ ascendWalkSiblings(ascEnd, endEnd, carryDown, stepInside, stepAtEnd, stepOutside, arg);
+ var cacChildStart = Arrays.last(ascStart);
+ var cacChildEnd = Arrays.last(ascEnd);
if (cacChildStart && cacChildStart !== cacChildEnd) {
var next;
Dom.walkUntilNode(cac.firstChild, stepOutside, cacChildStart, arg);
- next = stepAtStart(cacChildStart, arg);
+ next = cacChildStart.nextSibling;
+ stepAtStart(cacChildStart, arg);
Dom.walkUntilNode(next, stepInside, cacChildEnd, arg);
if (cacChildEnd) {
- next = stepAtEnd(cacChildEnd, arg);
+ next = cacChildEnd.nextSibling;
+ stepAtEnd(cacChildEnd, arg);
Dom.walk(next, stepOutside, arg);
}
}
}
@@ -215,76 +233,143 @@
* style="font-weight: normal">. See hasOverrideAncestor below.
*
* @param liveRange range's boundary points should be between nodes
* (Dom.splitTextContainers).
*
- * @param isUpperBoundary args (node). Identifies exclusive upper
- * boundary element, only elements below which will be modified.
+ * @param formatter a map with the following properties
+ * isUpperBoundary(node) - identifies exclusive upper
+ * boundary element, only elements below which will be modified.
*
- * @param getOverride(node). Returns a node's override, or
- * null if the node does not provide an override. The topmost node
- * for which getOverride returns a non-null value is the topmost
- * override. If there is a topmost override, and it is below the
- * upper boundary element, it will be cleared and pushed down.
+ * getOverride(node) - returns a node's override, or null/undefined
+ * if the node does not provide an override. The topmost node for
+ * which getOverride returns a non-null value is the topmost
+ * override. If there is a topmost override, and it is below the
+ * upper boundary element, it will be cleared and pushed down.
+ * A node with an override must not also provide the context:
+ * !(null != getOverride(node) && hasContext(node))
*
- * @param clearOverride(node). Should clear the given node of an
- * override. The given node may or may not have an override
- * set. Will be invoked shallowly for all ancestors of start and end
- * containers (up to isUpperBoundary or hasContext). May perform
- * mutations as explained above.
+ * clearOverride(node) - should clear the given node of an
+ * override. The given node may or may not have an override
+ * set. Will be invoked shallowly for all ancestors of start and end
+ * containers (up to isUpperBoundary or hasContext). May perform
+ * mutations as explained above.
*
- * @parma clearOverrideRec(node). Like clearOverride but
- * should clear the override recursively.
+ * clearOverrideRec(node) - like clearOverride but should clear
+ * the override recursively. If not provided, clearOverride will
+ * be applied recursively.
*
- * @param pushDownOverride(node, override). Applies the given
- * override to node. Should check whether the given node doesn't
- * already provide its own override, in which case the given
- * override should not be applied. May perform mutations as
- * explained above.
+ * pushDownOverride(node, override) - applies the given
+ * override to node. Should check whether the given node doesn't
+ * already provide its own override, in which case the given
+ * override should not be applied. May perform mutations as
+ * explained above.
*
- * @param hasContext(node). Returns true if the given node
- * already provides the context to set.
+ * hasContext(node) - returns true if the given node
+ * already provides the context to set.
*
- * @param setContext(node, hasOverrideAncestor). Applies the context
- * to the given node. Should clear overrides recursively. Should
- * also clear context recursively to avoid unnecessarily nested
- * contexts. hasOverrideAncestor is true if an override is in effect
- * above the given node (see explanation above). May perform
- * mutations as explained above.
+ * setContext(node, hasOverrideAncestor) - applies the context
+ * to the given node. Should clear overrides recursively. Should
+ * also clear context recursively to avoid unnecessarily nested
+ * contexts. hasOverrideAncestor is true if an override is in effect
+ * above the given node (see explanation above). May perform
+ * mutations as explained above.
*/
- function mutate(liveRange, isUpperBoundary, getOverride, clearOverride, clearOverrideRec, pushDownOverride, hasContext, setContext, rootHasContext) {
+ function mutate(liveRange, formatter, rootHasImpliedContext) {
if (liveRange.collapsed) {
return;
}
// Because range may be mutated during traversal, we must only
// refer to it before traversal.
var cac = liveRange.commonAncestorContainer;
var topmostOverrideNode = null;
var bottommostOverrideNode = null;
var isNonClearableOverride = false;
var upperBoundaryAndBeyond = false;
- var fromCacToContext = Dom.childAndParentsUntilIncl(cac, hasContext);
+ var fromCacToContext = Dom.childAndParentsUntilIncl(cac, function (node) {
+ // Because we shouldn't expect hasContext to handle the
+ // document element (which has nodeType 9).
+ return !node.parentNode || 9 === node.parentNode.nodeType || formatter.hasContext(node);
+ });
Arrays.forEach(fromCacToContext, function (node) {
- upperBoundaryAndBeyond = upperBoundaryAndBeyond || isUpperBoundary(node);
- if (getOverride(node)) {
+ upperBoundaryAndBeyond = upperBoundaryAndBeyond || formatter.isUpperBoundary(node);
+ if (null != formatter.getOverride(node)) {
topmostOverrideNode = node;
isNonClearableOverride = upperBoundaryAndBeyond;
bottommostOverrideNode = bottommostOverrideNode || node;
}
});
- if ((rootHasContext || hasContext(fromCacToContext[fromCacToContext.length - 1]))
+ if ((rootHasImpliedContext || formatter.hasContext(Arrays.last(fromCacToContext)))
&& !isNonClearableOverride) {
var pushDownFrom = topmostOverrideNode || cac;
- var cacOverride = getOverride(bottommostOverrideNode || cac);
- pushDownContext(liveRange, pushDownFrom, cacOverride, getOverride, clearOverride, clearOverrideRec, pushDownOverride);
+ var cacOverride = formatter.getOverride(bottommostOverrideNode || cac);
+ var clearOverrideRec = formatter.clearOverrideRec || function (node) {
+ Dom.walkRec(node, formatter.clearOverride);
+ };
+ pushDownContext(
+ liveRange,
+ pushDownFrom,
+ cacOverride,
+ formatter.getOverride,
+ formatter.clearOverride,
+ clearOverrideRec,
+ formatter.pushDownOverride
+ );
} else {
- walkBoundary(liveRange, getOverride, pushDownOverride, clearOverride, function (node) {
- return setContext(node, isNonClearableOverride);
- });
+ var setContext = function (node) {
+ formatter.setContext(node, isNonClearableOverride);
+ };
+ walkBoundary(
+ liveRange,
+ formatter.getOverride,
+ formatter.pushDownOverride,
+ formatter.clearOverride,
+ setContext
+ );
}
}
+ function fixupRange(liveRange, mutate) {
+ // Because we are mutating the range several times and don't
+ // want the caller to see the in-between updates, and because we
+ // are using trimRange() below to adjust the range's boundary
+ // points, which we don't want the browser to re-adjust (which
+ // some browsers do).
+ var range = Dom.stableRange(liveRange);
+
+ // Because we should avoid splitTextContainers() if this call is a noop.
+ if (range.collapsed) {
+ return;
+ }
+
+ // Because trimRangeClosingOpening(), mutate() and
+ // adjustPointMoveBackWithinRange() require boundary points to
+ // be between nodes.
+ Dom.splitTextContainers(range);
+
+ // Because we want unbolding
+ // <b>one<i>two{</i>three}</b>
+ // to result in
+ // <b>one<i>two</i></b>three
+ // and not in
+ // <b>one</b><i><b>two</b></i>three
+ // and because adjustPointMoveBackWithinRange() requires the
+ // left boundary point to be next to a non-ignorable node.
+ Dom.trimRangeClosingOpening(range, Html.isIgnorableWhitespace);
+
+ // Because mutation needs to keep track and adjust boundary
+ // points.
+ var leftPoint = Dom.cursorFromBoundaryPoint(range.startContainer, range.startOffset);
+ var rightPoint = Dom.cursorFromBoundaryPoint(range.endContainer, range.endOffset);
+
+ mutate(range, leftPoint, rightPoint);
+
+ // Because we must reflect the adjusted boundary points in the
+ // given range.
+ Dom.setRangeStartFromCursor(liveRange, leftPoint);
+ Dom.setRangeEndFromCursor(liveRange, rightPoint);
+ }
+
function adjustPointShallowRemove(point, left, node) {
if (point.node === node) {
point.next();
}
}
@@ -312,19 +397,19 @@
if (point.node === node && !point.atEnd) {
point.node = wrapper;
}
}
- function shallowRemoveAdjust(node, leftPoint, rightPoint) {
+ function removeShallowAdjust(node, leftPoint, rightPoint) {
adjustPointShallowRemove(leftPoint, true, node);
adjustPointShallowRemove(rightPoint, false, node);
- Dom.shallowRemove(node);
+ Dom.removeShallow(node);
}
function wrapAdjust(node, wrapper, leftPoint, rightPoint) {
if (wrapper.parentNode) {
- shallowRemoveAdjust(wrapper, leftPoint, rightPoint);
+ removeShallowAdjust(wrapper, leftPoint, rightPoint);
}
adjustPointWrap(leftPoint, true, node, wrapper);
adjustPointWrap(rightPoint, false, node, wrapper);
Dom.wrap(node, wrapper);
}
@@ -333,25 +418,19 @@
adjustPointMoveBackWithinRange(leftPoint, true, node, ref, atEnd);
adjustPointMoveBackWithinRange(rightPoint, false, node, ref, atEnd);
Dom.insert(node, ref, atEnd);
}
- function nextSibling(node) {
- return node.nextSibling;
- }
-
- // TODO when restacking the <b> that wraps "z" in
- // <u><b>x</b><s><b>z</b></s></u>, join with the <b> that wraps "x".
function restackRec(node, hasContext, notIgnoreHorizontal, notIgnoreVertical) {
if (1 !== node.nodeType || notIgnoreVertical(node)) {
return null;
}
- var maybeContext = Dom.walkUntil(node.firstChild, nextSibling, notIgnoreHorizontal);
+ var maybeContext = Dom.next(node.firstChild, notIgnoreHorizontal);
if (!maybeContext) {
return null;
}
- var notIgnorable = Dom.walkUntil(maybeContext.nextSibling, nextSibling, notIgnoreHorizontal);
+ var notIgnorable = Dom.next(maybeContext.nextSibling, notIgnoreHorizontal);
if (notIgnorable) {
return null;
}
if (hasContext(maybeContext)) {
return maybeContext;
@@ -373,156 +452,325 @@
}
wrapAdjust(node, context, leftPoint, rightPoint);
return true;
}
- function format(liveRange, nodeName, unformat) {
- var leftPoint;
- var rightPoint;
+ function ensureWrapper(node, nodeName, hasWrapper, leftPoint, rightPoint) {
+ if (node.previousSibling && !hasWrapper(node.previousSibling)) {
+ // Because restacking here solves two problems: one the
+ // case where the context was unnecessarily pushed down
+ // on the left of the range, and two to join with a
+ // context node that already exists to the left of the
+ // range.
+ restack(node.previousSibling,
+ hasWrapper,
+ Html.isIgnorableWhitespace,
+ Html.isInlineFormattable,
+ leftPoint,
+ rightPoint);
+ }
+ if (node.previousSibling && hasWrapper(node.previousSibling)) {
+ insertAdjust(node, node.previousSibling, true, leftPoint, rightPoint);
+ return true;
+ }
+ if (!hasWrapper(node)) {
+ var wrapper = document.createElement(nodeName);
+ wrapAdjust(node, wrapper, leftPoint, rightPoint);
+ return true;
+ }
+ return false;
+ }
+ function isUpperBoundary_default(node) {
+ // Because the body element is an obvious upper boundary, and
+ // because, when we are inside an editable, we shouldn't make
+ // modifications outside the editable (if we are not inside
+ // an editable, we don't care).
+ return 'BODY' === node.nodeName || Html.isEditingHost(node);
+ }
+
+ function makeNodeFormatter(nodeName, leftPoint, rightPoint) {
function hasContext(node) {
- if (unformat) {
- // Because we pass rootHasContext=true to mutate.
- return false;
- }
return nodeName === node.nodeName;
}
- function getOverride(node) {
- if (!unformat) {
- return false;
+ function clearContext(node) {
+ if (nodeName === node.nodeName) {
+ removeShallowAdjust(node, leftPoint, rightPoint);
}
- return nodeName === node.nodeName;
}
- function hasOverride(node) {
- return !!getOverride(node);
+ function clearContextRec(node) {
+ Dom.walkRec(node, clearContext);
}
- function clearOverride(node) {
- var next = node.nextSibling;
- if (unformat && nodeName === node.nodeName) {
- shallowRemoveAdjust(node, leftPoint, rightPoint);
+ function setContext(node) {
+ if (ensureWrapper(node, nodeName, hasContext, leftPoint, rightPoint)) {
+ // Because the node was wrapped with a context, and if
+ // the node itself has the context, it should be cleared
+ // to avoid nested contexts.
+ clearContextRec(node);
+ } else {
+ // Because the node itself has the context and was not
+ // wrapped, we must only clear its children.
+ Dom.walk(node.firstChild, clearContextRec);
}
- return next;
}
- function clearOverrideRec(node) {
- return Dom.walkRec(node, clearOverride);
+ return {
+ hasContext: hasContext,
+ getOverride: Fn.noop,
+ clearOverride: Fn.noop,
+ pushDownOverride: Fn.noop,
+ setContext: setContext,
+ isUpperBoundary: isUpperBoundary_default
+ };
+ }
+
+ function makeNodeUnformatter(nodeName, leftPoint, rightPoint) {
+
+ function getOverride(node) {
+ return nodeName === node.nodeName ? true : null;
}
- function clearContext(node) {
- var next = node.nextSibling;
- if (!unformat && nodeName === node.nodeName) {
- shallowRemoveAdjust(node, leftPoint, rightPoint);
+ function clearOverride(node) {
+ if (nodeName === node.nodeName) {
+ removeShallowAdjust(node, leftPoint, rightPoint);
}
- return next;
}
- function clearContextRec(node) {
- return Dom.walkRec(node, clearContext);
+ function pushDownOverride(node, override) {
+ if (!override) {
+ return;
+ }
+ ensureWrapper(node, nodeName, getOverride, leftPoint, rightPoint);
}
- function ensureWrapper(node, hasWrapper) {
- if (node.previousSibling && !hasWrapper(node.previousSibling)) {
- // Because restacking here solves two problems: one the
- // case where the context was unnecessarily pushed down
- // on the left of the range, and two to join with a
- // context node that already exists to the left of the
- // range.
- restack(node.previousSibling,
- hasWrapper,
- Html.isIgnorableWhitespace,
- Html.isInlineFormattable,
- leftPoint, rightPoint);
+ return {
+ hasContext: Fn.returnFalse,
+ setContext: Fn.noop,
+ getOverride: getOverride,
+ clearOverride: clearOverride,
+ pushDownOverride: pushDownOverride,
+ isUpperBoundary: isUpperBoundary_default
+ };
+ }
+
+ function createStyleWrapper_default() {
+ return document.createElement('SPAN');
+ }
+
+ function isStyleEq_default(styleValueA, styleValueB) {
+ return styleValueA === styleValueB;
+ }
+
+ function isStyleWrapperReusable_default(node) {
+ return 'SPAN' === node.nodeName;
+ }
+
+ function isStyleWrapperPrunable_default(node) {
+ return ('SPAN' === node.nodeName
+ && Arrays.every(Arrays.map(Dom.attrs(node), Arrays.second),
+ Strings.empty));
+ }
+
+ function makeStyleFormatter(styleName, styleValue, createWrapper, isStyleEq, isReusable, isPrunable, leftPoint, rightPoint) {
+
+ function removeStyle(node, styleName) {
+ if (Strings.empty(Dom.getStyle(node, styleName))) {
+ return;
}
- if (node.previousSibling && hasWrapper(node.previousSibling)) {
- insertAdjust(node, node.previousSibling, true, leftPoint, rightPoint);
- return true;
- } else if (!hasWrapper(node)) {
- var wrapper = document.createElement(nodeName);
- wrapAdjust(node, wrapper, leftPoint, rightPoint);
- return true;
+ Dom.setStyle(node, styleName, null);
+ if (isPrunable(node)) {
+ removeShallowAdjust(node, leftPoint, rightPoint);
}
- return false;
}
- function pushDownOverride(node, override) {
- if (!override) {
- return node.nextSibling;
+ function setStyle(node, styleName, styleValue, prevWrapper) {
+ if (prevWrapper && prevWrapper === node.previousSibling) {
+ insertAdjust(node, prevWrapper, true, leftPoint, rightPoint);
+ removeStyle(node, styleName);
+ return prevWrapper;
}
- if (!unformat) {
- throw "not implemented";
+ if (isReusable(node)) {
+ Dom.setStyle(node, styleName, styleValue);
+ return prevWrapper;
}
- var next = node.nextSibling;
- ensureWrapper(node, hasOverride);
- return next;
+ var wrapper = createWrapper();
+ Dom.setStyle(wrapper, styleName, styleValue);
+ wrapAdjust(node, wrapper, leftPoint, rightPoint);
+ removeStyle(node, styleName);
+ return wrapper;
}
- function setContext(node) {
- if (unformat) {
- throw "not implemented";
+ function hasContext(node) {
+ return isStyleEq(Dom.getStyle(node, styleName), styleValue);
+ }
+
+ function getOverride(node) {
+ var override = Dom.getStyle(node, styleName);
+ return (Strings.empty(override) || isStyleEq(override, styleValue)
+ ? null
+ : override);
+ }
+
+ function clearOverride(node) {
+ removeStyle(node, styleName);
+ }
+
+ function clearOverrideRec(node) {
+ Dom.walkRec(node, clearOverride);
+ }
+
+ var overrideWrapper = null;
+ function pushDownOverride(node, override) {
+ if (Strings.empty(override) || !Strings.empty(Dom.getStyle(node, styleName))) {
+ return;
}
- var next = node.nextSibling;
- if (ensureWrapper(node, hasContext)) {
- // Because the node was wrapped with a context, and if
- // the node itself has the context, it should be cleared
- // to avoid nested contexts.
- clearContextRec(node);
- } else {
- // Because the node itself has the context and was not
- // wrapped, we must only clear its children.
- Dom.walk(node.firstChild, clearContextRec);
- }
- clearOverrideRec(node);
- return next;
+ overrideWrapper = setStyle(node, styleName, override, overrideWrapper);
}
- function isUpperBoundary(node) {
- return 'BODY' === node.nodeName;
+ var contextWrapper = null;
+ function setContext(node) {
+ Dom.walk(node.firstChild, clearOverrideRec);
+ contextWrapper = setStyle(node, styleName, styleValue, contextWrapper);
}
- // Because we are mutating the range several times and don't
- // want the caller to see the in-between updates, and because we
- // are using trimRange() below to adjust the range's boundary
- // points, which we don't want the browser to re-adjust (which
- // some browsers do).
- var range = Dom.stableRange(liveRange);
+ return {
+ hasContext: hasContext,
+ getOverride: getOverride,
+ clearOverride: clearOverride,
+ pushDownOverride: pushDownOverride,
+ setContext: setContext,
+ isUpperBoundary: isUpperBoundary_default
+ };
+ }
- // Because we should avoid splitTextContainers() if this call is a noop.
- if (range.collapsed) {
- return;
+ function format(liveRange, nodeName) {
+ fixupRange(liveRange, function (range, leftPoint, rightPoint) {
+ mutate(range, makeNodeFormatter(nodeName, leftPoint, rightPoint));
+ });
+ }
+
+ function unformat(liveRange, nodeName) {
+ fixupRange(liveRange, function (range, leftPoint, rightPoint) {
+ mutate(range, makeNodeUnformatter(nodeName, leftPoint, rightPoint), true);
+ });
+ }
+
+ function findReusableAncestor(range, hasContext, getOverride, isUpperBoundary, isReusable) {
+ var obstruction = null;
+ function untilIncl(node) {
+ return (null != getOverride(node)
+ || hasContext(node)
+ || isReusable(node)
+ || isUpperBoundary(node));
}
+ function beforeAfter(node) {
+ obstruction = obstruction || !Html.isIgnorableWhitespace(node);
+ }
+ var start = Dom.nodeAtOffset(range.startContainer, range.startOffset);
+ var end = Dom.nodeAtOffset(range.endContainer, range.endOffset);
+ var startEnd = Dom.isAtEnd(range.startContainer, range.startOffset);
+ var endEnd = Dom.isAtEnd(range.endContainer, range.endOffset);
+ var ascStart = Dom.childAndParentsUntilIncl(start, untilIncl);
+ var ascEnd = Dom.childAndParentsUntilIncl(end, untilIncl);
+ var reusable = Arrays.last(ascStart);
+ function at(node) {
+ // Because the start node is inside the range.
+ if (node === start && !startEnd) {
+ return;
+ }
+ // Because the end node is outside the range.
+ if (node === end && !endEnd) {
+ beforeAfter(node);
+ return;
+ }
+ obstruction = obstruction || !Html.isInlineFormattable(node);
+ }
+ if (!reusable || !isReusable(reusable) || reusable !== Arrays.last(ascEnd)) {
+ return null;
+ }
+ ascendWalkSiblings(ascStart, startEnd, Fn.noop, beforeAfter, at, Fn.noop);
+ if (obstruction) {
+ return null;
+ }
+ ascendWalkSiblings(ascEnd, endEnd, Fn.noop, Fn.noop, at, beforeAfter);
+ if (obstruction) {
+ return null;
+ }
+ return reusable;
+ }
- // Because trimRangeClosingOpening(), mutate() and
- // adjustPointMoveBackWithinRange() require boundary points to
- // be between nodes.
- Dom.splitTextContainers(range);
+ function formatStyle(liveRange, styleName, styleValue, createWrapper, isStyleEq, isReusable, isPrunable) {
+ createWrapper = createWrapper || createStyleWrapper_default;
+ isStyleEq = isStyleEq || isStyleEq_default;
+ isReusable = isReusable || isStyleWrapperReusable_default;
+ isPrunable = isPrunable || isStyleWrapperPrunable_default;
+ fixupRange(liveRange, function (range, leftPoint, rightPoint) {
+ var formatter = makeStyleFormatter(
+ styleName,
+ styleValue,
+ createWrapper,
+ isStyleEq,
+ isReusable,
+ isPrunable,
+ leftPoint,
+ rightPoint
+ );
+ var reusableAncestor = findReusableAncestor(
+ range,
+ formatter.hasContext,
+ formatter.getOverride,
+ formatter.isUpperBoundary,
+ isReusable
+ );
+ if (reusableAncestor) {
+ formatter.setContext(reusableAncestor);
+ } else {
+ mutate(range, formatter, false);
+ }
+ });
+ }
- // Because we want unbolding
- // <b>one<i>two{</i>three}</b>
- // to result in
- // <b>one<i>two</i></b>three
- // and not in
- // <b>one</b><i><b>two</b></i>three
- // and because adjustPointMoveBackWithinRange() requires the
- // left boundary point to be next to a non-ignorable node.
- Dom.trimRangeClosingOpening(range, Html.isIgnorableWhitespace);
+ function splitBoundary(liveRange, pred, clone) {
+ clone = clone || Dom.cloneShallow;
+ fixupRange(liveRange, function (range, leftPoint, rightPoint) {
- // Because mutation needs to keep track and adjust boundary
- // points.
- leftPoint = Dom.cursorFromBoundaryPoint(range.startContainer, range.startOffset);
- rightPoint = Dom.cursorFromBoundaryPoint(range.endContainer, range.endOffset);
+ var wrapper = null;
- mutate(range, isUpperBoundary, getOverride, clearOverride, clearOverrideRec, pushDownOverride, hasContext, setContext, unformat);
+ function carryDown(elem, stop) {
+ return stop || !pred(elem);
+ }
- // Because we must reflect the adjusted boundary points in the
- // given range.
- Dom.setRangeStartFromCursor(liveRange, leftPoint);
- Dom.setRangeEndFromCursor(liveRange, rightPoint);
+ function pushDown(node, stop) {
+ if (stop) {
+ return;
+ }
+ if (!wrapper || node.parentNode.previousSibling !== wrapper) {
+ wrapper = clone(node.parentNode);
+ insertAdjust(wrapper, node.parentNode, false, leftPoint, rightPoint);
+ }
+ insertAdjust(node, wrapper, true, leftPoint, rightPoint);
+ }
+
+ var sc = range.startContainer;
+ var so = range.startOffset;
+ var ec = range.endContainer;
+ var eo = range.endOffset;
+ var cac = range.commonAncestorContainer;
+ var startEnd = Dom.isAtEnd(sc, so);
+ var endEnd = Dom.isAtEnd(ec, eo);
+ var ascStart = Dom.childAndParentsUntilNode(Dom.nodeAtOffset(sc, so), cac);
+ var ascEnd = Dom.childAndParentsUntilNode(Dom.nodeAtOffset(ec, eo), cac);
+ ascendWalkSiblings(ascStart, startEnd, carryDown, pushDown, Fn.noop, Fn.noop, null);
+ ascendWalkSiblings(ascEnd, endEnd, carryDown, pushDown, Fn.noop, Fn.noop, null);
+ });
}
return {
- mutate: mutate,
- format: format
+ format: format,
+ unformat: unformat,
+ formatStyle: formatStyle,
+ splitBoundary: splitBoundary
};
});