Uses jsdiff, ace editor and github's css to do 100% client side line diffing. You should fork the code and build a UI for word and character diffing (it's supported by the jsdiff library, I just haven't hooked it up yet).
Created
March 29, 2011 21:36
-
-
Save max-mapper/893372 to your computer and use it in GitHub Desktop.
js diffing UI
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js" type="text/javascript"></script> | |
<script type="text/javascript"> | |
/* | |
Shameless port of a shameless port | |
@defunkt => @janl => @aq | |
See http://github.com/defunkt/mustache for more info. | |
*/(function(a){var b=function(){var a=function(){};return a.prototype={otag:"{{",ctag:"}}",pragmas:{},buffer:[],pragmas_implemented:{"IMPLICIT-ITERATOR":!0},context:{},render:function(a,b,c,d){d||(this.context=b,this.buffer=[]);if(!this.includes("",a)){if(d)return a;this.send(a);return}a=this.render_pragmas(a);var e=this.render_section(a,b,c);if(d)return this.render_tags(e,b,c,d);this.render_tags(e,b,c,d)},send:function(a){a!=""&&this.buffer.push(a)},render_pragmas:function(a){if(!this.includes("%",a))return a;var b=this,c=new RegExp(this.otag+"%([\\w-]+) ?([\\w]+=[\\w]+)?"+this.ctag);return a.replace(c,function(a,c,d){if(!b.pragmas_implemented[c])throw{message:"This implementation of mustache doesn't understand the '"+c+"' pragma"};b.pragmas[c]={};if(d){var e=d.split("=");b.pragmas[c][e[0]]=e[1]}return""})},render_partial:function(a,b,c){a=this.trim(a);if(!c||c[a]===undefined)throw{message:"unknown_partial '"+a+"'"};return typeof b[a]!="object"?this.render(c[a],b,c,!0):this.render(c[a],b[a],c,!0)},render_section:function(a,b,c){if(!this.includes("#",a)&&!this.includes("^",a))return a;var d=this,e=new RegExp(this.otag+"(\\^|\\#)\\s*(.+)\\s*"+this.ctag+"\n*([\\s\\S]+?)"+this.otag+"\\/\\s*\\2\\s*"+this.ctag+"\\s*","mg");return a.replace(e,function(a,e,f,g){var h=d.find(f,b);if(e=="^")return!h||d.is_array(h)&&h.length===0?d.render(g,b,c,!0):"";if(e=="#")return d.is_array(h)?d.map(h,function(a){return d.render(g,d.create_context(a),c,!0)}).join(""):d.is_object(h)?d.render(g,d.create_context(h),c,!0):typeof h=="function"?h.call(b,g,function(a){return d.render(a,b,c,!0)}):h?d.render(g,b,c,!0):""})},render_tags:function(a,b,c,d){var e=this,f=function(){return new RegExp(e.otag+"(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?"+e.ctag+"+","g")},g=f(),h=function(a,d,h){switch(d){case"!":return"";case"=":return e.set_delimiters(h),g=f(),"";case">":return e.render_partial(h,b,c);case"{":return e.find(h,b);default:return e.escape(e.find(h,b))}},i=a.split("\n");for(var j=0;j<i.length;j++)i[j]=i[j].replace(g,h,this),d||this.send(i[j]);if(d)return i.join("\n")},set_delimiters:function(a){var b=a.split(" ");this.otag=this.escape_regex(b[0]),this.ctag=this.escape_regex(b[1])},escape_regex:function(a){if(!arguments.callee.sRE){var b=["/",".","*","+","?","|","(",")","[","]","{","}","\\"];arguments.callee.sRE=new RegExp("(\\"+b.join("|\\")+")","g")}return a.replace(arguments.callee.sRE,"\\$1")},find:function(a,b){function c(a){return a===!1||a===0||a}a=this.trim(a);var d;return c(b[a])?d=b[a]:c(this.context[a])&&(d=this.context[a]),typeof d=="function"?d.apply(b):d!==undefined?d:""},includes:function(a,b){return b.indexOf(this.otag+a)!=-1},escape:function(a){return a=String(a===null?"":a),a.replace(/&(?!\w+;)|["<>\\]/g,function(a){switch(a){case"&":return"&";case"\\":return"\\\\";case'"':return'"';case"<":return"<";case">":return">";default:return a}})},create_context:function(a){if(this.is_object(a))return a;var b=".";this.pragmas["IMPLICIT-ITERATOR"]&&(b=this.pragmas["IMPLICIT-ITERATOR"].iterator);var c={};return c[b]=a,c},is_object:function(a){return a&&typeof a=="object"},is_array:function(a){return Object.prototype.toString.call(a)==="[object Array]"},trim:function(a){return a.replace(/^\s*|\s*$/g,"")},map:function(a,b){if(typeof a.map=="function")return a.map(b);var c=[],d=a.length;for(var e=0;e<d;e++)c.push(b(a[e]));return c}},{name:"mustache.js",version:"0.3.1-dev",to_html:function(b,c,d,e){var f=new a;e&&(f.send=e),f.render(b,c,d);if(!e)return f.buffer.join("\n")},escape:function(b){return(new a).escape(b)}}}();a.mustache=function(a,c,d){return b.to_html(a,c,d)},a.mustache.escape=function(a){return b.escape(a)}})(jQuery); | |
</script> | |
<script src="https://github.com/ajaxorg/ace/raw/master/build/src/ace.js" type="text/javascript" charset="utf-8"></script> | |
<script src="https://github.com/ajaxorg/ace/raw/master/build/src/mode-javascript.js" type="text/javascript" charset="utf-8"></script> | |
<script src="https://github.com/kpdecker/jsdiff/raw/master/diff.js" type="text/javascript"></script> | |
<script type="text/diff" id="old"></script> | |
<script type="text/diff" id="new"></script> | |
<script type="text/mustache" id="diffTemplate"> | |
<tr data-position="8"> | |
<td id="L0L241" class="line_numbers linkable-line-number"></td> | |
<td id="L0R241" class="line_numbers linkable-line-number"></td> | |
<td width="100%"> | |
<b class="add-bubble" remote=""></b> | |
<pre><div class="{{#added}}gi{{/added}}{{#removed}}gd{{/removed}}">{{value}}</div></pre> | |
</td> | |
</tr> | |
</script> | |
<script type="text/javascript"> | |
$(function() { | |
function renderDiff() { | |
$('#diffLines').html(""); | |
var oldFile = $( '#old' ).text(), | |
newFile = $( '#new' ).text(), | |
diffTemplate = $( '#diffTemplate' ).text(); | |
$( JsDiff.diffLines( oldFile, newFile ) ).map( function( i, diff ) { | |
$('#diffLines').append( $.mustache(diffTemplate, diff) ); | |
} ) | |
} | |
var editor; | |
$('#old').text($('#editor').html()); | |
function jsonParty() { | |
$('#new').text(editor.getSession().toString()); | |
renderDiff(); | |
} | |
editor = ace.edit("editor"); | |
jsonParty(); | |
$('textarea').keydown(function() { | |
window.setTimeout(jsonParty, 100, true); | |
}); | |
var JavaScriptMode = require("ace/mode/javascript").Mode; | |
editor.getSession().setMode(new JavaScriptMode()); | |
}) | |
</script> | |
<style type="text/css" media="screen"> | |
body { | |
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; | |
color: #666; | |
overflow: hidden; | |
} | |
h1 { | |
margin-top: 0; | |
} | |
ul { | |
list-style: none; | |
padding: 0; | |
margin: 0; | |
} | |
li { | |
margin-bottom: 20px; | |
clear: both; | |
} | |
label { | |
font-size: 10px; | |
font-weight: bold; | |
text-transform: uppercase; | |
display: block; | |
margin-bottom: 3px; | |
clear: both; | |
} | |
#editor { | |
width: 100%; | |
height: 300px; | |
margin: 0; | |
position: absolute; | |
bottom: 0; | |
left: 0; | |
right: 0; | |
} | |
.formOutput { | |
float: left; | |
clear: both; | |
} | |
.htmlOutput { | |
padding: 25px 0px 0px 0px; | |
font-size: 12px; | |
clear: both; | |
} | |
</style> | |
<link href="https://a248.e.akamai.net/assets.github.com/stylesheets/bundles/github-25d3866eecbdad76dacc91ca80d2434cc8f22c04.css" media="screen" rel="stylesheet" type="text/css" /> | |
<link href="https://a248.e.akamai.net/assets.github.com/stylesheets/bundles/github2-0362aac7f9b6a26a7d1f5bcb8c3d429a39498c73.css" media="screen" rel="stylesheet" type="text/css" /> | |
</head> | |
<body> | |
<div id="files" class="diff-view commentable"> | |
<div id="diff-0" class="file"> | |
<div class="data highlight"> | |
<table cellpadding="0" cellspacing="0" width="100%"> | |
<tbody id="diffLines"> | |
</tbody> | |
</table> | |
</div> | |
<div class="file-comments-place-holder" data-path="_attachments/pages/monocles.html"></div> | |
</div> | |
</div> | |
<pre id="editor">{ | |
"edit" : "this text", | |
"remove" : "and add lines", | |
"to see" : "diffs!" | |
} | |
</pre> | |
</body> | |
</html> |
update to rawgithub.com links and ace -> acebuilds
looks like css is just github.com stylesheet... so this will likely break again.
but here's a working version. if it breaks just pull the css from github.com head.
LIVE EXAMPLE: http://runnable.com/UtdEd7G1CSk6AAAb/diff-files-in-javascript-with-jsdiff-and-ace
<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">
/*
Shameless port of a shameless port
@defunkt => @janl => @aq
See http://github.com/defunkt/mustache for more info.
*/(function(a){var b=function(){var a=function(){};return a.prototype={otag:"{{",ctag:"}}",pragmas:{},buffer:[],pragmas_implemented:{"IMPLICIT-ITERATOR":!0},context:{},render:function(a,b,c,d){d||(this.context=b,this.buffer=[]);if(!this.includes("",a)){if(d)return a;this.send(a);return}a=this.render_pragmas(a);var e=this.render_section(a,b,c);if(d)return this.render_tags(e,b,c,d);this.render_tags(e,b,c,d)},send:function(a){a!=""&&this.buffer.push(a)},render_pragmas:function(a){if(!this.includes("%",a))return a;var b=this,c=new RegExp(this.otag+"%([\\w-]+) ?([\\w]+=[\\w]+)?"+this.ctag);return a.replace(c,function(a,c,d){if(!b.pragmas_implemented[c])throw{message:"This implementation of mustache doesn't understand the '"+c+"' pragma"};b.pragmas[c]={};if(d){var e=d.split("=");b.pragmas[c][e[0]]=e[1]}return""})},render_partial:function(a,b,c){a=this.trim(a);if(!c||c[a]===undefined)throw{message:"unknown_partial '"+a+"'"};return typeof b[a]!="object"?this.render(c[a],b,c,!0):this.render(c[a],b[a],c,!0)},render_section:function(a,b,c){if(!this.includes("#",a)&&!this.includes("^",a))return a;var d=this,e=new RegExp(this.otag+"(\\^|\\#)\\s*(.+)\\s*"+this.ctag+"\n*([\\s\\S]+?)"+this.otag+"\\/\\s*\\2\\s*"+this.ctag+"\\s*","mg");return a.replace(e,function(a,e,f,g){var h=d.find(f,b);if(e=="^")return!h||d.is_array(h)&&h.length===0?d.render(g,b,c,!0):"";if(e=="#")return d.is_array(h)?d.map(h,function(a){return d.render(g,d.create_context(a),c,!0)}).join(""):D.is_object(h)?d.render(g,d.create_context(h),c,!0):typeof h=="function"?h.call(b,g,function(a){return d.render(a,b,c,!0)}):h?d.render(g,b,c,!0):""})},render_tags:function(a,b,c,d){var e=this,f=function(){return new RegExp(e.otag+"(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?"+e.ctag+"+","g")},g=f(),h=function(a,d,h){switch(d){case"!":return"";case"=":return e.set_delimiters(h),g=f(),"";case">":return e.render_partial(h,b,c);case"{":return e.find(h,b);default:return e.escape(e.find(h,b))}},i=a.split("\n");for(var j=0;j<i.length;j++)i[j]=i[j].replace(g,h,this),d||this.send(i[j]);if(d)return i.join("\n")},set_delimiters:function(a){var b=a.split(" ");this.otag=this.escape_regex(b[0]),this.ctag=this.escape_regex(b[1])},escape_regex:function(a){if(!arguments.callee.sRE){var b=["/",".","*","+","?","|","(",")","[","]","{","}","\\"];arguments.callee.sRE=new RegExp("(\\"+b.join("|\\")+")","g")}return a.replace(arguments.callee.sRE,"\\$1")},find:function(a,b){function c(a){return a===!1||a===0||a}a=this.trim(a);var d;return c(b[a])?d=b[a]:c(this.context[a])&&(d=this.context[a]),typeof d=="function"?d.apply(b):D!==undefined?d:""},includes:function(a,b){return b.indexOf(this.otag+a)!=-1},escape:function(a){return a=String(a===null?"":a),a.replace(/&(?!\w+;)|["<>\\]/g,function(a){switch(a){case"&":return"&";case"\\":return"\\\\";case'"':return'"';case"<":return"<";case">":return">";default:return a}})},create_context:function(a){if(this.is_object(a))return a;var b=".";this.pragmas["IMPLICIT-ITERATOR"]&&(b=this.pragmas["IMPLICIT-ITERATOR"].iterator);var c={};return c[b]=a,c},is_object:function(a){return a&&typeof a=="object"},is_array:function(a){return Object.prototype.toString.call(a)==="[object Array]"},trim:function(a){return a.replace(/^\s*|\s*$/g,"")},map:function(a,b){if(typeof a.map=="function")return a.map(b);var c=[],d=a.length;for(var e=0;e<d;e++)c.push(b(a[e]));return c}},{name:"mustache.js",version:"0.3.1-dev",to_html:function(b,c,d,e){var f=new a;e&&(f.send=e),f.render(b,c,d);if(!e)return f.buffer.join("\n")},escape:function(b){return(new a).escape(b)}}}();a.mustache=function(a,c,d){return b.to_html(a,c,d)},a.mustache.escape=function(a){return b.escape(a)}})(jQuery);
</script>
<script src="http://rawgithub.com/ajaxorg/ace-builds/master/src/ace.js" type="text/javascript" charset="utf-8"></script>
<script src="http://rawgithub.com/ajaxorg/ace-builds/master/src/mode-javascript.js" type="text/javascript" charset="utf-8"></script>
<script src="http://rawgithub.com/kpdecker/jsdiff/master/diff.js" type="text/javascript"></script>
<script type="text/diff" id="old"></script>
<script type="text/diff" id="new"></script>
<script type="text/mustache" id="diffTemplate">
<tr data-position="8">
<td id="L0L241" class="line_numbers linkable-line-number"></td>
<td id="L0R241" class="line_numbers linkable-line-number"></td>
<td width="100%">
<b class="add-bubble" remote=""></b>
<pre><div class="{{#added}}gi{{/added}}{{#removed}}gd{{/removed}}">{{value}}</div></pre>
</td>
</tr>
</script>
<script type="text/javascript">
$(function() {
function renderDiff() {
$('#diffLines').html("");
var oldFile = $( '#old' ).text(),
newFile = $( '#new' ).text(),
diffTemplate = $( '#diffTemplate' ).text();
$( JsDiff.diffLines( oldFile, newFile ) ).map( function( i, diff ) {
$('#diffLines').append( $.mustache(diffTemplate, diff) );
} )
}
var editor;
$('#old').text($('#editor').html());
function jsonParty() {
$('#new').text(editor.getSession().toString());
renderDiff();
}
editor = ace.edit("editor");
jsonParty();
$('textarea').keydown(function() {
window.setTimeout(jsonParty, 100, true);
});
var JavaScriptMode = require("ace/mode/javascript").Mode;
editor.getSession().setMode(new JavaScriptMode());
})
</script>
<style type="text/css" media="screen">
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #666;
overflow: hidden;
}
h1 {
margin-top: 0;
}
ul {
list-style: none;
padding: 0;
margin: 0;
}
li {
margin-bottom: 20px;
clear: both;
}
label {
font-size: 10px;
font-weight: bold;
text-transform: uppercase;
display: block;
margin-bottom: 3px;
clear: both;
}
#editor {
width: 100%;
height: 300px;
margin: 0;
position: absolute;
bottom: 0;
left: 0;
right: 0;
}
.formOutput {
float: left;
clear: both;
}
.htmlOutput {
padding: 25px 0px 0px 0px;
font-size: 12px;
clear: both;
}
</style>
<link href="https://github.global.ssl.fastly.net/assets/github2-29e8f0829ba588f8a69c82f1a068a1e6a46701fe.css" media="screen" rel="stylesheet" type="text/css" />
<link href="https://github.global.ssl.fastly.net/assets/github-ea281967a8390ec8a707e53c711e8ad3d8033bd8.css" media="screen" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="files" class="diff-view commentable">
<div id="diff-0" class="file">
<div class="data highlight">
<table cellpadding="0" cellspacing="0" width="100%">
<tbody id="diffLines">
</tbody>
</table>
</div>
<div class="file-comments-place-holder" data-path="_attachments/pages/monocles.html"></div>
</div>
</div>
<pre id="editor">{
"edit" : "this text",
"remove" : "and add lines",
"to see" : "diffs!"
}
</pre>
</body>
</html>
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Tried to see at work but had a problem. This is log.
I think jsdiff a good library, but I have one problem with it, maybe you could help me.