UNPKG

jsondiffpatch

Version:
282 lines (251 loc) 8.8 kB
var base = require('./base'); var BaseFormatter = base.BaseFormatter; var HtmlFormatter = function HtmlFormatter() {}; HtmlFormatter.prototype = new BaseFormatter(); function htmlEscape(text) { var html = text; var replacements = [ [/&/g, '&amp;'], [/</g, '&lt;'], [/>/g, '&gt;'], [/'/g, '&apos;'], [/"/g, '&quot;'] ]; for (var i = 0; i < replacements.length; i++) { html = html.replace(replacements[i][0], replacements[i][1]); } return html; } HtmlFormatter.prototype.typeFormattterErrorFormatter = function(context, err) { context.out('<pre class="jsondiffpatch-error">' + err + '</pre>'); }; HtmlFormatter.prototype.formatValue = function(context, value) { context.out('<pre>' + htmlEscape(JSON.stringify(value, null, 2)) + '</pre>'); }; HtmlFormatter.prototype.formatTextDiffString = function(context, value) { var lines = this.parseTextDiff(value); context.out('<ul class="jsondiffpatch-textdiff">'); for (var i = 0, l = lines.length; i < l; i++) { var line = lines[i]; context.out('<li>' + '<div class="jsondiffpatch-textdiff-location">' + '<span class="jsondiffpatch-textdiff-line-number">' + line.location.line + '</span>' + '<span class="jsondiffpatch-textdiff-char">' + line.location.chr + '</span>' + '</div>' + '<div class="jsondiffpatch-textdiff-line">'); var pieces = line.pieces; for (var pieceIndex = 0, piecesLength = pieces.length; pieceIndex < piecesLength; pieceIndex++) { /* global unescape */ var piece = pieces[pieceIndex]; context.out('<span class="jsondiffpatch-textdiff-' + piece.type + '">' + htmlEscape(unescape(piece.text)) + '</span>'); } context.out('</div></li>'); } context.out('</ul>'); }; var adjustArrows = function jsondiffpatchHtmlFormatterAdjustArrows(node) { node = node || document; var getElementText = function(el) { return el.textContent || el.innerText; }; var eachByQuery = function(el, query, fn) { var elems = el.querySelectorAll(query); for (var i = 0, l = elems.length; i < l; i++) { fn(elems[i]); } }; var eachChildren = function(el, fn) { for (var i = 0, l = el.children.length; i < l; i++) { fn(el.children[i], i); } }; eachByQuery(node, '.jsondiffpatch-arrow', function(arrow) { var arrowParent = arrow.parentNode; var svg = arrow.children[0], path = svg.children[1]; svg.style.display = 'none'; var destination = getElementText(arrowParent.querySelector('.jsondiffpatch-moved-destination')); var container = arrowParent.parentNode; var destinationElem; eachChildren(container, function(child) { if (child.getAttribute('data-key') === destination) { destinationElem = child; } }); if (!destinationElem) { return; } try { var distance = destinationElem.offsetTop - arrowParent.offsetTop; svg.setAttribute('height', Math.abs(distance) + 6); arrow.style.top = (-8 + (distance > 0 ? 0 : distance)) + 'px'; var curve = distance > 0 ? 'M30,0 Q-10,' + Math.round(distance / 2) + ' 26,' + (distance - 4) : 'M30,' + (-distance) + ' Q-10,' + Math.round(-distance / 2) + ' 26,4'; path.setAttribute('d', curve); svg.style.display = ''; } catch (err) { return; } }); }; HtmlFormatter.prototype.rootBegin = function(context, type, nodeType) { var nodeClass = 'jsondiffpatch-' + type + (nodeType ? ' jsondiffpatch-child-node-type-' + nodeType : ''); context.out('<div class="jsondiffpatch-delta ' + nodeClass + '">'); }; HtmlFormatter.prototype.rootEnd = function(context) { context.out('</div>' + (context.hasArrows ? ('<script type="text/javascript">setTimeout(' + adjustArrows.toString() + ',10);</script>') : '')); }; HtmlFormatter.prototype.nodeBegin = function(context, key, leftKey, type, nodeType) { var nodeClass = 'jsondiffpatch-' + type + (nodeType ? ' jsondiffpatch-child-node-type-' + nodeType : ''); context.out('<li class="' + nodeClass + '" data-key="' + leftKey + '">' + '<div class="jsondiffpatch-property-name">' + leftKey + '</div>'); }; HtmlFormatter.prototype.nodeEnd = function(context) { context.out('</li>'); }; /* jshint camelcase: false */ HtmlFormatter.prototype.format_unchanged = function(context, delta, left) { if (typeof left === 'undefined') { return; } context.out('<div class="jsondiffpatch-value">'); this.formatValue(context, left); context.out('</div>'); }; HtmlFormatter.prototype.format_movedestination = function(context, delta, left) { if (typeof left === 'undefined') { return; } context.out('<div class="jsondiffpatch-value">'); this.formatValue(context, left); context.out('</div>'); }; HtmlFormatter.prototype.format_node = function(context, delta, left) { // recurse var nodeType = (delta._t === 'a') ? 'array' : 'object'; context.out('<ul class="jsondiffpatch-node jsondiffpatch-node-type-' + nodeType + '">'); this.formatDeltaChildren(context, delta, left); context.out('</ul>'); }; HtmlFormatter.prototype.format_added = function(context, delta) { context.out('<div class="jsondiffpatch-value">'); this.formatValue(context, delta[0]); context.out('</div>'); }; HtmlFormatter.prototype.format_modified = function(context, delta) { context.out('<div class="jsondiffpatch-value jsondiffpatch-left-value">'); this.formatValue(context, delta[0]); context.out('</div>' + '<div class="jsondiffpatch-value jsondiffpatch-right-value">'); this.formatValue(context, delta[1]); context.out('</div>'); }; HtmlFormatter.prototype.format_deleted = function(context, delta) { context.out('<div class="jsondiffpatch-value">'); this.formatValue(context, delta[0]); context.out('</div>'); }; HtmlFormatter.prototype.format_moved = function(context, delta) { context.out('<div class="jsondiffpatch-value">'); this.formatValue(context, delta[0]); context.out('</div><div class="jsondiffpatch-moved-destination">' + delta[1] + '</div>'); // draw an SVG arrow from here to move destination context.out( /*jshint multistr: true */ '<div class="jsondiffpatch-arrow" style="position: relative; left: -34px;">\ <svg width="30" height="60" style="position: absolute; display: none;">\ <defs>\ <marker id="markerArrow" markerWidth="8" markerHeight="8" refx="2" refy="4"\ orient="auto" markerUnits="userSpaceOnUse">\ <path d="M1,1 L1,7 L7,4 L1,1" style="fill: #339;" />\ </marker>\ </defs>\ <path d="M30,0 Q-10,25 26,50" style="stroke: #88f; stroke-width: 2px; fill: none;\ stroke-opacity: 0.5; marker-end: url(#markerArrow);"></path>\ </svg>\ </div>'); context.hasArrows = true; }; HtmlFormatter.prototype.format_textdiff = function(context, delta) { context.out('<div class="jsondiffpatch-value">'); this.formatTextDiffString(context, delta[0]); context.out('</div>'); }; /* jshint camelcase: true */ var showUnchanged = function(show, node, delay) { var el = node || document.body; var prefix = 'jsondiffpatch-unchanged-'; var classes = { showing: prefix + 'showing', hiding: prefix + 'hiding', visible: prefix + 'visible', hidden: prefix + 'hidden', }; var list = el.classList; if (!list) { return; } if (!delay) { list.remove(classes.showing); list.remove(classes.hiding); list.remove(classes.visible); list.remove(classes.hidden); if (show === false) { list.add(classes.hidden); } return; } if (show === false) { list.remove(classes.showing); list.add(classes.visible); setTimeout(function() { list.add(classes.hiding); }, 10); } else { list.remove(classes.hiding); list.add(classes.showing); list.remove(classes.hidden); } var intervalId = setInterval(function() { adjustArrows(el); }, 100); setTimeout(function() { list.remove(classes.showing); list.remove(classes.hiding); if (show === false) { list.add(classes.hidden); list.remove(classes.visible); } else { list.add(classes.visible); list.remove(classes.hidden); } setTimeout(function() { list.remove(classes.visible); clearInterval(intervalId); }, delay + 400); }, delay); }; var hideUnchanged = function(node, delay) { return showUnchanged(false, node, delay); }; exports.HtmlFormatter = HtmlFormatter; exports.showUnchanged = showUnchanged; exports.hideUnchanged = hideUnchanged; var defaultInstance; exports.format = function(delta, left) { if (!defaultInstance) { defaultInstance = new HtmlFormatter(); } return defaultInstance.format(delta, left); };