UNPKG

node-webodf

Version:

WebODF - JavaScript Document Engine http://webodf.org/

282 lines (255 loc) 12.6 kB
<!DOCTYPE html> <html> <head> <title>Direct Formatting</title> <script type="text/javascript"> 'use strict'; 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"> 'use strict'; 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>