TiddlyWiki5/core/modules/widgets/diff-text.js

150 lines
4.1 KiB
JavaScript

/*\
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) {
options.hasDom = true;
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;
})();