/*\ title: $:/core/modules/widgets/diff-text.js type: application/javascript module-type: widget Widget to display a diff between two texts \*/ (function(){ /*jslint node: true, browser: true */ /*global $tw: false */ "use strict"; var Widget = require("$:/core/modules/widgets/widget.js").widget, dmp = require("$:/core/modules/utils/diff-match-patch/diff_match_patch.js"); var DiffTextWidget = function(parseTreeNode,options) { this.initialise(parseTreeNode,options); }; /* Inherit from the base widget class */ DiffTextWidget.prototype = new Widget(); DiffTextWidget.prototype.invisibleCharacters = { "\n": "↩︎\n", "\r": "⇠", "\t": "⇥\t" }; /* Render this widget into the DOM */ DiffTextWidget.prototype.render = function(parent,nextSibling) { this.parentDomNode = parent; this.computeAttributes(); this.execute(); // Create the diff var dmpObject = new dmp.diff_match_patch(), diffs = dmpObject.diff_main(this.getAttribute("source",""),this.getAttribute("dest","")); // Apply required cleanup switch(this.getAttribute("cleanup","semantic")) { case "none": // No cleanup break; case "efficiency": dmpObject.diff_cleanupEfficiency(diffs); break; default: // case "semantic" dmpObject.diff_cleanupSemantic(diffs); break; } // Create the elements var domContainer = this.document.createElement("div"), domDiff = this.createDiffDom(diffs); parent.insertBefore(domContainer,nextSibling); // Set variables this.setVariable("diff-count",diffs.reduce(function(acc,diff) { if(diff[0] !== dmp.DIFF_EQUAL) { acc++; } return acc; },0).toString()); // Render child widgets this.renderChildren(domContainer,null); // Render the diff domContainer.appendChild(domDiff); // Save our container this.domNodes.push(domContainer); }; /* Create DOM elements representing a list of diffs */ DiffTextWidget.prototype.createDiffDom = function(diffs) { var self = this; // Create the element and assign the attributes var domPre = this.document.createElement("pre"), domCode = this.document.createElement("code"); $tw.utils.each(diffs,function(diff) { var tag = diff[0] === dmp.DIFF_INSERT ? "ins" : (diff[0] === dmp.DIFF_DELETE ? "del" : "span"), className = diff[0] === dmp.DIFF_INSERT ? "tc-diff-insert" : (diff[0] === dmp.DIFF_DELETE ? "tc-diff-delete" : "tc-diff-equal"), dom = self.document.createElement(tag), text = diff[1], currPos = 0, re = /([\x00-\x1F])/mg, match = re.exec(text), span, printable; dom.className = className; while(match) { if(currPos < match.index) { dom.appendChild(self.document.createTextNode(text.slice(currPos,match.index))); } span = self.document.createElement("span"); span.className = "tc-diff-invisible"; printable = self.invisibleCharacters[match[0]] || ("[0x" + match[0].charCodeAt(0).toString(16) + "]"); span.appendChild(self.document.createTextNode(printable)); dom.appendChild(span); currPos = match.index + match[0].length; match = re.exec(text); } if(currPos < text.length) { dom.appendChild(self.document.createTextNode(text.slice(currPos))); } domCode.appendChild(dom); }); domPre.appendChild(domCode); return domPre; }; /* Compute the internal state of the widget */ DiffTextWidget.prototype.execute = function() { // Make child widgets var parseTreeNodes; if(this.parseTreeNode && this.parseTreeNode.children && this.parseTreeNode.children.length > 0) { parseTreeNodes = this.parseTreeNode.children; } else { parseTreeNodes = [{ type: "transclude", attributes: { tiddler: {type: "string", value: "$:/language/Diffs/CountMessage"} } }]; } this.makeChildWidgets(parseTreeNodes); }; /* Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering */ DiffTextWidget.prototype.refresh = function(changedTiddlers) { var changedAttributes = this.computeAttributes(); if(changedAttributes.source || changedAttributes.dest || changedAttributes.cleanup) { this.refreshSelf(); return true; } else { return this.refreshChildren(changedTiddlers); } }; exports["diff-text"] = DiffTextWidget; })();