node-webodf
Version:
WebODF - JavaScript Document Engine http://webodf.org/
282 lines (255 loc) • 12.6 kB
HTML
<html>
<head>
<title>Direct Formatting</title>
<script type="text/javascript">var runtime = {
getWindow : function() { return window; },
getDOM : function() { return document; },
log : function(msg) { console.log(msg); }
};
function LoopWatchDog(timeout, maxChecks) {
"use strict";
var startTime = Date.now(),
checks = 0;
function check() {
var t;
if (timeout) {
t = Date.now();
if (t - startTime > timeout) {
runtime.log("alert", "watchdog timeout");
throw "timeout!";
}
}
if (maxChecks > 0) {
checks += 1;
if (checks > maxChecks) {
runtime.log("alert", "watchdog loop overflow");
throw "loop overflow";
}
}
}
this.check = check;
}
function containsNode(range, node) {
var nodeRange = document.createRange(),
result;
nodeRange.selectNode(node);
result = range.compareBoundaryPoints(range.START_TO_START, nodeRange) === -1 &&
range.compareBoundaryPoints(range.END_TO_END, nodeRange) === 1;
nodeRange.detach();
return result;
}
function getTextNodes(document, range, nodeRange) {
var root = /**@type {!Node}*/ (range.commonAncestorContainer.nodeType === Node.TEXT_NODE ?
range.commonAncestorContainer.parentNode : range.commonAncestorContainer),
treeWalker = document.createTreeWalker(root,
NodeFilter.SHOW_ALL,
function (node) {
nodeRange.selectNode(node);
if (range.compareBoundaryPoints(range.END_TO_START, nodeRange) === -1 &&
range.compareBoundaryPoints(range.START_TO_END, nodeRange) === 1) {
return node.nodeType === Node.TEXT_NODE ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
}
return NodeFilter.FILTER_REJECT;
},
false);
treeWalker.currentNode = range.startContainer.previousSibling || range.startContainer.parentNode;
// Make the first call to nextNode return startContainer
return treeWalker;
}
function isParagraph(node) {
return node.localName === 'p' || node.localName === 'h';
}
function StyleApplicator(range, info) {
var nextTextNodes;
function isStyleApplied(textNode) {
var style = window.getComputedStyle(textNode.parentNode, null);
return style && Object.keys(info).every(function(key) { return info[key] === style[key]; });
}
function splitBoundaries(range) {
var newNode;
// Must split end first to stop the start point from being lost
if (range.endOffset !== 0
&& range.endContainer.nodeType === Node.TEXT_NODE
&& range.endOffset !== range.endContainer.length) {
nextTextNodes.push(range.endContainer.splitText(range.endOffset));
// The end doesn't need to be reset as endContainer & endOffset are still valid after the modification
}
if (range.startOffset !== 0
&& range.startContainer.nodeType === Node.TEXT_NODE
&& range.startOffset !== range.startContainer.length) {
newNode = range.startContainer.splitText(range.startOffset);
nextTextNodes.push(newNode);
range.setStart(newNode, 0);
}
}
function moveToNewSpan(textNode) {
var originalContainer = textNode.parentNode,
parentContainer,
styledContainer,
parentInsertionNode,
unstyledContainer,
unstyledInsertionPoint,
passedRange = false,
loopGuard = new LoopWatchDog(false, 100);
if (isParagraph(originalContainer)) {
// Text node is at the first level within a paragraph element
parentContainer = originalContainer;
// No other spans encapsulate this node, so create a new one to do this
styledContainer = textNode.ownerDocument.createElement('span');
parentInsertionNode = textNode;
// Insert trailing siblings after the new styledContainer
unstyledContainer = originalContainer;
unstyledInsertionPoint = textNode;
} else if(!textNode.previousSibling) {
// The text node is the first child in a span element
parentContainer = originalContainer.parentNode;
// Claim the original span as the styled container
styledContainer = originalContainer;
parentInsertionNode = originalContainer.nextSibling;
// Insert trailing siblings into a clone of the original container
unstyledContainer = null; // Created if necessary
unstyledInsertionPoint = null; // Brand new container, so simply append unstyled children
} else {
// The text node has some preceding elements
parentContainer = originalContainer.parentNode;
// A new styled container is necessary as the preceding elements will be left in the original place
styledContainer = originalContainer.cloneNode(false);
parentInsertionNode = originalContainer.nextSibling;
// Insert trailing siblings into a clone of the original container
unstyledContainer = null; // Created if necessary
unstyledInsertionPoint = null; // Brand new container, so simply append unstyled children
}
if (!parentContainer.contains(styledContainer)) {
parentContainer.insertBefore(styledContainer, parentInsertionNode);
}
while (textNode.nextSibling) {
loopGuard.check();
if (!passedRange && containsNode(range, textNode.nextSibling)) {
styledContainer.appendChild(textNode.nextSibling);
} else {
if (!unstyledContainer) {
// A new container is necessary to hold the trailing unstyled children
unstyledContainer = originalContainer.cloneNode(false);
parentContainer.insertBefore(unstyledContainer, parentInsertionNode);
}
passedRange = true;
unstyledContainer.insertBefore(textNode.nextSibling, unstyledInsertionPoint);
}
}
if (!styledContainer.contains(textNode)) {
styledContainer.insertBefore(textNode, styledContainer.firstChild);
}
return styledContainer;
}
function processNode(textNode) {
var isStyled = isStyleApplied(textNode),
container;
if (isStyled === false) {
container = moveToNewSpan(textNode);
Object.keys(info).forEach(function(key) {
container.style[key] = info[key];
});
}
}
function mergeTextNodes(node1, node2) {
if (node1.nodeType === Node.TEXT_NODE) {
if (node1.length === 0) {
node1.parentNode.removeChild(node1);
} else if (node2.nodeType === Node.TEXT_NODE) {
node2.insertData(0, node1.data);
node1.parentNode.removeChild(node1);
return node2;
}
}
return node1;
}
function cleanupTextNode(node) {
if (node.nextSibling) {
node = mergeTextNodes(node, node.nextSibling);
}
if (node.previousSibling) {
mergeTextNodes(node.previousSibling, node);
}
}
this.applyStyle = function() {
var document = runtime.getWindow().document,
nodeRange = document.createRange(),
iterator, n;
nextTextNodes = []; // Reset instance node-modified stack
splitBoundaries(range);
iterator = getTextNodes(document, range, nodeRange);
n = iterator.nextNode();
while (n) {
processNode(n);
n = iterator.nextNode();
}
nextTextNodes.forEach(cleanupTextNode);
nodeRange.detach();
}
}
;
</script>
<script type="text/javascript">function range(startNode, startOffset, endNode, endOffset) {
var range = document.createRange();
range.setStart(startNode, startOffset);
range.setEnd(endNode, endOffset);
return range
}
function applyStyle(range, info) {
var applicator = new StyleApplicator(range, info);
applicator.applyStyle();
}
/**
* @param {!Element} parent
* @param {...Number} children
* @return {!Element} parent
*/
function child() {
var args = Array.prototype.slice.call(arguments, 0),
parent = args.shift(),
children = args.slice(0),
node = parent;
while (children.length && node) {
node = node.childNodes[children.shift()];
}
return node;
}
function init() {
var p = document.getElementsByTagName('p'),
ranges = [];
ranges.push(range(child(p[0], 0), 2, child(p[0], 0), 5));
ranges.push(range(child(p[1], 0, 0), 2, child(p[1], 0, 0), 5));
ranges.push(range(child(p[2], 1, 0), 0, child(p[2], 1, 0), 3));
ranges.push(range(child(p[3], 1), 0, child(p[3], 1), 3));
ranges.push(range(child(p[4], 0, 0), 2, child(p[4], 0, 0), 5));
ranges.push(range(child(p[5], 0, 0), 2, child(p[5], 0, 0), 5));
ranges.push(range(child(p[6], 0), 2, child(p[6], 1, 0), 3));
ranges.push(range(child(p[7], 0, 0, 0), 2, child(p[7], 0, 0, 0), 5));
ranges.push(range(child(p[8], 1, 0), 0, child(p[8], 2, 0), 1));
ranges.push(range(child(p[9], 0), 2, child(p[9], 0), 5));
ranges.push(range(child(p[10], 0), 0, child(p[10], 0), 3));
ranges.forEach(function(range, i) {
console.log('Processing range ' + i);
applyStyle(range, { "color" : "red" });
})
}
;
</script>
</head>
<body onload="init()">
<p>ABCDEFG</p>
<p><span>ABCDEFG</span></p>
<p><span>AB</span><span>CDE</span><span>FG</span></p>
<p><span>AB</span>CDE<span>FG</span></p>
<p><span style="background-color: lightblue">ABCDEFG</span></p>
<p><span style="color: red">ABCDEFG</span></p>
<p>ABC<span style="color: red">DEFG</span></p>
<p><span><span>ABCDEFG</span></span></p>
<p>AB<span>CD</span><span style="text-decoration: underline">EFG</span></p>
<p>ABCDE</p>
<p>CDEFG</p>
</body>
</html>