├── docs ├── assets ├── CNAME ├── robots.txt ├── favicon.ico ├── img │ ├── snapshot-1.png │ ├── snapshot-2.png │ └── snapshot-3.png ├── sitemap.xml ├── main.min.css └── demo.html ├── website ├── CNAME ├── robots.txt ├── favicon.ico ├── img │ ├── snapshot-1.png │ ├── snapshot-2.png │ └── snapshot-3.png ├── templates │ ├── pages │ │ ├── index │ │ │ └── index-scripts.partial.mustache │ │ └── demo │ │ │ ├── demo-assets.partial.mustache │ │ │ ├── demo-scripts.partial.mustache │ │ │ ├── demo.partial.mustache │ │ │ └── demo.js │ └── template.mustache ├── sitemap.xml └── main.css ├── src ├── templates │ ├── generic-wrapper.mustache │ ├── tag-file-added.mustache │ ├── tag-file-changed.mustache │ ├── tag-file-deleted.mustache │ ├── tag-file-renamed.mustache │ ├── line-by-line-numbers.mustache │ ├── generic-file-path.mustache │ ├── generic-empty-diff.mustache │ ├── generic-column-line-number.mustache │ ├── icon-file-deleted.mustache │ ├── icon-file.mustache │ ├── icon-file-added.mustache │ ├── icon-file-renamed.mustache │ ├── icon-file-changed.mustache │ ├── file-summary-wrapper.mustache │ ├── file-summary-line.mustache │ ├── generic-line.mustache │ ├── line-by-line-file-diff.mustache │ ├── side-by-side-file-diff.mustache │ └── diff2html-templates.js ├── utils.js ├── html-printer.js ├── file-list-printer.js ├── hoganjs-utils.js ├── diff2html.js ├── ui │ ├── js │ │ ├── highlight.js-internals.js │ │ └── diff2html-ui.js │ └── css │ │ └── diff2html.css ├── rematch.js ├── printer-utils.js ├── line-by-line-printer.js └── side-by-side-printer.js ├── .eslintignore ├── .gitignore ├── .editorconfig ├── scripts ├── update-bower-version.sh ├── release-website.sh ├── release.sh ├── release-website.js └── hulk.js ├── typescript ├── diff2html-tests.ts └── diff2html.d.ts ├── CREDITS.md ├── test ├── utils-tests.js ├── hogan-cache-tests.js ├── printer-utils-tests.js └── file-list-printer-tests.js ├── .github └── ISSUE_TEMPLATE.md ├── bower.json ├── circle.yml ├── LICENSE.md ├── package.json ├── CONTRIBUTING.md ├── dist ├── diff2html.min.css ├── diff2html-ui.min.js └── diff2html-ui.js ├── .eslintrc.json └── README.md /docs/assets: -------------------------------------------------------------------------------- 1 | ../dist -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | diff2html.xyz -------------------------------------------------------------------------------- /docs/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * -------------------------------------------------------------------------------- /website/CNAME: -------------------------------------------------------------------------------- 1 | diff2html.xyz -------------------------------------------------------------------------------- /website/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hirokith/diff2html/master/docs/favicon.ico -------------------------------------------------------------------------------- /website/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hirokith/diff2html/master/website/favicon.ico -------------------------------------------------------------------------------- /docs/img/snapshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hirokith/diff2html/master/docs/img/snapshot-1.png -------------------------------------------------------------------------------- /docs/img/snapshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hirokith/diff2html/master/docs/img/snapshot-2.png -------------------------------------------------------------------------------- /docs/img/snapshot-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hirokith/diff2html/master/docs/img/snapshot-3.png -------------------------------------------------------------------------------- /src/templates/generic-wrapper.mustache: -------------------------------------------------------------------------------- 1 |
{{myName}}
'); 41 | 42 | var config = {templates: {'generic-empty-diff': emptyDiffTemplate}}; 43 | var hoganUtils = new (require('../src/hoganjs-utils.js').HoganJsUtils)(config); 44 | var result = hoganUtils.render('generic', 'empty-diff', {myName: 'Rodrigo Fernandes'}); 45 | assert.equal('Rodrigo Fernandes
', result); 46 | }); 47 | 48 | it('should allow templates to be overridden with uncompiled templates', function() { 49 | var emptyDiffTemplate = '{{myName}}
'; 50 | 51 | var config = {rawTemplates: {'generic-empty-diff': emptyDiffTemplate}}; 52 | var hoganUtils = new (require('../src/hoganjs-utils.js').HoganJsUtils)(config); 53 | var result = hoganUtils.render('generic', 'empty-diff', {myName: 'Rodrigo Fernandes'}); 54 | assert.equal('Rodrigo Fernandes
', result); 55 | }); 56 | 57 | it('should allow templates to be overridden giving priority to compiled templates', function() { 58 | var emptyDiffTemplate = HoganJsUtils.compile('{{myName}}
'); 59 | var emptyDiffTemplateUncompiled = 'Not used!
'; 60 | 61 | var config = { 62 | templates: {'generic-empty-diff': emptyDiffTemplate}, 63 | rawTemplates: {'generic-empty-diff': emptyDiffTemplateUncompiled} 64 | }; 65 | var hoganUtils = new (require('../src/hoganjs-utils.js').HoganJsUtils)(config); 66 | var result = hoganUtils.render('generic', 'empty-diff', {myName: 'Rodrigo Fernandes'}); 67 | assert.equal('Rodrigo Fernandes
', result); 68 | }); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /src/diff2html.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Diff to HTML (diff2html.js) 4 | * Author: rtfpessoa 5 | * 6 | */ 7 | 8 | (function() { 9 | var diffParser = require('./diff-parser.js').DiffParser; 10 | var htmlPrinter = require('./html-printer.js').HtmlPrinter; 11 | 12 | function Diff2Html() { 13 | } 14 | 15 | /* 16 | * Line diff type configuration 17 | var config = { 18 | 'wordByWord': true, // (default) 19 | // OR 20 | 'charByChar': true 21 | }; 22 | */ 23 | 24 | /* 25 | * Generates json object from string diff input 26 | */ 27 | Diff2Html.prototype.getJsonFromDiff = function(diffInput, config) { 28 | var configOrEmpty = config || {}; 29 | return diffParser.generateDiffJson(diffInput, configOrEmpty); 30 | }; 31 | 32 | /* 33 | * Generates the html diff. The config parameter configures the output/input formats and other options 34 | */ 35 | Diff2Html.prototype.getPrettyHtml = function(diffInput, config) { 36 | var configOrEmpty = config || {}; 37 | 38 | var diffJson = diffInput; 39 | if (!configOrEmpty.inputFormat || configOrEmpty.inputFormat === 'diff') { 40 | diffJson = diffParser.generateDiffJson(diffInput, configOrEmpty); 41 | } 42 | 43 | var fileList = ''; 44 | if (configOrEmpty.showFiles === true) { 45 | fileList = htmlPrinter.generateFileListSummary(diffJson, configOrEmpty); 46 | } 47 | 48 | var diffOutput = ''; 49 | if (configOrEmpty.outputFormat === 'side-by-side') { 50 | diffOutput = htmlPrinter.generateSideBySideJsonHtml(diffJson, configOrEmpty); 51 | } else { 52 | diffOutput = htmlPrinter.generateLineByLineJsonHtml(diffJson, configOrEmpty); 53 | } 54 | 55 | return fileList + diffOutput; 56 | }; 57 | 58 | /* 59 | * Deprecated methods - The following methods exist only to maintain compatibility with previous versions 60 | */ 61 | 62 | /* 63 | * Generates pretty html from string diff input 64 | */ 65 | Diff2Html.prototype.getPrettyHtmlFromDiff = function(diffInput, config) { 66 | var configOrEmpty = config || {}; 67 | configOrEmpty.inputFormat = 'diff'; 68 | configOrEmpty.outputFormat = 'line-by-line'; 69 | return this.getPrettyHtml(diffInput, configOrEmpty); 70 | }; 71 | 72 | /* 73 | * Generates pretty html from a json object 74 | */ 75 | Diff2Html.prototype.getPrettyHtmlFromJson = function(diffJson, config) { 76 | var configOrEmpty = config || {}; 77 | configOrEmpty.inputFormat = 'json'; 78 | configOrEmpty.outputFormat = 'line-by-line'; 79 | return this.getPrettyHtml(diffJson, configOrEmpty); 80 | }; 81 | 82 | /* 83 | * Generates pretty side by side html from string diff input 84 | */ 85 | Diff2Html.prototype.getPrettySideBySideHtmlFromDiff = function(diffInput, config) { 86 | var configOrEmpty = config || {}; 87 | configOrEmpty.inputFormat = 'diff'; 88 | configOrEmpty.outputFormat = 'side-by-side'; 89 | return this.getPrettyHtml(diffInput, configOrEmpty); 90 | }; 91 | 92 | /* 93 | * Generates pretty side by side html from a json object 94 | */ 95 | Diff2Html.prototype.getPrettySideBySideHtmlFromJson = function(diffJson, config) { 96 | var configOrEmpty = config || {}; 97 | configOrEmpty.inputFormat = 'json'; 98 | configOrEmpty.outputFormat = 'side-by-side'; 99 | return this.getPrettyHtml(diffJson, configOrEmpty); 100 | }; 101 | 102 | var diffObject = new Diff2Html(); 103 | module.exports.Diff2Html = diffObject; 104 | 105 | // Expose diff2html in the browser 106 | global.Diff2Html = diffObject; 107 | })(); 108 | -------------------------------------------------------------------------------- /src/ui/js/highlight.js-internals.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * highlight.js 4 | * Author: isagalaev 5 | * 6 | */ 7 | 8 | (function() { 9 | function HighlightJS() { 10 | } 11 | 12 | /* 13 | * Copied from Highlight.js Private API 14 | * Will be removed when this part of the API is exposed 15 | */ 16 | 17 | /* Utility vars */ 18 | 19 | var ArrayProto = []; 20 | 21 | /* Utility functions */ 22 | 23 | function escape(value) { 24 | return value.replace(/&/gm, '&').replace(//gm, '>'); 25 | } 26 | 27 | function tag(node) { 28 | return node.nodeName.toLowerCase(); 29 | } 30 | 31 | /* Stream merging */ 32 | 33 | HighlightJS.prototype.nodeStream = function(node) { 34 | var result = []; 35 | (function _nodeStream(node, offset) { 36 | for (var child = node.firstChild; child; child = child.nextSibling) { 37 | if (child.nodeType === 3) { 38 | offset += child.nodeValue.length; 39 | } else if (child.nodeType === 1) { 40 | result.push({ 41 | event: 'start', 42 | offset: offset, 43 | node: child 44 | }); 45 | offset = _nodeStream(child, offset); 46 | // Prevent void elements from having an end tag that would actually 47 | // double them in the output. There are more void elements in HTML 48 | // but we list only those realistically expected in code display. 49 | if (!tag(child).match(/br|hr|img|input/)) { 50 | result.push({ 51 | event: 'stop', 52 | offset: offset, 53 | node: child 54 | }); 55 | } 56 | } 57 | } 58 | return offset; 59 | })(node, 0); 60 | return result; 61 | }; 62 | 63 | HighlightJS.prototype.mergeStreams = function(original, highlighted, value) { 64 | var processed = 0; 65 | var result = ''; 66 | var nodeStack = []; 67 | 68 | function selectStream() { 69 | if (!original.length || !highlighted.length) { 70 | return original.length ? original : highlighted; 71 | } 72 | if (original[0].offset !== highlighted[0].offset) { 73 | return (original[0].offset < highlighted[0].offset) ? original : highlighted; 74 | } 75 | 76 | /* 77 | To avoid starting the stream just before it should stop the order is 78 | ensured that original always starts first and closes last: 79 | if (event1 == 'start' && event2 == 'start') 80 | return original; 81 | if (event1 == 'start' && event2 == 'stop') 82 | return highlighted; 83 | if (event1 == 'stop' && event2 == 'start') 84 | return original; 85 | if (event1 == 'stop' && event2 == 'stop') 86 | return highlighted; 87 | ... which is collapsed to: 88 | */ 89 | return highlighted[0].event === 'start' ? original : highlighted; 90 | } 91 | 92 | function open(node) { 93 | function attr_str(a) { 94 | return ' ' + a.nodeName + '="' + escape(a.value) + '"'; 95 | } 96 | 97 | result += '<' + tag(node) + ArrayProto.map.call(node.attributes, attr_str).join('') + '>'; 98 | } 99 | 100 | function close(node) { 101 | result += '' + tag(node) + '>'; 102 | } 103 | 104 | function render(event) { 105 | (event.event === 'start' ? open : close)(event.node); 106 | } 107 | 108 | while (original.length || highlighted.length) { 109 | var stream = selectStream(); 110 | result += escape(value.substring(processed, stream[0].offset)); 111 | processed = stream[0].offset; 112 | if (stream === original) { 113 | /* 114 | On any opening or closing tag of the original markup we first close 115 | the entire highlighted node stack, then render the original tag along 116 | with all the following original tags at the same offset and then 117 | reopen all the tags on the highlighted stack. 118 | */ 119 | nodeStack.reverse().forEach(close); 120 | do { 121 | render(stream.splice(0, 1)[0]); 122 | stream = selectStream(); 123 | } while (stream === original && stream.length && stream[0].offset === processed); 124 | nodeStack.reverse().forEach(open); 125 | } else { 126 | if (stream[0].event === 'start') { 127 | nodeStack.push(stream[0].node); 128 | } else { 129 | nodeStack.pop(); 130 | } 131 | render(stream.splice(0, 1)[0]); 132 | } 133 | } 134 | return result + escape(value.substr(processed)); 135 | }; 136 | 137 | /* **** Highlight.js Private API **** */ 138 | 139 | module.exports.HighlightJS = new HighlightJS(); 140 | })(); 141 | -------------------------------------------------------------------------------- /src/rematch.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Rematch (rematch.js) 4 | * Matching two sequences of objects by similarity 5 | * Author: W. Illmeyer, Nexxar GmbH 6 | * 7 | */ 8 | 9 | (function() { 10 | var Rematch = {}; 11 | 12 | /* 13 | Copyright (c) 2011 Andrei Mackenzie 14 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 15 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation 16 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 17 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 18 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 20 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | function levenshtein(a, b) { 25 | if (a.length === 0) { 26 | return b.length; 27 | } 28 | if (b.length === 0) { 29 | return a.length; 30 | } 31 | 32 | var matrix = []; 33 | 34 | // Increment along the first column of each row 35 | var i; 36 | for (i = 0; i <= b.length; i++) { 37 | matrix[i] = [i]; 38 | } 39 | 40 | // Increment each column in the first row 41 | var j; 42 | for (j = 0; j <= a.length; j++) { 43 | matrix[0][j] = j; 44 | } 45 | 46 | // Fill in the rest of the matrix 47 | for (i = 1; i <= b.length; i++) { 48 | for (j = 1; j <= a.length; j++) { 49 | if (b.charAt(i - 1) === a.charAt(j - 1)) { 50 | matrix[i][j] = matrix[i - 1][j - 1]; 51 | } else { 52 | matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, // Substitution 53 | Math.min(matrix[i][j - 1] + 1, // Insertion 54 | matrix[i - 1][j] + 1)); // Deletion 55 | } 56 | } 57 | } 58 | 59 | return matrix[b.length][a.length]; 60 | } 61 | 62 | Rematch.levenshtein = levenshtein; 63 | 64 | Rematch.distance = function distance(x, y) { 65 | x = x.trim(); 66 | y = y.trim(); 67 | var lev = levenshtein(x, y); 68 | var score = lev / (x.length + y.length); 69 | 70 | return score; 71 | }; 72 | 73 | Rematch.rematch = function rematch(distanceFunction) { 74 | function findBestMatch(a, b, cache) { 75 | var bestMatchDist = Infinity; 76 | var bestMatch; 77 | for (var i = 0; i < a.length; ++i) { 78 | for (var j = 0; j < b.length; ++j) { 79 | var cacheKey = JSON.stringify([a[i], b[j]]); 80 | var md; 81 | if (cache.hasOwnProperty(cacheKey)) { 82 | md = cache[cacheKey]; 83 | } else { 84 | md = distanceFunction(a[i], b[j]); 85 | cache[cacheKey] = md; 86 | } 87 | if (md < bestMatchDist) { 88 | bestMatchDist = md; 89 | bestMatch = {indexA: i, indexB: j, score: bestMatchDist}; 90 | } 91 | } 92 | } 93 | 94 | return bestMatch; 95 | } 96 | 97 | function group(a, b, level, cache) { 98 | if (typeof (cache) === 'undefined') { 99 | cache = {}; 100 | } 101 | 102 | var bm = findBestMatch(a, b, cache); 103 | 104 | if (!level) { 105 | level = 0; 106 | } 107 | 108 | if (!bm || (a.length + b.length < 3)) { 109 | return [[a, b]]; 110 | } 111 | 112 | var a1 = a.slice(0, bm.indexA); 113 | var b1 = b.slice(0, bm.indexB); 114 | var aMatch = [a[bm.indexA]]; 115 | var bMatch = [b[bm.indexB]]; 116 | var tailA = bm.indexA + 1; 117 | var tailB = bm.indexB + 1; 118 | var a2 = a.slice(tailA); 119 | var b2 = b.slice(tailB); 120 | 121 | var group1 = group(a1, b1, level + 1, cache); 122 | var groupMatch = group(aMatch, bMatch, level + 1, cache); 123 | var group2 = group(a2, b2, level + 1, cache); 124 | var result = groupMatch; 125 | 126 | if (bm.indexA > 0 || bm.indexB > 0) { 127 | result = group1.concat(result); 128 | } 129 | 130 | if (a.length > tailA || b.length > tailB) { 131 | result = result.concat(group2); 132 | } 133 | 134 | return result; 135 | } 136 | 137 | return group; 138 | }; 139 | 140 | module.exports.Rematch = Rematch; 141 | })(); 142 | -------------------------------------------------------------------------------- /test/printer-utils-tests.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | var PrinterUtils = require('../src/printer-utils.js').PrinterUtils; 4 | 5 | describe('Utils', function() { 6 | describe('getHtmlId', function() { 7 | it('should generate file unique id', function() { 8 | var result = PrinterUtils.getHtmlId({ 9 | oldName: 'sample.js', 10 | newName: 'sample.js' 11 | }); 12 | assert.equal('d2h-960013', result); 13 | }); 14 | it('should generate file unique id for empty hashes', function() { 15 | var result = PrinterUtils.getHtmlId({ 16 | oldName: 'sample.js', 17 | newName: 'sample.js' 18 | }); 19 | assert.equal('d2h-960013', result); 20 | }); 21 | }); 22 | 23 | describe('getDiffName', function() { 24 | it('should generate the file name for a changed file', function() { 25 | var result = PrinterUtils.getDiffName({ 26 | oldName: 'sample.js', 27 | newName: 'sample.js' 28 | }); 29 | assert.equal('sample.js', result); 30 | }); 31 | it('should generate the file name for a changed file and full rename', function() { 32 | var result = PrinterUtils.getDiffName({ 33 | oldName: 'sample1.js', 34 | newName: 'sample2.js' 35 | }); 36 | assert.equal('sample1.js → sample2.js', result); 37 | }); 38 | it('should generate the file name for a changed file and prefix rename', function() { 39 | var result = PrinterUtils.getDiffName({ 40 | oldName: 'src/path/sample.js', 41 | newName: 'source/path/sample.js' 42 | }); 43 | assert.equal('{src → source}/path/sample.js', result); 44 | }); 45 | it('should generate the file name for a changed file and suffix rename', function() { 46 | var result = PrinterUtils.getDiffName({ 47 | oldName: 'src/path/sample1.js', 48 | newName: 'src/path/sample2.js' 49 | }); 50 | assert.equal('src/path/{sample1.js → sample2.js}', result); 51 | }); 52 | it('should generate the file name for a changed file and middle rename', function() { 53 | var result = PrinterUtils.getDiffName({ 54 | oldName: 'src/really/big/path/sample.js', 55 | newName: 'src/small/path/sample.js' 56 | }); 57 | assert.equal('src/{really/big → small}/path/sample.js', result); 58 | }); 59 | it('should generate the file name for a deleted file', function() { 60 | var result = PrinterUtils.getDiffName({ 61 | oldName: 'src/my/file.js', 62 | newName: '/dev/null' 63 | }); 64 | assert.equal('src/my/file.js', result); 65 | }); 66 | it('should generate the file name for a new file', function() { 67 | var result = PrinterUtils.getDiffName({ 68 | oldName: '/dev/null', 69 | newName: 'src/my/file.js' 70 | }); 71 | assert.equal('src/my/file.js', result); 72 | }); 73 | it('should generate handle undefined filename', function() { 74 | var result = PrinterUtils.getDiffName({}); 75 | assert.equal('unknown/file/path', result); 76 | }); 77 | }); 78 | 79 | describe('diffHighlight', function() { 80 | it('should highlight two lines', function() { 81 | var result = PrinterUtils.diffHighlight( 82 | '-var myVar = 2;', 83 | '+var myVariable = 3;', 84 | {matching: 'words'} 85 | ); 86 | 87 | assert.deepEqual({ 88 | first: { 89 | prefix: '-', 90 | line: 'varGitHub, Bitbucket and GitLab commit and pull request compatible
9 |Just paste the GitHub, Bitbucket or GitLab commit, pull request or merge request url 10 | or any other git or unified compatible diff and we will render a pretty html representation of it 11 | with code syntax highlight and line similarity matching for better code reviews. 12 |
13 |Code Syntax Highlight
67 |Line similarity match (similar lines are together)
68 |Line by Line and Side by Side diffs
69 |Supports any git and unified compatible diffs
70 |Easy code selection
71 |Any GitHub, Bitbucket or GitLab Commit, Pull Request or Merge Request urls.
75 |Any Git or Unified Raw Diff or Patch urls.
76 |Just add a url parameter called diff to current url using as value your Commit, Pull Request, Merge Request, Diff 80 | or Patch url.
81 |ex: https://diff2html.xyz/{{ demoUrl }} 82 |
83 |diffy.org is an amazing tool created by pbu88 88 | to share your diffs and uses diff2html under the hood.
89 |Also, diff2html cli can directly publish diffs to diffy.org
90 |I want to thank kevinsimper for this great idea, 95 | providing better diff support for existing online services. 96 |
97 | -------------------------------------------------------------------------------- /docs/main.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright Colossal 2015 3 | * Adapted by @rtfpessoa 4 | */.template-index{width:100%}.template-index-min{min-width:700px}.container{width:100%;padding:0 8%}.m-b-md{margin-bottom:23px!important}.p-t{padding-top:15px!important}@media (min-width:768px){p.m-b{height:75px;overflow-y:hidden}}.btn{display:inline-block;color:#fff;background:#26a65b;font-weight:400}.btn:hover{color:#fff;background:#5dbe5d}.btn-clipboard{position:absolute;top:0;right:0;z-index:10;display:block;padding:5px 8px;font-size:12px;color:#fff;background-color:#767676;border-radius:0 4px 0 4px;cursor:pointer}.btn-clipboard:hover{color:#000;background-color:#dcdfe4}.footer{position:relative;padding:40px 0;text-align:center;font-size:14px;border-top:1px solid #dcdfe4}.footer p{margin-bottom:5px}.footer a{color:#26a65b}.container a{color:#26a65b}.container a.btn{color:#fff}.footer-list-item{display:inline-block}.footer-list-item:not(:last-child):after{content:"\b7"}.footer>ul{padding:0}@media (min-width:768px){.footer{padding:60px 0}}@media (min-width:768px){.row-centered{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}}.row-bordered{position:relative}.row-bordered:before{content:'';display:block;width:80%;position:absolute;bottom:0;left:50%;margin-left:-40%;height:1px;background:-webkit-radial-gradient(ellipse at center,rgba(0,0,0,.2) 0,rgba(255,255,255,0) 75%);background:-webkit-radial-gradient(center ellipse,rgba(0,0,0,.2) 0,rgba(255,255,255,0) 75%);background:radial-gradient(ellipse at center,rgba(0,0,0,.2) 0,rgba(255,255,255,0) 75%)}.hero{position:relative;text-align:center;padding:80px 0;border-bottom:1px solid #dcdfe4}.hero-booticon{font-family:Helvetica Neue,Helvetica,Arial,sans-serif;margin:0 auto 30px;width:100%;font-size:8vw;display:block;font-weight:500;text-align:center;cursor:default}.hero-homepage.hero{padding-top:0;padding-bottom:40px;overflow:hidden;border-bottom:0;border-bottom:1px solid #dcdfe4}.hero-homepage>.btn{margin-top:20px}.swag-line:before{content:'';position:fixed;display:block;top:0;left:0;right:0;height:5px;z-index:2;background-color:#26a65b;background:-webkit-linear-gradient(45deg,#28a142,#26a65b);background:linear-gradient(45deg,#28a142,#26a65b)}.navbar{background-color:#fff;border:0 #fff}.navbar-header{text-align:center}.navbar-brand{height:auto;padding:19px 25px;font-size:16px;display:inline-block;float:none;text-align:center;margin:5px 0 0}.navbar-nav{margin-right:-15px}.navbar-nav>li>a{font-size:14px}.navbar-default .navbar-brand,.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover,.navbar-default .navbar-nav>li>a,.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{background:0 0;color:#293a46;font-weight:300}.navbar-default .navbar-toggle{position:absolute;left:0;top:7px;border-color:#fff;color:#293a46;margin-right:0}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background:#f9f9f9;border-color:#f9f9f9}@media (min-width:768px){.navbar-full .navbar-brand{margin-left:-25px}.navbar-tall{height:125px}.navbar-tall .navbar-header,.navbar-tall .navbar-nav{line-height:125px;text-align:left}.navbar-brand{float:none;display:inline-block;text-align:left;margin:0}.navbar-nav>li>a{display:inline-block;margin-left:13px}.navbar-nav>li:first-child>a{margin-left:0}}.screenshot{display:block;overflow:hidden}.screenshot>img{width:100%}.screenshots-fan{margin-top:50px}.screenshots-fan .screenshot{position:relative;width:auto;display:inline-block;text-align:center}.screenshots-fan .screenshot:first-child,.screenshots-fan .screenshot:last-child{z-index:2}.screenshots-fan .screenshot{z-index:3}@media (min-width:768px){.screenshots-fan{position:relative;overflow:hidden;margin-top:60px;height:200px}.screenshots-fan .screenshot{height:auto;top:10px;width:350px}.screenshots-fan .screenshot:first-child,.screenshots-fan .screenshot:last-child{width:250px;position:absolute;top:65px}.screenshots-fan .screenshot:first-child{left:10px}.screenshots-fan .screenshot:last-child{left:auto;right:10px}}@media (min-width:992px){.screenshots-fan{margin-top:60px;height:240px}.screenshots-fan .screenshot{width:400px}.screenshots-fan .screenshot:first-child,.screenshots-fan .screenshot:last-child{width:300px}}@media (min-width:1200px){.screenshots-fan{margin-top:80px;height:380px}.screenshots-fan .screenshot{width:550px}.screenshots-fan .screenshot:first-child,.screenshots-fan .screenshot:last-child{width:450px}}body{font-size:16px;font-family:Roboto,sans-serif;font-weight:300;line-height:1.6}h1{font-size:26px;font-weight:300}h2{font-size:18px;font-weight:300}h3{font-size:26px;font-weight:300}h4{font-size:16px;font-weight:300}h5{font-size:16px;font-weight:400}h1,h2,h3,h4,h5{line-height:1.4}h1,h2{margin:10px 0}h5{margin:6px 0}@media (min-width:768px){body{font-size:16px;font-family:Roboto,sans-serif;font-weight:300;line-height:1.6}h1{font-size:38px;font-weight:300}h2{font-size:26px;font-weight:300;line-height:1.4}h3{font-size:26px;font-weight:300}h4{font-size:18px;font-weight:300}h5{font-size:16px;font-weight:400}}body{color:#293a46}a{text-decoration:none;color:inherit}a:focus,a:hover{text-decoration:underline}.nav li a{text-decoration:none;color:inherit}.nav li a:hover{text-decoration:underline}.text-muted{color:#697176}.template-index h3{font-size:21px;margin-bottom:12px}.template-index h4{color:#697176;line-height:1.6}.template-index h4 a,.template-index p a{color:#26a65b}.template-index h5{font-size:17px;margin-bottom:8px}.homepage-code-example,.homepage-terminal-example{position:relative;font-family:monospace;background:#272b38;color:#48d8a0;border-radius:8px;padding:30px}.homepage-code-example .text-muted,.homepage-terminal-example .text-muted{color:#6a7490}@media (min-width:768px){.homepage-terminal-example{padding:50px}.homepage-code-example{padding:10px}.homepage-code-example>p{margin:0}}.hero-green{color:#26a65b}.hero-black{color:#353535}.hero-red{color:#cb2c37}.svg-icon-large{width:50px;display:block;margin:0 auto}.svg-icon-large>svg{width:100%;height:auto}.row-padded-small{padding:40px 0}.unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.url-diff-container{width:980px}.diff-url-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;width:100%}.diff-url-input{display:inline-block;margin-right:10px;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;height:31px}.diff-url-btn{display:inline-block;float:right;width:48px}.options-label-value{font-weight:400}.diff-url-options-container label input,.diff-url-options-container label select{display:block}.col-md- .col-md-15{width:20%} -------------------------------------------------------------------------------- /dist/diff2html-ui.min.js: -------------------------------------------------------------------------------- 1 | !function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a="function"==typeof require&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}for(var i="function"==typeof require&&require,o=0;o