Merge pull request #238 from rtfpessoa/migrate-to-ts
Migrate codebase to Typescript
|
|
@ -8,13 +8,13 @@ jobs:
|
|||
- checkout
|
||||
- restore_cache:
|
||||
key: dependency-cache-{{ checksum "yarn.lock" }}
|
||||
- run: npm install
|
||||
- run: yarn
|
||||
- save_cache:
|
||||
key: dependency-cache-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- ./node_modules
|
||||
- run: npm run coverage
|
||||
- run: npm run check-coverage
|
||||
- run: yarn run build
|
||||
- run: yarn run coverage
|
||||
|
||||
build-latest: &latest-build
|
||||
docker:
|
||||
|
|
@ -29,49 +29,35 @@ jobs:
|
|||
key: dependency-cache-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- ./node_modules
|
||||
- run: yarn run test
|
||||
- run: yarn run build
|
||||
- run: yarn run lint
|
||||
- run: yarn run coverage
|
||||
- run: yarn run codacy
|
||||
- persist_to_workspace:
|
||||
root: ~/diff2html
|
||||
paths:
|
||||
- docs
|
||||
- dist
|
||||
|
||||
build-node_4:
|
||||
<<: *common-build
|
||||
docker:
|
||||
- image: node:4
|
||||
|
||||
build-node_5:
|
||||
<<: *common-build
|
||||
docker:
|
||||
- image: node:5
|
||||
|
||||
build-node_6:
|
||||
<<: *common-build
|
||||
docker:
|
||||
- image: node:6
|
||||
|
||||
build-node_7:
|
||||
<<: *common-build
|
||||
docker:
|
||||
- image: node:7
|
||||
- build
|
||||
|
||||
build-node_8:
|
||||
<<: *common-build
|
||||
docker:
|
||||
- image: node:8
|
||||
|
||||
build-node_9:
|
||||
build-node_10:
|
||||
<<: *common-build
|
||||
docker:
|
||||
- image: node:9
|
||||
- image: node:10
|
||||
|
||||
build-node_10:
|
||||
build-node_11:
|
||||
<<: *common-build
|
||||
docker:
|
||||
- image: node:11
|
||||
|
||||
build-node_12:
|
||||
<<: *latest-build
|
||||
docker:
|
||||
- image: node:10
|
||||
- image: node:12
|
||||
|
||||
deploy:
|
||||
machine:
|
||||
|
|
@ -80,11 +66,6 @@ jobs:
|
|||
steps:
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Prepare website sources
|
||||
command: |
|
||||
rm -f docs/assets
|
||||
mv dist docs/assets
|
||||
- run:
|
||||
name: Deploy
|
||||
working_directory: ~/diff2html/docs
|
||||
|
|
@ -94,16 +75,13 @@ workflows:
|
|||
version: 2
|
||||
build:
|
||||
jobs:
|
||||
- build-node_4
|
||||
- build-node_5
|
||||
- build-node_6
|
||||
- build-node_7
|
||||
- build-node_8
|
||||
- build-node_9
|
||||
- build-node_10
|
||||
- build-node_11
|
||||
- build-node_12
|
||||
- deploy:
|
||||
requires:
|
||||
- build-node_10
|
||||
- build-node_12
|
||||
filters:
|
||||
branches:
|
||||
only: master
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
# Skip coverage and build folders
|
||||
# Skip build results
|
||||
_target/**
|
||||
coverage/**
|
||||
dist/**
|
||||
|
||||
# Ignore symlink to build folder
|
||||
bundles/**
|
||||
docs/**
|
||||
|
||||
# Ignore HTML templates generated code
|
||||
src/**
|
||||
!src/*.js
|
||||
!src/ui/js/*.js
|
||||
lib/**
|
||||
lib-esm/**
|
||||
node_modules/**
|
||||
src/diff2html-templates.*
|
||||
typings/**
|
||||
|
|
|
|||
341
.eslintrc.json
|
|
@ -1,343 +1,30 @@
|
|||
{
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"ecmaVersion": 2018,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"experimentalObjectRestSpread": true,
|
||||
"jsx": true
|
||||
},
|
||||
"sourceType": "module"
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"es6": true,
|
||||
"node": true,
|
||||
"browser": true,
|
||||
"commonjs": true,
|
||||
"jquery": true,
|
||||
"phantomjs": true,
|
||||
"jasmine": true,
|
||||
"mocha": true,
|
||||
"amd": true,
|
||||
"worker": true,
|
||||
"qunit": true
|
||||
"commonjs": true
|
||||
},
|
||||
"plugins": [
|
||||
"standard",
|
||||
"promise"
|
||||
],
|
||||
"plugins": ["standard", "node", "import", "promise", "@typescript-eslint", "jest"],
|
||||
"globals": {
|
||||
"document": false,
|
||||
"navigator": false,
|
||||
"window": false
|
||||
},
|
||||
"rules": {
|
||||
"accessor-pairs": 2,
|
||||
"arrow-spacing": [
|
||||
2,
|
||||
{
|
||||
"before": true,
|
||||
"after": true
|
||||
}
|
||||
],
|
||||
"block-spacing": [
|
||||
2,
|
||||
"always"
|
||||
],
|
||||
"brace-style": [
|
||||
2,
|
||||
"1tbs",
|
||||
{
|
||||
"allowSingleLine": true
|
||||
}
|
||||
],
|
||||
"camelcase": [
|
||||
2,
|
||||
{
|
||||
"properties": "never"
|
||||
}
|
||||
],
|
||||
"comma-dangle": [
|
||||
2,
|
||||
"never"
|
||||
],
|
||||
"comma-spacing": [
|
||||
2,
|
||||
{
|
||||
"before": false,
|
||||
"after": true
|
||||
}
|
||||
],
|
||||
"comma-style": [
|
||||
2,
|
||||
"last"
|
||||
],
|
||||
"constructor-super": 2,
|
||||
"curly": [
|
||||
2,
|
||||
"multi-line"
|
||||
],
|
||||
"dot-location": [
|
||||
2,
|
||||
"property"
|
||||
],
|
||||
"eol-last": 2,
|
||||
"eqeqeq": [
|
||||
2,
|
||||
"allow-null"
|
||||
],
|
||||
"generator-star-spacing": [
|
||||
2,
|
||||
{
|
||||
"before": true,
|
||||
"after": true
|
||||
}
|
||||
],
|
||||
"handle-callback-err": [
|
||||
2,
|
||||
"^(err|error)$"
|
||||
],
|
||||
"indent": [
|
||||
2,
|
||||
2,
|
||||
{
|
||||
"SwitchCase": 1
|
||||
}
|
||||
],
|
||||
"jsx-quotes": [
|
||||
2,
|
||||
"prefer-single"
|
||||
],
|
||||
"key-spacing": [
|
||||
2,
|
||||
{
|
||||
"beforeColon": false,
|
||||
"afterColon": true
|
||||
}
|
||||
],
|
||||
"keyword-spacing": [
|
||||
2,
|
||||
{
|
||||
"before": true,
|
||||
"after": true
|
||||
}
|
||||
],
|
||||
"new-cap": [
|
||||
2,
|
||||
{
|
||||
"newIsCap": true,
|
||||
"capIsNew": false
|
||||
}
|
||||
],
|
||||
"new-parens": 2,
|
||||
"no-array-constructor": 2,
|
||||
"no-caller": 2,
|
||||
"no-class-assign": 2,
|
||||
"no-cond-assign": 2,
|
||||
"no-const-assign": 2,
|
||||
"no-control-regex": 2,
|
||||
"no-debugger": 2,
|
||||
"no-delete-var": 2,
|
||||
"no-dupe-args": 2,
|
||||
"no-dupe-class-members": 2,
|
||||
"no-dupe-keys": 2,
|
||||
"no-duplicate-case": 2,
|
||||
"no-duplicate-imports": 2,
|
||||
"no-empty-character-class": 2,
|
||||
"no-empty-pattern": 2,
|
||||
"no-eval": 2,
|
||||
"no-ex-assign": 2,
|
||||
"no-extend-native": 2,
|
||||
"no-extra-bind": 2,
|
||||
"no-extra-boolean-cast": 2,
|
||||
"no-extra-parens": [
|
||||
2,
|
||||
"functions"
|
||||
],
|
||||
"no-fallthrough": 2,
|
||||
"no-floating-decimal": 2,
|
||||
"no-func-assign": 2,
|
||||
"no-implied-eval": 2,
|
||||
"no-inner-declarations": [
|
||||
2,
|
||||
"functions"
|
||||
],
|
||||
"no-invalid-regexp": 2,
|
||||
"no-irregular-whitespace": 2,
|
||||
"no-iterator": 2,
|
||||
"no-label-var": 2,
|
||||
"no-labels": [
|
||||
2,
|
||||
{
|
||||
"allowLoop": false,
|
||||
"allowSwitch": false
|
||||
}
|
||||
],
|
||||
"no-lone-blocks": 2,
|
||||
"no-mixed-spaces-and-tabs": 2,
|
||||
"no-multi-spaces": 2,
|
||||
"no-multi-str": 2,
|
||||
"no-multiple-empty-lines": [
|
||||
2,
|
||||
{
|
||||
"max": 1
|
||||
}
|
||||
],
|
||||
"no-native-reassign": 2,
|
||||
"no-negated-in-lhs": 2,
|
||||
"no-new": 2,
|
||||
"no-new-func": 2,
|
||||
"no-new-object": 2,
|
||||
"no-new-require": 2,
|
||||
"no-new-symbol": 2,
|
||||
"no-new-wrappers": 2,
|
||||
"no-obj-calls": 2,
|
||||
"no-octal": 2,
|
||||
"no-octal-escape": 2,
|
||||
"no-path-concat": 2,
|
||||
"no-proto": 2,
|
||||
"no-redeclare": 2,
|
||||
"no-regex-spaces": 2,
|
||||
"no-return-assign": [
|
||||
2,
|
||||
"except-parens"
|
||||
],
|
||||
"no-self-assign": 2,
|
||||
"no-self-compare": 2,
|
||||
"no-sequences": 2,
|
||||
"no-shadow-restricted-names": 2,
|
||||
"no-spaced-func": 2,
|
||||
"no-sparse-arrays": 2,
|
||||
"no-this-before-super": 2,
|
||||
"no-throw-literal": 2,
|
||||
"no-trailing-spaces": 2,
|
||||
"no-undef": 2,
|
||||
"no-undef-init": 2,
|
||||
"no-unexpected-multiline": 2,
|
||||
"no-unmodified-loop-condition": 2,
|
||||
"no-unneeded-ternary": [
|
||||
2,
|
||||
{
|
||||
"defaultAssignment": false
|
||||
}
|
||||
],
|
||||
"no-unreachable": 2,
|
||||
"no-unsafe-finally": 2,
|
||||
"no-unused-vars": [
|
||||
2,
|
||||
{
|
||||
"vars": "all",
|
||||
"args": "none"
|
||||
}
|
||||
],
|
||||
"no-useless-call": 2,
|
||||
"no-useless-computed-key": 2,
|
||||
"no-useless-constructor": 2,
|
||||
"no-useless-escape": 2,
|
||||
"no-whitespace-before-property": 2,
|
||||
"no-with": 2,
|
||||
"one-var": [
|
||||
2,
|
||||
{
|
||||
"initialized": "never"
|
||||
}
|
||||
],
|
||||
"operator-linebreak": [
|
||||
2,
|
||||
"after",
|
||||
{
|
||||
"overrides": {
|
||||
"?": "before",
|
||||
":": "before"
|
||||
}
|
||||
}
|
||||
],
|
||||
"padded-blocks": [
|
||||
2,
|
||||
"never"
|
||||
],
|
||||
"quotes": [
|
||||
2,
|
||||
"single",
|
||||
"avoid-escape"
|
||||
],
|
||||
"semi": [
|
||||
2,
|
||||
"always"
|
||||
],
|
||||
"semi-spacing": [
|
||||
2,
|
||||
{
|
||||
"before": false,
|
||||
"after": true
|
||||
}
|
||||
],
|
||||
"space-before-blocks": [
|
||||
2,
|
||||
"always"
|
||||
],
|
||||
"space-before-function-paren": [
|
||||
2,
|
||||
"never"
|
||||
],
|
||||
"space-in-parens": [
|
||||
2,
|
||||
"never"
|
||||
],
|
||||
"space-infix-ops": 2,
|
||||
"space-unary-ops": [
|
||||
2,
|
||||
{
|
||||
"words": true,
|
||||
"nonwords": false
|
||||
}
|
||||
],
|
||||
"spaced-comment": [
|
||||
2,
|
||||
"always",
|
||||
{
|
||||
"markers": [
|
||||
"global",
|
||||
"globals",
|
||||
"eslint",
|
||||
"eslint-disable",
|
||||
"*package",
|
||||
"!",
|
||||
","
|
||||
]
|
||||
}
|
||||
],
|
||||
"template-curly-spacing": [
|
||||
2,
|
||||
"never"
|
||||
],
|
||||
"use-isnan": 2,
|
||||
"valid-typeof": 2,
|
||||
"wrap-iife": [
|
||||
2,
|
||||
"any"
|
||||
],
|
||||
"yield-star-spacing": [
|
||||
2,
|
||||
"both"
|
||||
],
|
||||
"yoda": [
|
||||
2,
|
||||
"never"
|
||||
],
|
||||
"standard/object-curly-even-spacing": [
|
||||
2,
|
||||
"either"
|
||||
],
|
||||
"standard/array-bracket-even-spacing": [
|
||||
2,
|
||||
"either"
|
||||
],
|
||||
"standard/computed-property-even-spacing": [
|
||||
2,
|
||||
"even"
|
||||
],
|
||||
"promise/param-names": 2,
|
||||
"linebreak-style": [
|
||||
2,
|
||||
"unix"
|
||||
]
|
||||
}
|
||||
"extends": [
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:jest/recommended",
|
||||
"plugin:promise/recommended",
|
||||
"standard",
|
||||
"plugin:prettier/recommended"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
7
.gitignore
vendored
|
|
@ -28,3 +28,10 @@ bower_components/
|
|||
|
||||
# Terraform
|
||||
/terraform/.terraform
|
||||
|
||||
/_target
|
||||
/src/diff2html-templates.*
|
||||
/docs/
|
||||
/bundles/
|
||||
/lib/
|
||||
/lib-esm/
|
||||
|
|
|
|||
3
.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
||||
208
README.md
|
|
@ -1,9 +1,8 @@
|
|||
# diff2html
|
||||
|
||||
[](https://www.codacy.com/app/rtfpessoa/diff2html?utm_source=github.com&utm_medium=referral&utm_content=rtfpessoa/diff2html&utm_campaign=Badge_Grade)
|
||||
[](https://www.codacy.com/app/rtfpessoa/diff2html?utm_source=github.com&utm_medium=referral&utm_content=rtfpessoa/diff2html&utm_campaign=Badge_Grade)
|
||||
[](https://www.codacy.com/app/rtfpessoa/diff2html?utm_source=github.com&utm_medium=referral&utm_content=rtfpessoa/diff2html&utm_campaign=Badge_Coverage)
|
||||
[](https://circleci.com/gh/rtfpessoa/diff2html)
|
||||
[](https://dependencyci.com/github/rtfpessoa/diff2html)
|
||||
|
||||
[](https://www.npmjs.com/package/diff2html)
|
||||
[](https://david-dm.org/rtfpessoa/diff2html)
|
||||
|
|
@ -22,21 +21,21 @@ diff2html generates pretty HTML diffs from git or unified diff output.
|
|||
|
||||
## Features
|
||||
|
||||
* Supports git and unified diffs
|
||||
- Supports git and unified diffs
|
||||
|
||||
* Line by line and Side by side diff
|
||||
- Line by line and Side by side diff
|
||||
|
||||
* New and old line numbers
|
||||
- New and old line numbers
|
||||
|
||||
* Inserted and removed lines
|
||||
- Inserted and removed lines
|
||||
|
||||
* GitHub like style
|
||||
- GitHub like style
|
||||
|
||||
* Code syntax highlight
|
||||
- Code syntax highlight
|
||||
|
||||
* Line similarity matching
|
||||
- Line similarity matching
|
||||
|
||||
* Easy code selection
|
||||
- Easy code selection
|
||||
|
||||
## Online Example
|
||||
|
||||
|
|
@ -44,15 +43,16 @@ diff2html generates pretty HTML diffs from git or unified diff output.
|
|||
|
||||
## Distributions
|
||||
|
||||
* [WebJar](http://www.webjars.org/)
|
||||
|
||||
* [Node Module](https://www.npmjs.org/package/diff2html)
|
||||
|
||||
* [Bower Package](http://bower.io/search/?q=diff2html)
|
||||
|
||||
* [Node CLI](https://www.npmjs.org/package/diff2html-cli)
|
||||
|
||||
* Manually download and import [dist/diff2html.min.js](./dist/diff2html.min.js) into your page
|
||||
- [WebJar](http://www.webjars.org/)
|
||||
- [Node Module](https://www.npmjs.org/package/diff2html)
|
||||
- [Node CLI](https://www.npmjs.org/package/diff2html-cli)
|
||||
- Manually download and import:
|
||||
- Browser
|
||||
- [bundles/js/diff2html.min.js](./bundles/js/diff2html.min.js) - includes the diff parser and html generator
|
||||
- [bundles/js/diff2html-ui.min.js](./bundles/js/diff2html-ui.min.js) - includes the wrapper of diff2html that adds highlight, synchronized scroll, and other nice features
|
||||
- Node.js
|
||||
- [lib/diff2html.js](./lib/diff2html.js) - targeted for es5, includes the diff parser and html generator
|
||||
- [lib-esm/diff2html.js](./lib-esm/diff2html.js) - targeted for es6 - includes the diff parser and html generator
|
||||
|
||||
## How to use
|
||||
|
||||
|
|
@ -62,7 +62,7 @@ Import the stylesheet
|
|||
|
||||
```html
|
||||
<!-- CSS -->
|
||||
<link rel="stylesheet" type="text/css" href="dist/diff2html.css">
|
||||
<link rel="stylesheet" type="text/css" href="bundles/css/diff2html.min.css" />
|
||||
```
|
||||
|
||||
You can also refer to it from a CDN like [CDNJS](https://cdnjs.com/libraries/diff2html).
|
||||
|
|
@ -73,37 +73,41 @@ Import the stylesheet and the library code
|
|||
|
||||
```html
|
||||
<!-- CSS -->
|
||||
<link rel="stylesheet" type="text/css" href="dist/diff2html.css">
|
||||
<link rel="stylesheet" type="text/css" href="bundles/css/diff2html.min.css" />
|
||||
|
||||
<!-- Javascripts -->
|
||||
<script type="text/javascript" src="dist/diff2html.js"></script>
|
||||
<script type="text/javascript" src="bundles/js/diff2html.min.js"></script>
|
||||
```
|
||||
|
||||
It will now be available as a global variable named `Diff2Html`.
|
||||
|
||||
```js
|
||||
var diffHtml = Diff2Html.getPrettyHtml(
|
||||
'<Unified Diff String>',
|
||||
{inputFormat: 'diff', showFiles: true, matching: 'lines', outputFormat: 'side-by-side'}
|
||||
);
|
||||
document.getElementById("destination-elem-id").innerHTML = diffHtml;
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
var diffHtml = global.Diff2Html.html("<Unified Diff String>", {
|
||||
drawFileList: true,
|
||||
matching: "lines",
|
||||
outputFormat: "side-by-side"
|
||||
});
|
||||
document.getElementById("destination-elem-id").innerHTML = diffHtml;
|
||||
});
|
||||
```
|
||||
|
||||
### Node Module
|
||||
|
||||
```js
|
||||
let diff2html = require("diff2html").Diff2Html
|
||||
const Diff2html = require("diff2html");
|
||||
const diffJson = Diff2html.parse("<Unified Diff String>");
|
||||
const diffHtml = Diff2html.html(diffJson, { drawFileList: true });
|
||||
document.getElementById("destination-elem-id").innerHTML = diffHtml;
|
||||
```
|
||||
|
||||
### Angular
|
||||
|
||||
* Typescript
|
||||
- Typescript
|
||||
|
||||
```typescript
|
||||
// import diff2html
|
||||
import {Diff2Html} from 'diff2html'
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
|
||||
import * as Diff2Html from "diff2html";
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
|
||||
export class AppDiffComponent implements OnInit {
|
||||
outputHtml: string;
|
||||
|
|
@ -111,18 +115,18 @@ export class AppDiffComponent implements OnInit {
|
|||
this.init();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
ngOnInit() {}
|
||||
|
||||
init() {
|
||||
let strInput = "--- a/server/vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go\n+++ b/server/vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go\n@@ -1035,6 +1035,17 @@ func Prctl(option int, arg2 uintptr, arg3 uintptr, arg4 uintptr, arg5 uintptr) (\n \n // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n \n+func Pselect(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *Timespec, sigmask *Sigset_t) (n int, err error) {\n+\tr0, _, e1 := Syscall6(SYS_PSELECT6, uintptr(nfd), uintptr(unsafe.Pointer(r)), uintptr(unsafe.Pointer(w)), uintptr(unsafe.Pointer(e)), uintptr(unsafe.Pointer(timeout)), uintptr(unsafe.Pointer(sigmask)))\n+\tn = int(r0)\n+\tif e1 != 0 {\n+\t\terr = errnoErr(e1)\n+\t}\n+\treturn\n+}\n+\n+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n+\n func read(fd int, p []byte) (n int, err error) {\n \tvar _p0 unsafe.Pointer\n \tif len(p) > 0 {\n";
|
||||
let outputHtml = Diff2Html.getPrettyHtml(strInput, {inputFormat: 'diff', showFiles: true, matching: 'lines'});
|
||||
let strInput =
|
||||
"--- a/server/vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go\n+++ b/server/vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go\n@@ -1035,6 +1035,17 @@ func Prctl(option int, arg2 uintptr, arg3 uintptr, arg4 uintptr, arg5 uintptr) (\n \n // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n \n+func Pselect(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *Timespec, sigmask *Sigset_t) (n int, err error) {\n+\tr0, _, e1 := Syscall6(SYS_PSELECT6, uintptr(nfd), uintptr(unsafe.Pointer(r)), uintptr(unsafe.Pointer(w)), uintptr(unsafe.Pointer(e)), uintptr(unsafe.Pointer(timeout)), uintptr(unsafe.Pointer(sigmask)))\n+\tn = int(r0)\n+\tif e1 != 0 {\n+\t\terr = errnoErr(e1)\n+\t}\n+\treturn\n+}\n+\n+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n+\n func read(fd int, p []byte) (n int, err error) {\n \tvar _p0 unsafe.Pointer\n \tif len(p) > 0 {\n";
|
||||
let outputHtml = Diff2Html.html(strInput, { drawFileList: true, matching: "lines" });
|
||||
this.outputHtml = outputHtml;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* HTML
|
||||
- HTML
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
|
|
@ -136,7 +140,7 @@ export class AppDiffComponent implements OnInit {
|
|||
</html>
|
||||
```
|
||||
|
||||
* `.angular-cli.json` - Add styles
|
||||
- `.angular-cli.json` - Add styles
|
||||
|
||||
```json
|
||||
"styles": [
|
||||
|
|
@ -148,27 +152,28 @@ export class AppDiffComponent implements OnInit {
|
|||
|
||||
```vue
|
||||
<template>
|
||||
<div v-html="prettyHtml" />
|
||||
<div v-html="prettyHtml" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Diff2Html } from "diff2html";
|
||||
import "diff2html/dist/diff2html.min.css";
|
||||
import * as Diff2Html from "diff2html";
|
||||
import "diff2html/bundles/css/diff2html.min.css";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
diffs: "--- a/server/vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go\n+++ b/server/vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go\n@@ -1035,6 +1035,17 @@ func Prctl(option int, arg2 uintptr, arg3 uintptr, arg4 uintptr, arg5 uintptr) (\n \n // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n \n+func Pselect(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *Timespec, sigmask *Sigset_t) (n int, err error) {\n+\tr0, _, e1 := Syscall6(SYS_PSELECT6, uintptr(nfd), uintptr(unsafe.Pointer(r)), uintptr(unsafe.Pointer(w)), uintptr(unsafe.Pointer(e)), uintptr(unsafe.Pointer(timeout)), uintptr(unsafe.Pointer(sigmask)))\n+\tn = int(r0)\n+\tif e1 != 0 {\n+\t\terr = errnoErr(e1)\n+\t}\n+\treturn\n+}\n+\n+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n+\n func read(fd int, p []byte) (n int, err error) {\n \tvar _p0 unsafe.Pointer\n \tif len(p) > 0 {\n"
|
||||
diffs:
|
||||
"--- a/server/vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go\n+++ b/server/vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go\n@@ -1035,6 +1035,17 @@ func Prctl(option int, arg2 uintptr, arg3 uintptr, arg4 uintptr, arg5 uintptr) (\n \n // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n \n+func Pselect(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *Timespec, sigmask *Sigset_t) (n int, err error) {\n+\tr0, _, e1 := Syscall6(SYS_PSELECT6, uintptr(nfd), uintptr(unsafe.Pointer(r)), uintptr(unsafe.Pointer(w)), uintptr(unsafe.Pointer(e)), uintptr(unsafe.Pointer(timeout)), uintptr(unsafe.Pointer(sigmask)))\n+\tn = int(r0)\n+\tif e1 != 0 {\n+\t\terr = errnoErr(e1)\n+\t}\n+\treturn\n+}\n+\n+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n+\n func read(fd int, p []byte) (n int, err error) {\n \tvar _p0 unsafe.Pointer\n \tif len(p) > 0 {\n"
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
prettyHtml() {
|
||||
return Diff2Html.getPrettyHtml(this.diffs, {
|
||||
inputFormat: "diff",
|
||||
showFiles: true,
|
||||
matching: "lines",
|
||||
outputFormat: "side-by-side"
|
||||
});
|
||||
inputFormat: "diff",
|
||||
drawFileList: true,
|
||||
matching: "lines",
|
||||
outputFormat: "side-by-side"
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -185,41 +190,40 @@ export default {
|
|||
|
||||
getPrettyHtml(input: any, configuration?: Options): string
|
||||
|
||||
> Check out the [src/diff2html.d.ts](./src/diff2html.d.ts) for a complete API definition in TypeScript.
|
||||
|
||||
> Check out the [docs/demo.html](./docs/demo.html) for a demo example.
|
||||
|
||||
## Configuration
|
||||
|
||||
The HTML output accepts a Javascript object with configuration. Possible options:
|
||||
|
||||
- `inputFormat`: the format of the input data: `'diff'` or `'json'`, default is `'diff'`
|
||||
- `outputFormat`: the format of the output data: `'line-by-line'` or `'side-by-side'`, default is `'line-by-line'`
|
||||
- `showFiles`: show a file list before the diff: `true` or `false`, default is `false`
|
||||
- `diffStyle`: show differences level in each line: `word` or `char`, default is `word`
|
||||
- `matching`: matching level: `'lines'` for matching lines, `'words'` for matching lines and words or `'none'`, default is `none`
|
||||
- `matchWordsThreshold`: similarity threshold for word matching, default is 0.25
|
||||
- `matchingMaxComparisons`: perform at most this much comparisons for line matching a block of changes, default is `2500`
|
||||
- `maxLineSizeInBlockForComparison`: maximum number os characters of the bigger line in a block to apply comparison, default is `200`
|
||||
- `maxLineLengthHighlight`: only perform diff changes highlight if lines are smaller than this, default is `10000`
|
||||
- `templates`: object with previously compiled templates to replace parts of the html
|
||||
- `rawTemplates`: object with raw not compiled templates to replace parts of the html
|
||||
- `renderNothingWhenEmpty`: render nothing if the diff shows no change in its comparison: `true` or `false`, default is `false`
|
||||
- `inputFormat`: the format of the input data: `'diff'` or `'json'`, default is `'diff'`
|
||||
- `outputFormat`: the format of the output data: `'line-by-line'` or `'side-by-side'`, default is `'line-by-line'`
|
||||
- `drawFileList`: show a file list before the diff: `true` or `false`, default is `false`
|
||||
- `diffStyle`: show differences level in each line: `word` or `char`, default is `word`
|
||||
- `matching`: matching level: `'lines'` for matching lines, `'words'` for matching lines and words or `'none'`, default is `none`
|
||||
- `matchWordsThreshold`: similarity threshold for word matching, default is 0.25
|
||||
- `matchingMaxComparisons`: perform at most this much comparisons for line matching a block of changes, default is `2500`
|
||||
- `maxLineSizeInBlockForComparison`: maximum number os characters of the bigger line in a block to apply comparison, default is `200`
|
||||
- `maxLineLengthHighlight`: only perform diff changes highlight if lines are smaller than this, default is `10000`
|
||||
- `compiledTemplates`: object with previously compiled templates to replace parts of the html
|
||||
- `rawTemplates`: object with raw not compiled templates to replace parts of the html
|
||||
- `renderNothingWhenEmpty`: render nothing if the diff shows no change in its comparison: `true` or `false`, default is `false`
|
||||
> For more information regarding the possible templates look into [src/templates](https://github.com/rtfpessoa/diff2html/tree/master/src/templates)
|
||||
|
||||
** Diff2HtmlUI Helper Options **
|
||||
- `synchronisedScroll`: scroll both panes in side-by-side mode: `true` or `false`, default is `false`
|
||||
|
||||
- `synchronisedScroll`: scroll both panes in side-by-side mode: `true` or `false`, default is `false`
|
||||
|
||||
> For more information regarding the possible templates look into [src/templates](https://github.com/rtfpessoa/diff2html/tree/master/src/templates)
|
||||
|
||||
|
||||
## Diff2HtmlUI Helper
|
||||
|
||||
> Simple wrapper to ease simple tasks in the browser such as: code highlight and js effects
|
||||
|
||||
* Invoke Diff2html
|
||||
* Inject output in DOM element
|
||||
* Enable collapsible file summary list
|
||||
* Enable syntax highlight of the code in the diffs
|
||||
- Invoke Diff2html
|
||||
- Inject output in DOM element
|
||||
- Enable collapsible file summary list
|
||||
- Enable syntax highlight of the code in the diffs
|
||||
|
||||
### How to use
|
||||
|
||||
|
|
@ -227,52 +231,56 @@ The HTML output accepts a Javascript object with configuration. Possible options
|
|||
|
||||
```html
|
||||
<!-- CSS -->
|
||||
<link rel="stylesheet" type="text/css" href="dist/diff2html.css">
|
||||
<link rel="stylesheet" type="text/css" href="bundles/css/diff2html.min.css" />
|
||||
|
||||
<!-- Javascripts -->
|
||||
<script type="text/javascript" src="dist/diff2html.js"></script>
|
||||
<script type="text/javascript" src="dist/diff2html-ui.js"></script>
|
||||
<script type="text/javascript" src="bundles/js/diff2html-ui.min.js"></script>
|
||||
```
|
||||
|
||||
#### Init
|
||||
|
||||
```js
|
||||
var diff2htmlUi = new Diff2HtmlUI({diff: diffString});
|
||||
const targetElement = document.getElementById("destination-elem-id");
|
||||
const configuration = { drawFileList: true, matching: "lines" };
|
||||
|
||||
const diff2htmlUi = new Diff2HtmlUI(diffString, targetElement, configuration);
|
||||
// or
|
||||
var diff2htmlUi = new Diff2HtmlUI({json: diffJson});
|
||||
const diff2htmlUi = new Diff2HtmlUI(diffJson, targetElement, configuration);
|
||||
```
|
||||
|
||||
#### Draw
|
||||
|
||||
```js
|
||||
diff2htmlUi.draw('html-target-elem', {inputFormat: 'json', showFiles: true, matching: 'lines'});
|
||||
diff2htmlUi.draw();
|
||||
```
|
||||
|
||||
#### Syntax Highlight
|
||||
|
||||
> Add the dependencies.
|
||||
Choose one color scheme, and add the main highlight code. Note that the stylesheet for the color scheme must come **before** the main diff2html stylesheet.
|
||||
If your favourite language is not included in the default package also add its javascript highlight file.
|
||||
|
||||
```html
|
||||
<!-- Stylesheet -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/styles/github.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="dist/diff2html.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/styles/github.min.css" />
|
||||
<link rel="stylesheet" type="text/css" href="bundles/css/diff2html.min.css" />
|
||||
|
||||
<!-- Javascripts -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/highlight.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/languages/scala.min.js"></script>
|
||||
<script type="text/javascript" src="dist/diff2html-ui.js"></script>
|
||||
<script type="text/javascript" src="bundles/js/diff2html-ui.min.js"></script>
|
||||
```
|
||||
|
||||
> Invoke the Diff2HtmlUI helper
|
||||
> Pass the option `highlight` with value true or invoke `diff2htmlUi.highlightCode()` after `diff2htmlUi.draw()`.
|
||||
|
||||
```js
|
||||
$(document).ready(function() {
|
||||
var diff2htmlUi = new Diff2HtmlUI({diff: lineDiffExample});
|
||||
diff2htmlUi.draw('#line-by-line', {inputFormat: 'json', showFiles: true, matching: 'lines'});
|
||||
diff2htmlUi.highlightCode('#line-by-line');
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const diffString = `diff --git a/sample.js b/sample.js
|
||||
index 0000001..0ddf2ba
|
||||
--- a/sample.js
|
||||
+++ b/sample.js
|
||||
@@ -1 +1 @@
|
||||
-console.log("Hello World!")
|
||||
+console.log("Hello from Diff2Html!")`;
|
||||
const targetElement = document.getElementById("myDiffElement");
|
||||
const configuration = { inputFormat: "json", drawFileList: true, matching: "lines", highlight: true };
|
||||
const diff2htmlUi = new Diff2HtmlUI(diffString, targetElement, configuration);
|
||||
diff2htmlUi.draw();
|
||||
diff2htmlUi.highlightCode();
|
||||
});
|
||||
```
|
||||
|
||||
|
|
@ -282,17 +290,18 @@ $(document).ready(function() {
|
|||
|
||||
```html
|
||||
<!-- Javascripts -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.js"></script>
|
||||
<script type="text/javascript" src="dist/diff2html-ui.js"></script>
|
||||
<script type="text/javascript" src="bundles/js/diff2html-ui.min.js"></script>
|
||||
```
|
||||
|
||||
> Invoke the Diff2HtmlUI helper
|
||||
> Pass the option `fileListToggle` with value true or invoke `diff2htmlUi.fileListToggle()` after `diff2htmlUi.draw()`.
|
||||
|
||||
```js
|
||||
$(document).ready(function() {
|
||||
var diff2htmlUi = new Diff2HtmlUI({diff: lineDiffExample});
|
||||
diff2htmlUi.draw('#line-by-line', {inputFormat: 'json', showFiles: true, matching: 'lines'});
|
||||
diff2htmlUi.fileListCloseable('#line-by-line', false);
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const targetElement = document.getElementById("myDiffElement");
|
||||
var diff2htmlUi = new Diff2HtmlUI(lineDiffExample, targetElement, { drawFileList: true, matching: "lines" });
|
||||
diff2htmlUi.draw();
|
||||
diff2htmlUi.fileListToggle(false);
|
||||
});
|
||||
```
|
||||
|
||||
|
|
@ -301,11 +310,13 @@ $(document).ready(function() {
|
|||
### 1. Out of memory or Slow execution
|
||||
|
||||
#### Causes:
|
||||
* Big files
|
||||
* Big lines
|
||||
|
||||
- Big files
|
||||
- Big lines
|
||||
|
||||
#### Fix:
|
||||
* Disable the line matching algorithm, by setting the option `{"matching": "none"}` when invoking diff2html
|
||||
|
||||
- Disable the line matching algorithm, by setting the option `{"matching": "none"}` when invoking diff2html
|
||||
|
||||
## Contributions
|
||||
|
||||
|
|
@ -355,6 +366,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|||
|
||||
<!-- markdownlint-enable -->
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
||||
|
|
|
|||
366
dist/diff2html-ui.js
vendored
|
|
@ -1,366 +0,0 @@
|
|||
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||
(function (global){
|
||||
/*
|
||||
*
|
||||
* Diff to HTML (diff2html-ui.js)
|
||||
* Author: rtfpessoa
|
||||
*
|
||||
* Depends on: [ jQuery ]
|
||||
* Optional dependencies on: [ highlight.js ]
|
||||
*
|
||||
*/
|
||||
|
||||
/*global $, hljs, Diff2Html*/
|
||||
|
||||
(function() {
|
||||
var highlightJS = require('./highlight.js-internals.js').HighlightJS;
|
||||
|
||||
var diffJson = null;
|
||||
var defaultTarget = 'body';
|
||||
var currentSelectionColumnId = -1;
|
||||
|
||||
function Diff2HtmlUI(config) {
|
||||
var cfg = config || {};
|
||||
|
||||
if (cfg.diff) {
|
||||
diffJson = Diff2Html.getJsonFromDiff(cfg.diff);
|
||||
} else if (cfg.json) {
|
||||
diffJson = cfg.json;
|
||||
}
|
||||
|
||||
this._initSelection();
|
||||
}
|
||||
|
||||
Diff2HtmlUI.prototype.draw = function(targetId, config) {
|
||||
var cfg = config || {};
|
||||
cfg.inputFormat = 'json';
|
||||
var $target = this._getTarget(targetId);
|
||||
$target.html(Diff2Html.getPrettyHtml(diffJson, cfg));
|
||||
|
||||
if (cfg.synchronisedScroll) {
|
||||
this.synchronisedScroll($target, cfg);
|
||||
}
|
||||
};
|
||||
|
||||
Diff2HtmlUI.prototype.synchronisedScroll = function(targetId) {
|
||||
var $target = this._getTarget(targetId);
|
||||
$target.find('.d2h-file-side-diff').scroll(function() {
|
||||
var $this = $(this);
|
||||
$this.closest('.d2h-file-wrapper').find('.d2h-file-side-diff')
|
||||
.scrollLeft($this.scrollLeft());
|
||||
});
|
||||
};
|
||||
|
||||
Diff2HtmlUI.prototype.fileListCloseable = function(targetId, startVisible) {
|
||||
var $target = this._getTarget(targetId);
|
||||
|
||||
var hashTag = this._getHashTag();
|
||||
|
||||
var $showBtn = $target.find('.d2h-show');
|
||||
var $hideBtn = $target.find('.d2h-hide');
|
||||
var $fileList = $target.find('.d2h-file-list');
|
||||
|
||||
if (hashTag === 'files-summary-show') show();
|
||||
else if (hashTag === 'files-summary-hide') hide();
|
||||
else if (startVisible) show();
|
||||
else hide();
|
||||
|
||||
$showBtn.click(show);
|
||||
$hideBtn.click(hide);
|
||||
|
||||
function show() {
|
||||
$showBtn.hide();
|
||||
$hideBtn.show();
|
||||
$fileList.show();
|
||||
}
|
||||
|
||||
function hide() {
|
||||
$hideBtn.hide();
|
||||
$showBtn.show();
|
||||
$fileList.hide();
|
||||
}
|
||||
};
|
||||
|
||||
Diff2HtmlUI.prototype.highlightCode = function(targetId) {
|
||||
var that = this;
|
||||
|
||||
var $target = that._getTarget(targetId);
|
||||
|
||||
// collect all the diff files and execute the highlight on their lines
|
||||
var $files = $target.find('.d2h-file-wrapper');
|
||||
$files.map(function(_i, file) {
|
||||
var oldLinesState;
|
||||
var newLinesState;
|
||||
var $file = $(file);
|
||||
var language = $file.data('lang');
|
||||
|
||||
// collect all the code lines and execute the highlight on them
|
||||
var $codeLines = $file.find('.d2h-code-line-ctn');
|
||||
$codeLines.map(function(_j, line) {
|
||||
var $line = $(line);
|
||||
var text = line.textContent;
|
||||
var lineParent = line.parentNode;
|
||||
|
||||
var lineState;
|
||||
if (lineParent.className.indexOf('d2h-del') !== -1) {
|
||||
lineState = oldLinesState;
|
||||
} else {
|
||||
lineState = newLinesState;
|
||||
}
|
||||
|
||||
var result = hljs.getLanguage(language) ? hljs.highlight(language, text, true, lineState) : hljs.highlightAuto(text);
|
||||
|
||||
if (lineParent.className.indexOf('d2h-del') !== -1) {
|
||||
oldLinesState = result.top;
|
||||
} else if (lineParent.className.indexOf('d2h-ins') !== -1) {
|
||||
newLinesState = result.top;
|
||||
} else {
|
||||
oldLinesState = result.top;
|
||||
newLinesState = result.top;
|
||||
}
|
||||
|
||||
var originalStream = highlightJS.nodeStream(line);
|
||||
if (originalStream.length) {
|
||||
var resultNode = document.createElementNS('http://www.w3.org/1999/xhtml', 'div');
|
||||
resultNode.innerHTML = result.value;
|
||||
result.value = highlightJS.mergeStreams(originalStream, highlightJS.nodeStream(resultNode), text);
|
||||
}
|
||||
|
||||
$line.addClass('hljs');
|
||||
$line.addClass(result.language);
|
||||
$line.html(result.value);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Diff2HtmlUI.prototype._getTarget = function(targetId) {
|
||||
var $target;
|
||||
|
||||
if (typeof targetId === 'object' && targetId instanceof jQuery) {
|
||||
$target = targetId;
|
||||
} else if (typeof targetId === 'string') {
|
||||
$target = $(targetId);
|
||||
} else {
|
||||
console.error("Wrong target provided! Falling back to default value 'body'.");
|
||||
console.log('Please provide a jQuery object or a valid DOM query string.');
|
||||
$target = $(defaultTarget);
|
||||
}
|
||||
|
||||
return $target;
|
||||
};
|
||||
|
||||
Diff2HtmlUI.prototype._getHashTag = function() {
|
||||
var docUrl = document.URL;
|
||||
var hashTagIndex = docUrl.indexOf('#');
|
||||
|
||||
var hashTag = null;
|
||||
if (hashTagIndex !== -1) {
|
||||
hashTag = docUrl.substr(hashTagIndex + 1);
|
||||
}
|
||||
|
||||
return hashTag;
|
||||
};
|
||||
|
||||
Diff2HtmlUI.prototype._distinct = function(collection) {
|
||||
return collection.filter(function(v, i) {
|
||||
return collection.indexOf(v) === i;
|
||||
});
|
||||
};
|
||||
|
||||
Diff2HtmlUI.prototype._initSelection = function() {
|
||||
var body = $('body');
|
||||
var that = this;
|
||||
|
||||
body.on('mousedown', '.d2h-diff-table', function(event) {
|
||||
var target = $(event.target);
|
||||
var table = target.closest('.d2h-diff-table');
|
||||
|
||||
if (target.closest('.d2h-code-line,.d2h-code-side-line').length) {
|
||||
table.removeClass('selecting-left');
|
||||
table.addClass('selecting-right');
|
||||
currentSelectionColumnId = 1;
|
||||
} else if (target.closest('.d2h-code-linenumber,.d2h-code-side-linenumber').length) {
|
||||
table.removeClass('selecting-right');
|
||||
table.addClass('selecting-left');
|
||||
currentSelectionColumnId = 0;
|
||||
}
|
||||
});
|
||||
|
||||
body.on('copy', '.d2h-diff-table', function(event) {
|
||||
var clipboardData = event.originalEvent.clipboardData;
|
||||
var text = that._getSelectedText();
|
||||
clipboardData.setData('text', text);
|
||||
event.preventDefault();
|
||||
});
|
||||
};
|
||||
|
||||
Diff2HtmlUI.prototype._getSelectedText = function() {
|
||||
var sel = window.getSelection();
|
||||
var range = sel.getRangeAt(0);
|
||||
var doc = range.cloneContents();
|
||||
var nodes = doc.querySelectorAll('tr');
|
||||
var text = '';
|
||||
var idx = currentSelectionColumnId;
|
||||
|
||||
if (nodes.length === 0) {
|
||||
text = doc.textContent;
|
||||
} else {
|
||||
[].forEach.call(nodes, function(tr, i) {
|
||||
var td = tr.cells[tr.cells.length === 1 ? 0 : idx];
|
||||
text += (i ? '\n' : '') + td.textContent.replace(/(?:\r\n|\r|\n)/g, '');
|
||||
});
|
||||
}
|
||||
|
||||
return text;
|
||||
};
|
||||
|
||||
module.exports.Diff2HtmlUI = Diff2HtmlUI;
|
||||
|
||||
// Expose diff2html in the browser
|
||||
global.Diff2HtmlUI = Diff2HtmlUI;
|
||||
})();
|
||||
|
||||
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||||
},{"./highlight.js-internals.js":2}],2:[function(require,module,exports){
|
||||
/*
|
||||
*
|
||||
* highlight.js
|
||||
* Author: isagalaev
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
function HighlightJS() {
|
||||
}
|
||||
|
||||
/*
|
||||
* Copied from Highlight.js Private API
|
||||
* Will be removed when this part of the API is exposed
|
||||
*/
|
||||
|
||||
/* Utility vars */
|
||||
|
||||
var ArrayProto = [];
|
||||
|
||||
/* Utility functions */
|
||||
|
||||
function escape(value) {
|
||||
return value.replace(/&/gm, '&').replace(/</gm, '<').replace(/>/gm, '>');
|
||||
}
|
||||
|
||||
function tag(node) {
|
||||
return node.nodeName.toLowerCase();
|
||||
}
|
||||
|
||||
/* Stream merging */
|
||||
|
||||
HighlightJS.prototype.nodeStream = function(node) {
|
||||
var result = [];
|
||||
(function _nodeStream(node, offset) {
|
||||
for (var child = node.firstChild; child; child = child.nextSibling) {
|
||||
if (child.nodeType === 3) {
|
||||
offset += child.nodeValue.length;
|
||||
} else if (child.nodeType === 1) {
|
||||
result.push({
|
||||
event: 'start',
|
||||
offset: offset,
|
||||
node: child
|
||||
});
|
||||
offset = _nodeStream(child, offset);
|
||||
// Prevent void elements from having an end tag that would actually
|
||||
// double them in the output. There are more void elements in HTML
|
||||
// but we list only those realistically expected in code display.
|
||||
if (!tag(child).match(/br|hr|img|input/)) {
|
||||
result.push({
|
||||
event: 'stop',
|
||||
offset: offset,
|
||||
node: child
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return offset;
|
||||
})(node, 0);
|
||||
return result;
|
||||
};
|
||||
|
||||
HighlightJS.prototype.mergeStreams = function(original, highlighted, value) {
|
||||
var processed = 0;
|
||||
var result = '';
|
||||
var nodeStack = [];
|
||||
|
||||
function selectStream() {
|
||||
if (!original.length || !highlighted.length) {
|
||||
return original.length ? original : highlighted;
|
||||
}
|
||||
if (original[0].offset !== highlighted[0].offset) {
|
||||
return (original[0].offset < highlighted[0].offset) ? original : highlighted;
|
||||
}
|
||||
|
||||
/*
|
||||
To avoid starting the stream just before it should stop the order is
|
||||
ensured that original always starts first and closes last:
|
||||
if (event1 == 'start' && event2 == 'start')
|
||||
return original;
|
||||
if (event1 == 'start' && event2 == 'stop')
|
||||
return highlighted;
|
||||
if (event1 == 'stop' && event2 == 'start')
|
||||
return original;
|
||||
if (event1 == 'stop' && event2 == 'stop')
|
||||
return highlighted;
|
||||
... which is collapsed to:
|
||||
*/
|
||||
return highlighted[0].event === 'start' ? original : highlighted;
|
||||
}
|
||||
|
||||
function open(node) {
|
||||
function attr_str(a) {
|
||||
return ' ' + a.nodeName + '="' + escape(a.value) + '"';
|
||||
}
|
||||
|
||||
result += '<' + tag(node) + ArrayProto.map.call(node.attributes, attr_str).join('') + '>';
|
||||
}
|
||||
|
||||
function close(node) {
|
||||
result += '</' + tag(node) + '>';
|
||||
}
|
||||
|
||||
function render(event) {
|
||||
(event.event === 'start' ? open : close)(event.node);
|
||||
}
|
||||
|
||||
while (original.length || highlighted.length) {
|
||||
var stream = selectStream();
|
||||
result += escape(value.substring(processed, stream[0].offset));
|
||||
processed = stream[0].offset;
|
||||
if (stream === original) {
|
||||
/*
|
||||
On any opening or closing tag of the original markup we first close
|
||||
the entire highlighted node stack, then render the original tag along
|
||||
with all the following original tags at the same offset and then
|
||||
reopen all the tags on the highlighted stack.
|
||||
*/
|
||||
nodeStack.reverse().forEach(close);
|
||||
do {
|
||||
render(stream.splice(0, 1)[0]);
|
||||
stream = selectStream();
|
||||
} while (stream === original && stream.length && stream[0].offset === processed);
|
||||
nodeStack.reverse().forEach(open);
|
||||
} else {
|
||||
if (stream[0].event === 'start') {
|
||||
nodeStack.push(stream[0].node);
|
||||
} else {
|
||||
nodeStack.pop();
|
||||
}
|
||||
render(stream.splice(0, 1)[0]);
|
||||
}
|
||||
}
|
||||
return result + escape(value.substr(processed));
|
||||
};
|
||||
|
||||
/* **** Highlight.js Private API **** */
|
||||
|
||||
module.exports.HighlightJS = new HighlightJS();
|
||||
})();
|
||||
|
||||
},{}]},{},[1]);
|
||||
1
dist/diff2html-ui.min.js
vendored
367
dist/diff2html.css
vendored
71
dist/diff2html.d.ts
vendored
|
|
@ -1,71 +0,0 @@
|
|||
// Type definitions for diff2html
|
||||
// Project: https://github.com/rtfpessoa/diff2html
|
||||
// Definitions by: rtfpessoa <https://github.com/rtfpessoa/>
|
||||
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
||||
|
||||
declare namespace Diff2Html {
|
||||
|
||||
export interface Options {
|
||||
inputFormat?: 'diff' | 'json';
|
||||
outputFormat?: 'line-by-line' | 'side-by-side';
|
||||
showFiles?: boolean;
|
||||
diffStyle?: 'word' | 'char';
|
||||
matching?: 'lines' | 'words' | 'none';
|
||||
matchWordsThreshold?: number;
|
||||
matchingMaxComparisons?: number;
|
||||
maxLineSizeInBlockForComparison?: number;
|
||||
maxLineLengthHighlight?: number;
|
||||
templates?: object;
|
||||
rawTemplates?: object;
|
||||
renderNothingWhenEmpty?: boolean;
|
||||
}
|
||||
|
||||
export interface Line {
|
||||
content: string;
|
||||
type: string;
|
||||
oldNumber: number;
|
||||
newNumber: number;
|
||||
}
|
||||
|
||||
export interface Block {
|
||||
oldStartLine: number;
|
||||
oldStartLine2?: number;
|
||||
newStartLine: number;
|
||||
header: string;
|
||||
lines: Line[];
|
||||
}
|
||||
|
||||
export interface Result {
|
||||
addedLines: number;
|
||||
deletedLines: number;
|
||||
isCombined: boolean;
|
||||
isGitDiff: boolean;
|
||||
oldName: string;
|
||||
newName: string;
|
||||
language: string;
|
||||
blocks: Block[];
|
||||
oldMode?: string;
|
||||
newMode?: string;
|
||||
deletedFileMode?: string;
|
||||
newFileMode?: string;
|
||||
isDeleted?: boolean;
|
||||
isNew?: boolean;
|
||||
isCopy?: boolean;
|
||||
isRename?: boolean;
|
||||
unchangedPercentage?: number;
|
||||
changedPercentage?: number;
|
||||
checksumBefore?: string;
|
||||
checksumAfter?: string;
|
||||
mode?: string;
|
||||
}
|
||||
|
||||
export interface Diff2Html {
|
||||
getJsonFromDiff(input: string, configuration?: Options): Result[];
|
||||
getPrettyHtml(input: any, configuration?: Options): string;
|
||||
}
|
||||
}
|
||||
|
||||
declare module "diff2html" {
|
||||
var d2h: { "Diff2Html": Diff2Html.Diff2Html };
|
||||
export = d2h;
|
||||
}
|
||||
4782
dist/diff2html.js
vendored
1
dist/diff2html.min.css
vendored
1
dist/diff2html.min.js
vendored
|
|
@ -1 +0,0 @@
|
|||
../dist
|
||||
255
docs/demo.html
|
|
@ -1,255 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" class="js">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<!--[if IE]>
|
||||
<meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'/>
|
||||
<![endif]-->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<meta name="description" content="Diff parser and pretty html generator">
|
||||
<meta name="keywords" content="diff2html,git,diff,unified,pretty,html,css,javaccript">
|
||||
<meta name="author" content="Rodrigo Fernandes (rtfpessoa)">
|
||||
|
||||
<title>diff2html</title>
|
||||
|
||||
<!-- search engine -->
|
||||
<link rel="canonical" href="https://diff2html.xyz">
|
||||
|
||||
<!-- open graph -->
|
||||
<meta property="og:title" content="diff2html">
|
||||
<meta property="og:type" content="website">
|
||||
|
||||
<meta property="og:description"
|
||||
content="Diff parser and pretty html generator.">
|
||||
|
||||
<meta property="og:url" content="https://diff2html.xyz">
|
||||
<meta property="og:site_name" content="diff2html">
|
||||
|
||||
<!-- Bootstrap -->
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"
|
||||
integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
|
||||
|
||||
<!-- Custom styles for this template -->
|
||||
<link href="main.min.css" rel="stylesheet">
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/styles/github.min.css">
|
||||
|
||||
<!-- diff2html -->
|
||||
<link rel="stylesheet" type="text/css" href="assets/diff2html.min.css">
|
||||
<!-- -->
|
||||
|
||||
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-78351861-2', 'auto');
|
||||
ga('send', 'pageview');
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body class="template-index template-index-min">
|
||||
<div class="swag-line">
|
||||
|
||||
<div class="container">
|
||||
<nav class="navbar navbar-default navbar-tall navbar-full" role="navigation">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#global-nav">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="index.html">diff2html</a>
|
||||
</div>
|
||||
|
||||
<div class="collapse navbar-collapse" id="global-nav">
|
||||
<div class="navbar-right">
|
||||
<ul class="nav navbar-nav">
|
||||
|
||||
<li>
|
||||
<a href="index.html#install">Getting Started</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="index.html#cli">CLI</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="demo.html?diff=https://github.com/rtfpessoa/diff2html/pull/106">Demo</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="https://github.com/rtfpessoa/diff2html#how-to-use" target="_blank">Docs</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="https://github.com/rtfpessoa/diff2html/issues/new" target="_blank">Support</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<h1>Diff Prettifier <a href="#help">
|
||||
<svg height="32" class="octicon octicon-unverified" viewBox="0 0 16 16" version="1.1" width="64" aria-hidden="true">
|
||||
<path
|
||||
d="M15.67 7.06l-1.08-1.34c-.17-.22-.28-.48-.31-.77l-.19-1.7a1.51 1.51 0 0 0-1.33-1.33l-1.7-.19c-.3-.03-.56-.16-.78-.33L8.94.32c-.55-.44-1.33-.44-1.88 0L5.72 1.4c-.22.17-.48.28-.77.31l-1.7.19c-.7.08-1.25.63-1.33 1.33l-.19 1.7c-.03.3-.16.56-.33.78L.32 7.05c-.44.55-.44 1.33 0 1.88l1.08 1.34c.17.22.28.48.31.77l.19 1.7c.08.7.63 1.25 1.33 1.33l1.7.19c.3.03.56.16.78.33l1.34 1.08c.55.44 1.33.44 1.88 0l1.34-1.08c.22-.17.48-.28.77-.31l1.7-.19c.7-.08 1.25-.63 1.33-1.33l.19-1.7c.03-.3.16-.56.33-.78l1.08-1.34c.44-.55.44-1.33 0-1.88zM9 11.5c0 .28-.22.5-.5.5h-1c-.27 0-.5-.22-.5-.5v-1c0-.28.23-.5.5-.5h1c.28 0 .5.22.5.5v1zm1.56-4.89c-.06.17-.17.33-.3.47-.13.16-.14.19-.33.38-.16.17-.31.3-.52.45-.11.09-.2.19-.28.27-.08.08-.14.17-.19.27-.05.1-.08.19-.11.3-.03.11-.03.13-.03.25H7.13c0-.22 0-.31.03-.48.03-.19.08-.36.14-.52.06-.14.14-.28.25-.42.11-.13.23-.25.41-.38.27-.19.36-.3.48-.52.12-.22.2-.38.2-.59 0-.27-.06-.45-.2-.58-.13-.13-.31-.19-.58-.19-.09 0-.19.02-.3.05-.11.03-.17.09-.25.16-.08.07-.14.11-.2.2a.41.41 0 0 0-.09.28h-2c0-.38.13-.56.27-.83.16-.27.36-.5.61-.67.25-.17.55-.3.88-.38.33-.08.7-.13 1.09-.13.44 0 .83.05 1.17.13.34.09.63.22.88.39.23.17.41.38.55.63.13.25.19.55.19.88 0 .22 0 .42-.08.59l-.02-.01z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</h1>
|
||||
<p>GitHub, Bitbucket and GitLab commit and pull request compatible</p>
|
||||
<p>Just paste the GitHub, Bitbucket or GitLab commit, pull request or merge request url
|
||||
or any other git or unified compatible diff and we will render a pretty html representation of it
|
||||
with code syntax highlight and line similarity matching for better code reviews.
|
||||
</p>
|
||||
<h2>Options:</h2>
|
||||
<div class="row">
|
||||
<div class="col-md-2 col-xs-12 col-15">
|
||||
<label title="Output format of the HTML, either line by line or side by side">Output Format
|
||||
<select class="options-label-value" id="diff-url-options-output-format" name="outputFormat">
|
||||
<option value="line-by-line" selected>Line by Line</option>
|
||||
<option value="side-by-side">Side by Side</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div class=" col-md-2 col-xs-12 col-15">
|
||||
<label title="Show the file list summary before the diff">File Summary
|
||||
<input class="options-label-value" id="diff-url-options-show-files" type="checkbox" name="showFiles" checked/>
|
||||
</label>
|
||||
</div>
|
||||
<div class=" col-md-2 col-xs-12 col-15">
|
||||
<label title="Level of matching for the comparison algorithm">Matching Type
|
||||
<select class="options-label-value" id="diff-url-options-matching" name="matching">
|
||||
<option value="lines">Lines</option>
|
||||
<option value="words" selected>Words</option>
|
||||
<option value="none">None</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div class=" col-md-2 col-xs-12 col-15">
|
||||
<label title="Similarity threshold for the matching algorithm">Words Threshold
|
||||
<input class="options-label-value" id="diff-url-options-match-words-threshold" type="number"
|
||||
name="matchWordsThreshold" value="0.25" step="0.05"
|
||||
min="0" max="1"/>
|
||||
</label>
|
||||
</div>
|
||||
<div class=" col-md-2 col-xs-12 col-15">
|
||||
<label title="Maximum number of comparison performed by the matching algorithm in a block of changes">Max
|
||||
Comparisons
|
||||
<input class="options-label-value" id="diff-url-options-matching-max-comparisons" type="number"
|
||||
name="matchingMaxComparisons" value="2500"
|
||||
step="100" min="0"/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<div class="diff-url-wrapper">
|
||||
<input id="url" class="diff-url-input" type="text" name="url" placeholder="URL"/>
|
||||
<a id="url-btn" class="diff-url-btn btn btn-sm" href="#">Load</a>
|
||||
</div>
|
||||
<br>
|
||||
<div id="url-diff-container" style="margin: 0 auto;">
|
||||
</div>
|
||||
<br>
|
||||
<h3 id="help">Help:</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<b>Why should I use this instead of GitHub, Bitbucket or GitLab?</b>
|
||||
<p>Code Syntax Highlight</p>
|
||||
<p>Line similarity match (similar lines are together)</p>
|
||||
<p>Line by Line and Side by Side diffs</p>
|
||||
<p>Supports any git and unified compatible diffs</p>
|
||||
<p>Easy code selection</p>
|
||||
</li>
|
||||
<li>
|
||||
<b>What urls are supported?</b>
|
||||
<p>Any GitHub, Bitbucket or GitLab Commit, Pull Request or Merge Request urls.</p>
|
||||
<p>Any Git or Unified Raw Diff or Patch urls.</p>
|
||||
</li>
|
||||
<li>
|
||||
<b>Can I send a custom url for a friend, colleague or co-worker?</b>
|
||||
<p>Just add a url parameter called diff to current url using as value your Commit, Pull Request, Merge Request, Diff
|
||||
or Patch url.</p>
|
||||
<p>ex: <a href="demo.html?diff=https://github.com/rtfpessoa/diff2html/pull/106">https://diff2html.xyz/demo.html?diff=https://github.com/rtfpessoa/diff2html/pull/106</a>
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<b>Why can't I paste a diff?</b>
|
||||
<p><a href="https://diffy.org/">diffy.org</a> is an amazing tool created by <a
|
||||
href="https://github.com/pbu88">pbu88</a>
|
||||
to share your diffs and uses diff2html under the hood.</p>
|
||||
<p>Also, diff2html cli can directly publish diffs to <a href="https://diffy.org/">diffy.org</a></p>
|
||||
</li>
|
||||
</ul>
|
||||
<br>
|
||||
<h3>Thank you</h3>
|
||||
<p>I want to thank <a href="https://github.com/kevinsimper">kevinsimper</a> for this great idea,
|
||||
providing better diff support for existing online services.
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<footer class="footer clearfix">
|
||||
<p class="col-xs-10 col-xs-offset-1">
|
||||
Website originally designed and built by
|
||||
<a href="https://twitter.com/mdo" target="_blank">@mdo</a>,
|
||||
<a href="https://twitter.com/fat" target="_blank">@fat</a>, and
|
||||
<a href="https://twitter.com/dhg" target="_blank">@dhg</a>,
|
||||
adapted with <span class="hero-red">❤</span> by
|
||||
<a href="https://twitter.com/rtfpessoa" target="_blank">@rtfpessoa</a>.
|
||||
</p>
|
||||
<ul class="footer-list col-xs-10 col-xs-offset-1">
|
||||
|
||||
<li class="footer-list-item">
|
||||
<a class="footer-list-link" href="https://github.com/rtfpessoa/diff2html#how-to-use"
|
||||
target="_blank">FAQ</a>
|
||||
</li>
|
||||
|
||||
<li class="footer-list-item">
|
||||
<a class="footer-list-link" href="https://diff2html.xyz">diff2html</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- General JavaScript -->
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.3/jquery.min.js"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"
|
||||
integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS"
|
||||
crossorigin="anonymous"></script>
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "http://schema.org/",
|
||||
"@type": "SoftwareSourceCode",
|
||||
"name": "diff2html",
|
||||
"author": "Rodrigo Fernandes",
|
||||
"image": "https://diff2html.xyz/img/snapshot-3.png",
|
||||
"description": "Diff parser and pretty html generator.",
|
||||
"codeRepository": "https://github.com/rtfpessoa/diff2html",
|
||||
"programmingLanguage": "JavaScript",
|
||||
"runtimePlatform": "Node >= 0.12",
|
||||
"mainEntityOfPage": "https://diff2html.xyz/"
|
||||
}
|
||||
</script>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/highlight.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/languages/scala.min.js"></script>
|
||||
|
||||
<!-- diff2html -->
|
||||
<script type="text/javascript" src="assets/diff2html.min.js"></script>
|
||||
<script type="text/javascript" src="assets/diff2html-ui.min.js"></script>
|
||||
<!-- -->
|
||||
|
||||
<script type="text/javascript" src="demo.min.js"></script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
769
docs/demo.js
|
|
@ -1,769 +0,0 @@
|
|||
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
||||
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
||||
(factory((global.WHATWGFetch = {})));
|
||||
}(this, (function (exports) { 'use strict';
|
||||
|
||||
var support = {
|
||||
searchParams: 'URLSearchParams' in self,
|
||||
iterable: 'Symbol' in self && 'iterator' in Symbol,
|
||||
blob:
|
||||
'FileReader' in self &&
|
||||
'Blob' in self &&
|
||||
(function() {
|
||||
try {
|
||||
new Blob();
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
})(),
|
||||
formData: 'FormData' in self,
|
||||
arrayBuffer: 'ArrayBuffer' in self
|
||||
};
|
||||
|
||||
function isDataView(obj) {
|
||||
return obj && DataView.prototype.isPrototypeOf(obj)
|
||||
}
|
||||
|
||||
if (support.arrayBuffer) {
|
||||
var viewClasses = [
|
||||
'[object Int8Array]',
|
||||
'[object Uint8Array]',
|
||||
'[object Uint8ClampedArray]',
|
||||
'[object Int16Array]',
|
||||
'[object Uint16Array]',
|
||||
'[object Int32Array]',
|
||||
'[object Uint32Array]',
|
||||
'[object Float32Array]',
|
||||
'[object Float64Array]'
|
||||
];
|
||||
|
||||
var isArrayBufferView =
|
||||
ArrayBuffer.isView ||
|
||||
function(obj) {
|
||||
return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeName(name) {
|
||||
if (typeof name !== 'string') {
|
||||
name = String(name);
|
||||
}
|
||||
if (/[^a-z0-9\-#$%&'*+.^_`|~]/i.test(name)) {
|
||||
throw new TypeError('Invalid character in header field name')
|
||||
}
|
||||
return name.toLowerCase()
|
||||
}
|
||||
|
||||
function normalizeValue(value) {
|
||||
if (typeof value !== 'string') {
|
||||
value = String(value);
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Build a destructive iterator for the value list
|
||||
function iteratorFor(items) {
|
||||
var iterator = {
|
||||
next: function() {
|
||||
var value = items.shift();
|
||||
return {done: value === undefined, value: value}
|
||||
}
|
||||
};
|
||||
|
||||
if (support.iterable) {
|
||||
iterator[Symbol.iterator] = function() {
|
||||
return iterator
|
||||
};
|
||||
}
|
||||
|
||||
return iterator
|
||||
}
|
||||
|
||||
function Headers(headers) {
|
||||
this.map = {};
|
||||
|
||||
if (headers instanceof Headers) {
|
||||
headers.forEach(function(value, name) {
|
||||
this.append(name, value);
|
||||
}, this);
|
||||
} else if (Array.isArray(headers)) {
|
||||
headers.forEach(function(header) {
|
||||
this.append(header[0], header[1]);
|
||||
}, this);
|
||||
} else if (headers) {
|
||||
Object.getOwnPropertyNames(headers).forEach(function(name) {
|
||||
this.append(name, headers[name]);
|
||||
}, this);
|
||||
}
|
||||
}
|
||||
|
||||
Headers.prototype.append = function(name, value) {
|
||||
name = normalizeName(name);
|
||||
value = normalizeValue(value);
|
||||
var oldValue = this.map[name];
|
||||
this.map[name] = oldValue ? oldValue + ', ' + value : value;
|
||||
};
|
||||
|
||||
Headers.prototype['delete'] = function(name) {
|
||||
delete this.map[normalizeName(name)];
|
||||
};
|
||||
|
||||
Headers.prototype.get = function(name) {
|
||||
name = normalizeName(name);
|
||||
return this.has(name) ? this.map[name] : null
|
||||
};
|
||||
|
||||
Headers.prototype.has = function(name) {
|
||||
return this.map.hasOwnProperty(normalizeName(name))
|
||||
};
|
||||
|
||||
Headers.prototype.set = function(name, value) {
|
||||
this.map[normalizeName(name)] = normalizeValue(value);
|
||||
};
|
||||
|
||||
Headers.prototype.forEach = function(callback, thisArg) {
|
||||
for (var name in this.map) {
|
||||
if (this.map.hasOwnProperty(name)) {
|
||||
callback.call(thisArg, this.map[name], name, this);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Headers.prototype.keys = function() {
|
||||
var items = [];
|
||||
this.forEach(function(value, name) {
|
||||
items.push(name);
|
||||
});
|
||||
return iteratorFor(items)
|
||||
};
|
||||
|
||||
Headers.prototype.values = function() {
|
||||
var items = [];
|
||||
this.forEach(function(value) {
|
||||
items.push(value);
|
||||
});
|
||||
return iteratorFor(items)
|
||||
};
|
||||
|
||||
Headers.prototype.entries = function() {
|
||||
var items = [];
|
||||
this.forEach(function(value, name) {
|
||||
items.push([name, value]);
|
||||
});
|
||||
return iteratorFor(items)
|
||||
};
|
||||
|
||||
if (support.iterable) {
|
||||
Headers.prototype[Symbol.iterator] = Headers.prototype.entries;
|
||||
}
|
||||
|
||||
function consumed(body) {
|
||||
if (body.bodyUsed) {
|
||||
return Promise.reject(new TypeError('Already read'))
|
||||
}
|
||||
body.bodyUsed = true;
|
||||
}
|
||||
|
||||
function fileReaderReady(reader) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
reader.onload = function() {
|
||||
resolve(reader.result);
|
||||
};
|
||||
reader.onerror = function() {
|
||||
reject(reader.error);
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
function readBlobAsArrayBuffer(blob) {
|
||||
var reader = new FileReader();
|
||||
var promise = fileReaderReady(reader);
|
||||
reader.readAsArrayBuffer(blob);
|
||||
return promise
|
||||
}
|
||||
|
||||
function readBlobAsText(blob) {
|
||||
var reader = new FileReader();
|
||||
var promise = fileReaderReady(reader);
|
||||
reader.readAsText(blob);
|
||||
return promise
|
||||
}
|
||||
|
||||
function readArrayBufferAsText(buf) {
|
||||
var view = new Uint8Array(buf);
|
||||
var chars = new Array(view.length);
|
||||
|
||||
for (var i = 0; i < view.length; i++) {
|
||||
chars[i] = String.fromCharCode(view[i]);
|
||||
}
|
||||
return chars.join('')
|
||||
}
|
||||
|
||||
function bufferClone(buf) {
|
||||
if (buf.slice) {
|
||||
return buf.slice(0)
|
||||
} else {
|
||||
var view = new Uint8Array(buf.byteLength);
|
||||
view.set(new Uint8Array(buf));
|
||||
return view.buffer
|
||||
}
|
||||
}
|
||||
|
||||
function Body() {
|
||||
this.bodyUsed = false;
|
||||
|
||||
this._initBody = function(body) {
|
||||
this._bodyInit = body;
|
||||
if (!body) {
|
||||
this._bodyText = '';
|
||||
} else if (typeof body === 'string') {
|
||||
this._bodyText = body;
|
||||
} else if (support.blob && Blob.prototype.isPrototypeOf(body)) {
|
||||
this._bodyBlob = body;
|
||||
} else if (support.formData && FormData.prototype.isPrototypeOf(body)) {
|
||||
this._bodyFormData = body;
|
||||
} else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
|
||||
this._bodyText = body.toString();
|
||||
} else if (support.arrayBuffer && support.blob && isDataView(body)) {
|
||||
this._bodyArrayBuffer = bufferClone(body.buffer);
|
||||
// IE 10-11 can't handle a DataView body.
|
||||
this._bodyInit = new Blob([this._bodyArrayBuffer]);
|
||||
} else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {
|
||||
this._bodyArrayBuffer = bufferClone(body);
|
||||
} else {
|
||||
this._bodyText = body = Object.prototype.toString.call(body);
|
||||
}
|
||||
|
||||
if (!this.headers.get('content-type')) {
|
||||
if (typeof body === 'string') {
|
||||
this.headers.set('content-type', 'text/plain;charset=UTF-8');
|
||||
} else if (this._bodyBlob && this._bodyBlob.type) {
|
||||
this.headers.set('content-type', this._bodyBlob.type);
|
||||
} else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
|
||||
this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (support.blob) {
|
||||
this.blob = function() {
|
||||
var rejected = consumed(this);
|
||||
if (rejected) {
|
||||
return rejected
|
||||
}
|
||||
|
||||
if (this._bodyBlob) {
|
||||
return Promise.resolve(this._bodyBlob)
|
||||
} else if (this._bodyArrayBuffer) {
|
||||
return Promise.resolve(new Blob([this._bodyArrayBuffer]))
|
||||
} else if (this._bodyFormData) {
|
||||
throw new Error('could not read FormData body as blob')
|
||||
} else {
|
||||
return Promise.resolve(new Blob([this._bodyText]))
|
||||
}
|
||||
};
|
||||
|
||||
this.arrayBuffer = function() {
|
||||
if (this._bodyArrayBuffer) {
|
||||
return consumed(this) || Promise.resolve(this._bodyArrayBuffer)
|
||||
} else {
|
||||
return this.blob().then(readBlobAsArrayBuffer)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this.text = function() {
|
||||
var rejected = consumed(this);
|
||||
if (rejected) {
|
||||
return rejected
|
||||
}
|
||||
|
||||
if (this._bodyBlob) {
|
||||
return readBlobAsText(this._bodyBlob)
|
||||
} else if (this._bodyArrayBuffer) {
|
||||
return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer))
|
||||
} else if (this._bodyFormData) {
|
||||
throw new Error('could not read FormData body as text')
|
||||
} else {
|
||||
return Promise.resolve(this._bodyText)
|
||||
}
|
||||
};
|
||||
|
||||
if (support.formData) {
|
||||
this.formData = function() {
|
||||
return this.text().then(decode)
|
||||
};
|
||||
}
|
||||
|
||||
this.json = function() {
|
||||
return this.text().then(JSON.parse)
|
||||
};
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
// HTTP methods whose capitalization should be normalized
|
||||
var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'];
|
||||
|
||||
function normalizeMethod(method) {
|
||||
var upcased = method.toUpperCase();
|
||||
return methods.indexOf(upcased) > -1 ? upcased : method
|
||||
}
|
||||
|
||||
function Request(input, options) {
|
||||
options = options || {};
|
||||
var body = options.body;
|
||||
|
||||
if (input instanceof Request) {
|
||||
if (input.bodyUsed) {
|
||||
throw new TypeError('Already read')
|
||||
}
|
||||
this.url = input.url;
|
||||
this.credentials = input.credentials;
|
||||
if (!options.headers) {
|
||||
this.headers = new Headers(input.headers);
|
||||
}
|
||||
this.method = input.method;
|
||||
this.mode = input.mode;
|
||||
this.signal = input.signal;
|
||||
if (!body && input._bodyInit != null) {
|
||||
body = input._bodyInit;
|
||||
input.bodyUsed = true;
|
||||
}
|
||||
} else {
|
||||
this.url = String(input);
|
||||
}
|
||||
|
||||
this.credentials = options.credentials || this.credentials || 'same-origin';
|
||||
if (options.headers || !this.headers) {
|
||||
this.headers = new Headers(options.headers);
|
||||
}
|
||||
this.method = normalizeMethod(options.method || this.method || 'GET');
|
||||
this.mode = options.mode || this.mode || null;
|
||||
this.signal = options.signal || this.signal;
|
||||
this.referrer = null;
|
||||
|
||||
if ((this.method === 'GET' || this.method === 'HEAD') && body) {
|
||||
throw new TypeError('Body not allowed for GET or HEAD requests')
|
||||
}
|
||||
this._initBody(body);
|
||||
}
|
||||
|
||||
Request.prototype.clone = function() {
|
||||
return new Request(this, {body: this._bodyInit})
|
||||
};
|
||||
|
||||
function decode(body) {
|
||||
var form = new FormData();
|
||||
body
|
||||
.trim()
|
||||
.split('&')
|
||||
.forEach(function(bytes) {
|
||||
if (bytes) {
|
||||
var split = bytes.split('=');
|
||||
var name = split.shift().replace(/\+/g, ' ');
|
||||
var value = split.join('=').replace(/\+/g, ' ');
|
||||
form.append(decodeURIComponent(name), decodeURIComponent(value));
|
||||
}
|
||||
});
|
||||
return form
|
||||
}
|
||||
|
||||
function parseHeaders(rawHeaders) {
|
||||
var headers = new Headers();
|
||||
// Replace instances of \r\n and \n followed by at least one space or horizontal tab with a space
|
||||
// https://tools.ietf.org/html/rfc7230#section-3.2
|
||||
var preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, ' ');
|
||||
preProcessedHeaders.split(/\r?\n/).forEach(function(line) {
|
||||
var parts = line.split(':');
|
||||
var key = parts.shift().trim();
|
||||
if (key) {
|
||||
var value = parts.join(':').trim();
|
||||
headers.append(key, value);
|
||||
}
|
||||
});
|
||||
return headers
|
||||
}
|
||||
|
||||
Body.call(Request.prototype);
|
||||
|
||||
function Response(bodyInit, options) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
this.type = 'default';
|
||||
this.status = options.status === undefined ? 200 : options.status;
|
||||
this.ok = this.status >= 200 && this.status < 300;
|
||||
this.statusText = 'statusText' in options ? options.statusText : 'OK';
|
||||
this.headers = new Headers(options.headers);
|
||||
this.url = options.url || '';
|
||||
this._initBody(bodyInit);
|
||||
}
|
||||
|
||||
Body.call(Response.prototype);
|
||||
|
||||
Response.prototype.clone = function() {
|
||||
return new Response(this._bodyInit, {
|
||||
status: this.status,
|
||||
statusText: this.statusText,
|
||||
headers: new Headers(this.headers),
|
||||
url: this.url
|
||||
})
|
||||
};
|
||||
|
||||
Response.error = function() {
|
||||
var response = new Response(null, {status: 0, statusText: ''});
|
||||
response.type = 'error';
|
||||
return response
|
||||
};
|
||||
|
||||
var redirectStatuses = [301, 302, 303, 307, 308];
|
||||
|
||||
Response.redirect = function(url, status) {
|
||||
if (redirectStatuses.indexOf(status) === -1) {
|
||||
throw new RangeError('Invalid status code')
|
||||
}
|
||||
|
||||
return new Response(null, {status: status, headers: {location: url}})
|
||||
};
|
||||
|
||||
exports.DOMException = self.DOMException;
|
||||
try {
|
||||
new exports.DOMException();
|
||||
} catch (err) {
|
||||
exports.DOMException = function(message, name) {
|
||||
this.message = message;
|
||||
this.name = name;
|
||||
var error = Error(message);
|
||||
this.stack = error.stack;
|
||||
};
|
||||
exports.DOMException.prototype = Object.create(Error.prototype);
|
||||
exports.DOMException.prototype.constructor = exports.DOMException;
|
||||
}
|
||||
|
||||
function fetch(input, init) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var request = new Request(input, init);
|
||||
|
||||
if (request.signal && request.signal.aborted) {
|
||||
return reject(new exports.DOMException('Aborted', 'AbortError'))
|
||||
}
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
|
||||
function abortXhr() {
|
||||
xhr.abort();
|
||||
}
|
||||
|
||||
xhr.onload = function() {
|
||||
var options = {
|
||||
status: xhr.status,
|
||||
statusText: xhr.statusText,
|
||||
headers: parseHeaders(xhr.getAllResponseHeaders() || '')
|
||||
};
|
||||
options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL');
|
||||
var body = 'response' in xhr ? xhr.response : xhr.responseText;
|
||||
resolve(new Response(body, options));
|
||||
};
|
||||
|
||||
xhr.onerror = function() {
|
||||
reject(new TypeError('Network request failed'));
|
||||
};
|
||||
|
||||
xhr.ontimeout = function() {
|
||||
reject(new TypeError('Network request failed'));
|
||||
};
|
||||
|
||||
xhr.onabort = function() {
|
||||
reject(new exports.DOMException('Aborted', 'AbortError'));
|
||||
};
|
||||
|
||||
xhr.open(request.method, request.url, true);
|
||||
|
||||
if (request.credentials === 'include') {
|
||||
xhr.withCredentials = true;
|
||||
} else if (request.credentials === 'omit') {
|
||||
xhr.withCredentials = false;
|
||||
}
|
||||
|
||||
if ('responseType' in xhr && support.blob) {
|
||||
xhr.responseType = 'blob';
|
||||
}
|
||||
|
||||
request.headers.forEach(function(value, name) {
|
||||
xhr.setRequestHeader(name, value);
|
||||
});
|
||||
|
||||
if (request.signal) {
|
||||
request.signal.addEventListener('abort', abortXhr);
|
||||
|
||||
xhr.onreadystatechange = function() {
|
||||
// DONE (success or failure)
|
||||
if (xhr.readyState === 4) {
|
||||
request.signal.removeEventListener('abort', abortXhr);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit);
|
||||
})
|
||||
}
|
||||
|
||||
fetch.polyfill = true;
|
||||
|
||||
if (!self.fetch) {
|
||||
self.fetch = fetch;
|
||||
self.Headers = Headers;
|
||||
self.Request = Request;
|
||||
self.Response = Response;
|
||||
}
|
||||
|
||||
exports.Headers = Headers;
|
||||
exports.Request = Request;
|
||||
exports.Response = Response;
|
||||
exports.fetch = fetch;
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
})));
|
||||
|
||||
},{}],2:[function(require,module,exports){
|
||||
/* global Diff2HtmlUI */
|
||||
|
||||
/*
|
||||
* Example URLs:
|
||||
*
|
||||
* https://github.com/rtfpessoa/diff2html/commit/7d02e67f3b3386ac5d804f974d025cd7a1165839
|
||||
* https://github.com/rtfpessoa/diff2html/pull/106
|
||||
*
|
||||
* https://gitlab.com/gitlab-org/gitlab-ce/commit/4e963fed42ad518caa7353d361a38a1250c99c41
|
||||
* https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6763
|
||||
*
|
||||
* https://bitbucket.org/atlassian/amps/commits/52c38116f12475f75af4a147b7a7685478b83eca
|
||||
* https://bitbucket.org/atlassian/amps/pull-requests/236
|
||||
*/
|
||||
|
||||
$(document).ready(function() {
|
||||
// Improves browser compatibility
|
||||
require('whatwg-fetch');
|
||||
|
||||
var searchParam = 'diff';
|
||||
|
||||
var $container = $('.container');
|
||||
var $url = $('#url');
|
||||
var $outputFormat = $('#diff-url-options-output-format');
|
||||
var $showFiles = $('#diff-url-options-show-files');
|
||||
var $matching = $('#diff-url-options-matching');
|
||||
var $wordsThreshold = $('#diff-url-options-match-words-threshold');
|
||||
var $matchingMaxComparisons = $('#diff-url-options-matching-max-comparisons');
|
||||
|
||||
if (window.location.search) {
|
||||
var url = getUrlFromSearch(window.location.search);
|
||||
$url.val(url);
|
||||
smartDraw(url);
|
||||
}
|
||||
|
||||
bind();
|
||||
|
||||
$outputFormat
|
||||
.add($showFiles)
|
||||
.add($matching)
|
||||
.add($wordsThreshold)
|
||||
.add($matchingMaxComparisons)
|
||||
.change(function(e) {
|
||||
console.log('');
|
||||
console.log(e);
|
||||
console.log('');
|
||||
smartDraw(null, true);
|
||||
});
|
||||
|
||||
function getUrlFromSearch(search) {
|
||||
try {
|
||||
return search
|
||||
.split('?')[1]
|
||||
.split(searchParam + '=')[1]
|
||||
.split('&')[0];
|
||||
} catch (_ignore) {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getParamsFromSearch(search) {
|
||||
var map = {};
|
||||
try {
|
||||
search
|
||||
.split('?')[1]
|
||||
.split('&')
|
||||
.map(function(e) {
|
||||
var values = e.split('=');
|
||||
map[values[0]] = values[1];
|
||||
});
|
||||
} catch (_ignore) {
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
function bind() {
|
||||
$('#url-btn').click(function(e) {
|
||||
e.preventDefault();
|
||||
var url = $url.val();
|
||||
smartDraw(url);
|
||||
});
|
||||
|
||||
$url.on('paste', function(e) {
|
||||
var url = e.originalEvent.clipboardData.getData('Text');
|
||||
smartDraw(url);
|
||||
});
|
||||
}
|
||||
|
||||
function prepareUrl(url) {
|
||||
var fetchUrl;
|
||||
var headers = new Headers();
|
||||
|
||||
var githubCommitUrl = /^https?:\/\/(?:www\.)?github\.com\/(.*?)\/(.*?)\/commit\/(.*?)(?:\.diff)?(?:\.patch)?(?:\/.*)?$/;
|
||||
var githubPrUrl = /^https?:\/\/(?:www\.)?github\.com\/(.*?)\/(.*?)\/pull\/(.*?)(?:\.diff)?(?:\.patch)?(?:\/.*)?$/;
|
||||
|
||||
var gitlabCommitUrl = /^https?:\/\/(?:www\.)?gitlab\.com\/(.*?)\/(.*?)\/commit\/(.*?)(?:\.diff)?(?:\.patch)?(?:\/.*)?$/;
|
||||
var gitlabPrUrl = /^https?:\/\/(?:www\.)?gitlab\.com\/(.*?)\/(.*?)\/merge_requests\/(.*?)(?:\.diff)?(?:\.patch)?(?:\/.*)?$/;
|
||||
|
||||
var bitbucketCommitUrl = /^https?:\/\/(?:www\.)?bitbucket\.org\/(.*?)\/(.*?)\/commits\/(.*?)(?:\/raw)?(?:\/.*)?$/;
|
||||
var bitbucketPrUrl = /^https?:\/\/(?:www\.)?bitbucket\.org\/(.*?)\/(.*?)\/pull-requests\/(.*?)(?:\/.*)?$/;
|
||||
|
||||
function gitLabUrlGen(userName, projectName, type, value) {
|
||||
return 'https://crossorigin.me/https://gitlab.com/' + userName + '/' + projectName + '/' + type + '/' + value + '.diff';
|
||||
}
|
||||
|
||||
function gitHubUrlGen(userName, projectName, type, value) {
|
||||
headers.append('Accept', 'application/vnd.github.v3.diff');
|
||||
return 'https://api.github.com/repos/' + userName + '/' + projectName + '/' + type + '/' + value;
|
||||
}
|
||||
|
||||
function bitbucketUrlGen(userName, projectName, type, value) {
|
||||
var baseUrl = 'https://bitbucket.org/api/2.0/repositories/';
|
||||
if (type === 'pullrequests') {
|
||||
return baseUrl + userName + '/' + projectName + '/pullrequests/' + value + '/diff';
|
||||
}
|
||||
return baseUrl + userName + '/' + projectName + '/diff/' + value;
|
||||
}
|
||||
|
||||
var values;
|
||||
if ((values = githubCommitUrl.exec(url))) {
|
||||
fetchUrl = gitHubUrlGen(values[1], values[2], 'commits', values[3]);
|
||||
} else if ((values = githubPrUrl.exec(url))) {
|
||||
fetchUrl = gitHubUrlGen(values[1], values[2], 'pulls', values[3]);
|
||||
} else if ((values = gitlabCommitUrl.exec(url))) {
|
||||
fetchUrl = gitLabUrlGen(values[1], values[2], 'commit', values[3]);
|
||||
} else if ((values = gitlabPrUrl.exec(url))) {
|
||||
fetchUrl = gitLabUrlGen(values[1], values[2], 'merge_requests', values[3]);
|
||||
} else if ((values = bitbucketCommitUrl.exec(url))) {
|
||||
fetchUrl = bitbucketUrlGen(values[1], values[2], 'commit', values[3]);
|
||||
} else if ((values = bitbucketPrUrl.exec(url))) {
|
||||
fetchUrl = bitbucketUrlGen(values[1], values[2], 'pullrequests', values[3]);
|
||||
} else {
|
||||
console.info('Could not parse url, using the provided url.');
|
||||
fetchUrl = 'https://crossorigin.me/' + url;
|
||||
}
|
||||
|
||||
return {
|
||||
originalUrl: url,
|
||||
url: fetchUrl,
|
||||
headers: headers
|
||||
};
|
||||
}
|
||||
|
||||
function smartDraw(urlOpt, forced) {
|
||||
var url = urlOpt || $url.val();
|
||||
var req = prepareUrl(url);
|
||||
draw(req, forced);
|
||||
}
|
||||
|
||||
function draw(req, forced) {
|
||||
if (!validateUrl(req.url)) {
|
||||
console.error('Invalid url provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (validateUrl(req.originalUrl)) updateUrl(req.originalUrl);
|
||||
|
||||
var outputFormat = $outputFormat.val();
|
||||
var showFiles = $showFiles.is(':checked');
|
||||
var matching = $matching.val();
|
||||
var wordsThreshold = $wordsThreshold.val();
|
||||
var matchingMaxComparisons = $matchingMaxComparisons.val();
|
||||
|
||||
fetch(req.url, {
|
||||
method: 'GET',
|
||||
headers: req.headers,
|
||||
mode: 'cors',
|
||||
cache: 'default'
|
||||
})
|
||||
.then(function(res) {
|
||||
return res.text();
|
||||
})
|
||||
.then(function(data) {
|
||||
var container = '#url-diff-container';
|
||||
var diff2htmlUi = new Diff2HtmlUI({diff: data});
|
||||
|
||||
if (outputFormat === 'side-by-side') {
|
||||
$container.css({'width': '100%'});
|
||||
} else {
|
||||
$container.css({'width': ''});
|
||||
}
|
||||
|
||||
var params = getParamsFromSearch(window.location.search);
|
||||
delete params[searchParam];
|
||||
|
||||
if (forced) {
|
||||
params['outputFormat'] = outputFormat;
|
||||
params['showFiles'] = showFiles;
|
||||
params['matching'] = matching;
|
||||
params['wordsThreshold'] = wordsThreshold;
|
||||
params['matchingMaxComparisons'] = matchingMaxComparisons;
|
||||
} else {
|
||||
params['outputFormat'] = params['outputFormat'] || outputFormat;
|
||||
params['showFiles'] = String(params['showFiles']) !== 'false' || (params['showFiles'] === null && showFiles);
|
||||
params['matching'] = params['matching'] || matching;
|
||||
params['wordsThreshold'] = params['wordsThreshold'] || wordsThreshold;
|
||||
params['matchingMaxComparisons'] = params['matchingMaxComparisons'] || matchingMaxComparisons;
|
||||
|
||||
$outputFormat.val(params['outputFormat']);
|
||||
$showFiles.prop('checked', params['showFiles']);
|
||||
$matching.val(params['matching']);
|
||||
$wordsThreshold.val(params['wordsThreshold']);
|
||||
$matchingMaxComparisons.val(params['matchingMaxComparisons']);
|
||||
}
|
||||
|
||||
params['synchronisedScroll'] = params['synchronisedScroll'] || true;
|
||||
|
||||
diff2htmlUi.draw(container, params);
|
||||
diff2htmlUi.fileListCloseable(container, params['fileListCloseable'] || false);
|
||||
if (params['highlight'] === undefined || params['highlight']) {
|
||||
diff2htmlUi.highlightCode(container);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function validateUrl(url) {
|
||||
return /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(url);
|
||||
}
|
||||
|
||||
function updateUrl(url) {
|
||||
var params = getParamsFromSearch(window.location.search);
|
||||
|
||||
if (params[searchParam] === url) return;
|
||||
|
||||
params[searchParam] = url;
|
||||
|
||||
var paramString = Object.keys(params).map(function(k) { return k + '=' + params[k]; }).join('&');
|
||||
|
||||
window.location = 'demo.html?' + paramString;
|
||||
}
|
||||
});
|
||||
|
||||
},{"whatwg-fetch":1}]},{},[2]);
|
||||
1
docs/demo.min.js
vendored
BIN
docs/favicon.ico
|
Before Width: | Height: | Size: 4.6 KiB |
433
docs/index.html
|
|
@ -1,433 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" class="js">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<!--[if IE]>
|
||||
<meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'/>
|
||||
<![endif]-->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<meta name="description" content="Diff parser and pretty html generator">
|
||||
<meta name="keywords" content="diff2html,git,diff,unified,pretty,html,css,javaccript">
|
||||
<meta name="author" content="Rodrigo Fernandes (rtfpessoa)">
|
||||
|
||||
<title>diff2html</title>
|
||||
|
||||
<!-- search engine -->
|
||||
<link rel="canonical" href="https://diff2html.xyz">
|
||||
|
||||
<!-- open graph -->
|
||||
<meta property="og:title" content="diff2html">
|
||||
<meta property="og:type" content="website">
|
||||
|
||||
<meta property="og:description"
|
||||
content="Diff parser and pretty html generator.">
|
||||
|
||||
<meta property="og:url" content="https://diff2html.xyz">
|
||||
<meta property="og:site_name" content="diff2html">
|
||||
|
||||
<!-- Bootstrap -->
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"
|
||||
integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
|
||||
|
||||
<!-- Custom styles for this template -->
|
||||
<link href="main.min.css" rel="stylesheet">
|
||||
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-78351861-2', 'auto');
|
||||
ga('send', 'pageview');
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body class="template-index ">
|
||||
<div class="swag-line">
|
||||
|
||||
<div class="container">
|
||||
<nav class="navbar navbar-default navbar-tall navbar-full" role="navigation">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#global-nav">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="index.html">diff2html</a>
|
||||
</div>
|
||||
|
||||
<div class="collapse navbar-collapse" id="global-nav">
|
||||
<div class="navbar-right">
|
||||
<ul class="nav navbar-nav">
|
||||
|
||||
<li>
|
||||
<a href="index.html#install">Getting Started</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="index.html#cli">CLI</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="demo.html?diff=https://github.com/rtfpessoa/diff2html/pull/106">Demo</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="https://github.com/rtfpessoa/diff2html#how-to-use" target="_blank">Docs</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="https://github.com/rtfpessoa/diff2html/issues/new" target="_blank">Support</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="hero hero-homepage">
|
||||
<span class="hero-booticon">
|
||||
<span class="hero-green">diff</span><span class="hero-black">2</span><span
|
||||
class="hero-red">html</span>
|
||||
</span>
|
||||
<h1 class="hero-header">Diff parser and pretty html generator</h1>
|
||||
<h4 class="text-muted">Better diffs, unmatched reviews.</h4>
|
||||
<h2><a class="btn btn-lg" href="demo.html?diff=https://github.com/rtfpessoa/diff2html/pull/106">Demo</a></h2>
|
||||
|
||||
<div class="screenshots screenshots-fan clearfix">
|
||||
|
||||
<img class="screenshot hidden-xs" src="img/snapshot-2.png">
|
||||
|
||||
<a class="screenshot" href="demo.html?diff=https://github.com/rtfpessoa/diff2html/pull/106">
|
||||
<img src="img/snapshot-3.png">
|
||||
</a>
|
||||
|
||||
<a class="screenshot hidden-xs" href="demo.html?diff=https://github.com/rtfpessoa/diff2html/pull/106">
|
||||
<img src="img/snapshot-1.png">
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="row row-padded-small row-bordered">
|
||||
<div class="col-sm-8 col-sm-offset-2 text-center">
|
||||
<h2 class="m-b-md">Each diff provides a comprehensive visualization of the code changes,
|
||||
helping developers identify problems and better understand the changes.</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row row-padded-small homepage-grid row-bordered p-t text-center">
|
||||
<div class="col-sm-4">
|
||||
<span class="svg-icon-large">
|
||||
<svg aria-hidden="true" class="octicon octicon-diff" height="16" version="1.1"
|
||||
viewBox="0 0 14 16" width="14"><path
|
||||
d="M6 7h2v1H6v2h-1V8H3v-1h2V5h1v2zM3 13h5v-1H3v1z m4.5-11l3.5 3.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V3c0-0.55 0.45-1 1-1h6.5z m2.5 4L7 3H1v12h9V6zM8.5 0S3 0 3 0v1h5l4 4v8h1V4.5L8.5 0z"></path></svg>
|
||||
</span>
|
||||
<h5><strong>Line by Line and Side by Side changes</strong></h5>
|
||||
<p class="text-muted">Each diff features a line by line and side by side preview of your
|
||||
changes.</p>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-4">
|
||||
<span class="svg-icon-large">
|
||||
<svg aria-hidden="true" class="octicon octicon-tasklist" height="16" version="1.1"
|
||||
viewBox="0 0 16 16" width="16"><path
|
||||
d="M15.41 9H7.59c-0.59 0-0.59-0.41-0.59-1s0-1 0.59-1h7.81c0.59 0 0.59 0.41 0.59 1s0 1-0.59 1zM9.59 4c-0.59 0-0.59-0.41-0.59-1s0-1 0.59-1h5.81c0.59 0 0.59 0.41 0.59 1s0 1-0.59 1H9.59zM0 3.91l1.41-1.3 1.59 1.59L7.09 0l1.41 1.41-5.5 5.5L0 3.91z m7.59 8.09h7.81c0.59 0 0.59 0.41 0.59 1s0 1-0.59 1H7.59c-0.59 0-0.59-0.41-0.59-1s0-1 0.59-1z"></path></svg>
|
||||
</span>
|
||||
<h5><strong>Code syntax highlight</strong></h5>
|
||||
<p class="text-muted">All the code changes are syntax highlighted using <a
|
||||
href="https://highlightjs.org/">highlight.js</a>,
|
||||
providing more readability.</p>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-4">
|
||||
<span class="svg-icon-large">
|
||||
<svg aria-hidden="true" class="octicon octicon-clippy" height="16" version="1.1"
|
||||
viewBox="0 0 14 16"
|
||||
width="14">
|
||||
<path d="M2 12h4v1H2v-1z m5-6H2v1h5v-1z m2 3V7L6 10l3 3V11h5V9H9z m-4.5-1H2v1h2.5v-1zM2 11h2.5v-1H2v1z m9 1h1v2c-0.02 0.28-0.11 0.52-0.3 0.7s-0.42 0.28-0.7 0.3H1c-0.55 0-1-0.45-1-1V3c0-0.55 0.45-1 1-1h3C4 0.89 4.89 0 6 0s2 0.89 2 2h3c0.55 0 1 0.45 1 1v5h-1V5H1v9h10V12zM2 4h8c0-0.55-0.45-1-1-1h-1c-0.55 0-1-0.45-1-1s-0.45-1-1-1-1 0.45-1 1-0.45 1-1 1h-1c-0.55 0-1 0.45-1 1z"></path>
|
||||
</svg>
|
||||
</span>
|
||||
<h5><strong>Line similarity matching</strong></h5>
|
||||
<p class="text-muted">Similar lines are paired, allowing for easier change tracking.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="install" class="row-padded-small row-centered row-bordered">
|
||||
<div class="col-md-12">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3><strong>Install with Bower</strong></h3>
|
||||
<p>You can install and manage diff2html's CSS and JS using Bower:</p>
|
||||
<div class="homepage-code-example">
|
||||
<p><span class="unselectable">> $ </span><span class="text-muted">bower install diff2html</span></p>
|
||||
<span class="btn-clipboard" data-clipboard-text="bower install diff2html" title="Copy">Copy</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3><strong>Install with npm</strong></h3>
|
||||
<p>You can also install diff2html using npm:</p>
|
||||
<div class="homepage-code-example">
|
||||
<p><span class="unselectable">> $ </span><span class="text-muted">npm install diff2html</span></p>
|
||||
<span class="btn-clipboard" data-clipboard-text="npm install diff2html" title="Copy">Copy</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row row-padded-small">
|
||||
<div class="col-md-12">
|
||||
<a href="https://github.com/rtfpessoa/diff2html#how-to-use" target="_blank">
|
||||
Find usage examples in the Docs
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="cli" class="row row-padded-small row-centered row-bordered">
|
||||
<div class="col-xs-10 col-xs-offset-1 col-md-6 col-md-offset-0">
|
||||
<h3><strong>With command line integration</strong></h3>
|
||||
<h4 class="m-b-md">We work hard to make sure you can have your diffs in a simple and flexible
|
||||
way. Go <a href="https://github.com/rtfpessoa/diff2html-cli" target="_blank">here full
|
||||
documentation</a>.</h4>
|
||||
</div>
|
||||
<div class="col-xs-10 col-xs-offset-1 col-md-6 col-md-offset-0">
|
||||
<div class="homepage-terminal-example">
|
||||
<p class="m-b-md">
|
||||
<span class="unselectable">> $ </span><span class="text-muted">npm install -g diff2html-cli</span><br>
|
||||
<span class="unselectable">diff2html cli installed!</span>
|
||||
</p>
|
||||
<p class="m-b-md">
|
||||
<span class="unselectable">> $ </span><span class="text-muted">diff2html</span><br>
|
||||
<span class="unselectable">Previous commit changes on your browser</span>
|
||||
</p>
|
||||
<p>
|
||||
<span class="unselectable">> $ <span class="text-muted">is that it?</span><br>
|
||||
Yup, it's that simple.</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="users" class="row row-padded-small row-centered row-bordered">
|
||||
<div class="col-md-12">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3><strong>Projects using diff2html</strong></h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row row-eq-height">
|
||||
<div class="col-xs-12 col-sm-6 col-md-3 m-b-lg">
|
||||
<div class="panel panel-default panel-profile m-b-0">
|
||||
<div class="panel-body text-center">
|
||||
<h5 class="panel-title">diff2html-cli</h5>
|
||||
<p class="m-b">diff2html from your terminal to the browser.</p>
|
||||
<a href="https://github.com/rtfpessoa/diff2html-cli" target="_blank"
|
||||
class="btn btn-primary-outline btn-sm m-b">
|
||||
<span class="icon icon-add-user"></span> View GitHub
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-6 col-md-3 m-b-lg">
|
||||
<div class="panel panel-default panel-profile m-b-0">
|
||||
<div class="panel-body text-center">
|
||||
<h5 class="panel-title">Codacy</h5>
|
||||
<p class="m-b">Check code style, security, duplication, complexity and coverage on every change.</p>
|
||||
<a href="https://www.codacy.com" target="_blank"
|
||||
class="btn btn-primary-outline btn-sm m-b">
|
||||
<span class="icon icon-add-user"></span> Website
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-6 col-md-3 m-b-lg">
|
||||
<div class="panel panel-default panel-profile m-b-0">
|
||||
<div class="panel-body text-center">
|
||||
<h5 class="panel-title">Ungit</h5>
|
||||
<p class="m-b">The easiest way to use git. On any platform. Anywhere.</p>
|
||||
<a href="https://github.com/FredrikNoren/ungit" target="_blank"
|
||||
class="btn btn-primary-outline btn-sm m-b">
|
||||
<span class="icon icon-add-user"></span> View GitHub
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-6 col-md-3 m-b-lg">
|
||||
<div class="panel panel-default panel-profile m-b-0">
|
||||
<div class="panel-body text-center">
|
||||
<h5 class="panel-title">Diffy</h5>
|
||||
<p class="m-b">Share your diffs and explain your ideas without committing.</p>
|
||||
<a href="https://diffy.org/" target="_blank"
|
||||
class="btn btn-primary-outline btn-sm m-b">
|
||||
<span class="icon icon-add-user"></span> Website
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-6 col-md-3 m-b-lg">
|
||||
<div class="panel panel-default panel-profile m-b-0">
|
||||
<div class="panel-body text-center">
|
||||
<h5 class="panel-title">diff-pane</h5>
|
||||
<p class="m-b">Atom - Diff two panes.</p>
|
||||
<a href="https://github.com/t-ari/diff-pane" target="_blank"
|
||||
class="btn btn-primary-outline btn-sm m-b">
|
||||
<span class="icon icon-add-user"></span> View GitHub
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-6 col-md-3 m-b-lg">
|
||||
<div class="panel panel-default panel-profile m-b-0">
|
||||
<div class="panel-body text-center">
|
||||
<h5 class="panel-title">node-giff</h5>
|
||||
<p class="m-b">Display git diff on browser.</p>
|
||||
<a href="https://github.com/do7be/node-giff" target="_blank"
|
||||
class="btn btn-primary-outline btn-sm m-b">
|
||||
<span class="icon icon-add-user"></span> View GitHub
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-6 col-md-3 m-b-lg">
|
||||
<div class="panel panel-default panel-profile m-b-0">
|
||||
<div class="panel-body text-center">
|
||||
<h5 class="panel-title">edgar-monitor</h5>
|
||||
<p class="m-b">A module that processes new Edgar filings and sends out
|
||||
notifications.</p>
|
||||
<a href="https://github.com/buzzfeed-openlab/edgar-monitor" target="_blank"
|
||||
class="btn btn-primary-outline btn-sm m-b">
|
||||
<span class="icon icon-add-user"></span> View GitHub
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-6 col-md-3 m-b-lg">
|
||||
<div class="panel panel-default panel-profile m-b-0">
|
||||
<div class="panel-body text-center">
|
||||
<h5 class="panel-title">node-git</h5>
|
||||
<p class="m-b">Execute Git Command by Node.js.</p>
|
||||
<a href="https://github.com/liangshuai/node-git" target="_blank"
|
||||
class="btn btn-primary-outline btn-sm m-b">
|
||||
<span class="icon icon-add-user"></span> View GitHub
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-6 col-md-3 m-b-lg">
|
||||
<div class="panel panel-default panel-profile m-b-0">
|
||||
<div class="panel-body text-center">
|
||||
<h5 class="panel-title">Jenkins</h5>
|
||||
<p class="m-b">Show diffs between builds</p>
|
||||
<a href="https://wiki.jenkins-ci.org/display/JENKINS/Last+Changes+Plugin" target="_blank"
|
||||
class="btn btn-primary-outline btn-sm m-b">
|
||||
<span class="icon icon-add-user"></span> Website
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-6 col-md-3 m-b-lg">
|
||||
<div class="panel panel-default panel-profile m-b-0">
|
||||
<div class="panel-body text-center">
|
||||
<h5 class="panel-title">Light Review</h5>
|
||||
<p class="m-b">Code Reviews with maximum control for the leading developers</p>
|
||||
<a href="http://light-review.com/" target="_blank"
|
||||
class="btn btn-primary-outline btn-sm m-b">
|
||||
<span class="icon icon-add-user"></span> Website
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-6 col-md-3 m-b-lg">
|
||||
<div class="panel panel-default panel-profile m-b-0">
|
||||
<div class="panel-body text-center">
|
||||
<h5 class="panel-title">Simple Git</h5>
|
||||
<p class="m-b">A simple package to be able to drive GIT</p>
|
||||
<a href="https://github.com/mauricioszabo/simple-git" target="_blank"
|
||||
class="btn btn-primary-outline btn-sm m-b">
|
||||
<span class="icon icon-add-user"></span> View GitHub
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row row-padded-small text-center">
|
||||
<div class="col-sm-8 col-sm-offset-2 text-center">
|
||||
<h3><strong>Open Source</strong></h3>
|
||||
<h4 class="m-b-md">diff2html is open source.
|
||||
If you'd like to be part of the diff2html community or help us improve,
|
||||
find us on <a href="https://github.com/rtfpessoa/diff2html" target="_blank">GitHub</a> and
|
||||
<a href="https://gitter.im/rtfpessoa/diff2html" target="_blank">Gitter</a>. Need any help?
|
||||
</h4>
|
||||
<a class="btn btn-md" href="https://github.com/rtfpessoa/diff2html#how-to-use" target="_blank">
|
||||
Read more in the Docs
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<footer class="footer clearfix">
|
||||
<p class="col-xs-10 col-xs-offset-1">
|
||||
Website originally designed and built by
|
||||
<a href="https://twitter.com/mdo" target="_blank">@mdo</a>,
|
||||
<a href="https://twitter.com/fat" target="_blank">@fat</a>, and
|
||||
<a href="https://twitter.com/dhg" target="_blank">@dhg</a>,
|
||||
adapted with <span class="hero-red">❤</span> by
|
||||
<a href="https://twitter.com/rtfpessoa" target="_blank">@rtfpessoa</a>.
|
||||
</p>
|
||||
<ul class="footer-list col-xs-10 col-xs-offset-1">
|
||||
|
||||
<li class="footer-list-item">
|
||||
<a class="footer-list-link" href="https://github.com/rtfpessoa/diff2html#how-to-use"
|
||||
target="_blank">FAQ</a>
|
||||
</li>
|
||||
|
||||
<li class="footer-list-item">
|
||||
<a class="footer-list-link" href="https://diff2html.xyz">diff2html</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- General JavaScript -->
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.3/jquery.min.js"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"
|
||||
integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS"
|
||||
crossorigin="anonymous"></script>
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "http://schema.org/",
|
||||
"@type": "SoftwareSourceCode",
|
||||
"name": "diff2html",
|
||||
"author": "Rodrigo Fernandes",
|
||||
"image": "https://diff2html.xyz/img/snapshot-3.png",
|
||||
"description": "Diff parser and pretty html generator.",
|
||||
"codeRepository": "https://github.com/rtfpessoa/diff2html",
|
||||
"programmingLanguage": "JavaScript",
|
||||
"runtimePlatform": "Node >= 0.12",
|
||||
"mainEntityOfPage": "https://diff2html.xyz/"
|
||||
}
|
||||
</script>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/1.5.10/clipboard.min.js"></script>
|
||||
|
||||
<script>
|
||||
new Clipboard(document.getElementsByClassName("btn-clipboard"));
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
564
docs/main.css
4
docs/main.min.css
vendored
|
|
@ -1 +0,0 @@
|
|||
User-agent: *
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset
|
||||
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
|
||||
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
|
||||
<url>
|
||||
<loc>https://diff2html.xyz/</loc>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://diff2html.xyz/index.html</loc>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://diff2html.xyz/demo.html</loc>
|
||||
</url>
|
||||
</urlset>
|
||||
15
jest.config.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
module.exports = {
|
||||
verbose: true,
|
||||
preset: "ts-jest",
|
||||
testEnvironment: "node",
|
||||
coverageDirectory: "./coverage",
|
||||
coverageReporters: ["lcov", "text", "html"],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
statements: 90,
|
||||
branches: 85,
|
||||
functions: 90,
|
||||
lines: 90
|
||||
}
|
||||
}
|
||||
};
|
||||
119
package.json
|
|
@ -32,56 +32,101 @@
|
|||
"url": "https://www.github.com/rtfpessoa/diff2html/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
"node": "8.* || >=10"
|
||||
},
|
||||
"preferGlobal": true,
|
||||
"scripts": {
|
||||
"release": "./scripts/release.sh",
|
||||
"release-website": "./scripts/release-website.sh",
|
||||
"templates": "./scripts/hulk.js --wrapper node --variable 'browserTemplates' ./src/templates/*.mustache > ./src/templates/diff2html-templates.js",
|
||||
"lint": "eslint '*/**/*.{js,jsx,ts,tsx}'",
|
||||
"style": "yarn run lint",
|
||||
"lint": "eslint .",
|
||||
"coverage": "istanbul cover _mocha -- -u exports -R spec ./test/**/*",
|
||||
"check-coverage": "istanbul check-coverage --statements 90 --functions 90 --branches 85 --lines 90 ./coverage/coverage.json",
|
||||
"test": "yarn run coverage && yarn run check-coverage",
|
||||
"test": "yarn run build-templates && jest",
|
||||
"coverage": "jest --collectCoverage",
|
||||
"coverage-html": "yarn run coverage && open ./coverage/index.html",
|
||||
"codacy": "cat ./coverage/lcov.info | codacy-coverage",
|
||||
"preversion": "yarn run release && yarn run release-website && yarn run lint && yarn test",
|
||||
"version": "git add -A src dist docs package.json",
|
||||
"build": "yarn run build-css && yarn run build-templates && yarn run build-es5 && yarn run build-esm && yarn run build-bundles && yarn run build-website",
|
||||
"build-es5": "rm -rf lib; tsc -p tsconfig.json --outDir lib",
|
||||
"build-esm": "rm -rf lib-esm; tsc -p tsconfig.json -m es6 --outDir lib-esm",
|
||||
"build-bundles": "rm -rf ./bundles/js; NODE_ENV=production WEBPACK_MINIMIZE=true webpack --mode production --config webpack.bundles.ts",
|
||||
"build-css": "rm -rf ./bundles/css; postcss --use autoprefixer postcss-import postcss-preset-env cssnano -o ./bundles/css/diff2html.min.css ./src/ui/css/diff2html.css",
|
||||
"build-templates": "ts-node ./scripts/hulk.ts --wrapper ts --variable 'defaultTemplates' ./src/templates/*.mustache > ./src/diff2html-templates.ts",
|
||||
"build-website": "rm -rf docs; NODE_ENV=production WEBPACK_MINIMIZE=true webpack --mode production --config webpack.website.ts",
|
||||
"start-website": "WEBPACK_MINIFY=false NODE_ENV=dev webpack-dev-server --mode dev --config webpack.website.ts",
|
||||
"preversion": "yarn run build && yarn run lint && yarn test",
|
||||
"version": "git add -A package.json",
|
||||
"postversion": "git push && git push --tags"
|
||||
},
|
||||
"main": "./src/diff2html.js",
|
||||
"browser": {
|
||||
"fs": false
|
||||
},
|
||||
"main": "./lib/diff2html.js",
|
||||
"dependencies": {
|
||||
"diff": "^4.0.1",
|
||||
"hogan.js": "^3.0.2",
|
||||
"merge": "^1.2.1",
|
||||
"whatwg-fetch": "^3.0.0"
|
||||
"diff": "4.0.1",
|
||||
"hogan.js": "3.0.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"highlight.js": "9.16.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^9.6.0",
|
||||
"browserify": "^16.2.3",
|
||||
"clean-css-cli": "^4.3.0",
|
||||
"codacy-coverage": "^3.4.0",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-plugin-promise": "^4.1.1",
|
||||
"eslint-plugin-standard": "^4.0.0",
|
||||
"fast-html-parser": "^1.0.1",
|
||||
"istanbul": "^0.4.5",
|
||||
"mkdirp": "^0.5.1",
|
||||
"mocha": "5.2.0",
|
||||
"nopt": "^4.0.1",
|
||||
"postcss-cli": "^6.1.2",
|
||||
"uglify-js": "^3.6.0"
|
||||
"@types/autoprefixer": "9.6.1",
|
||||
"@types/clipboard": "2.0.1",
|
||||
"@types/copy-webpack-plugin": "5.0.0",
|
||||
"@types/diff": "4.0.2",
|
||||
"@types/highlight.js": "9.12.3",
|
||||
"@types/hogan.js": "3.0.0",
|
||||
"@types/html-webpack-plugin": "3.2.1",
|
||||
"@types/jest": "24.0.23",
|
||||
"@types/mini-css-extract-plugin": "0.8.0",
|
||||
"@types/mkdirp": "0.5.2",
|
||||
"@types/node": "12.12.12",
|
||||
"@types/nopt": "3.0.29",
|
||||
"@types/webpack": "4.41.0",
|
||||
"@typescript-eslint/eslint-plugin": "2.8.0",
|
||||
"@typescript-eslint/parser": "2.8.0",
|
||||
"autoprefixer": "9.7.2",
|
||||
"bootstrap": "3.4.1",
|
||||
"clipboard": "2.0.4",
|
||||
"codacy-coverage": "3.4.0",
|
||||
"copy-webpack-plugin": "5.0.5",
|
||||
"css-loader": "3.2.0",
|
||||
"cssnano": "4.1.10",
|
||||
"eslint": "6.7.0",
|
||||
"eslint-config-prettier": "6.7.0",
|
||||
"eslint-config-standard": "14.1.0",
|
||||
"eslint-plugin-import": "2.18.2",
|
||||
"eslint-plugin-jest": "23.0.4",
|
||||
"eslint-plugin-node": "10.0.0",
|
||||
"eslint-plugin-prettier": "3.1.1",
|
||||
"eslint-plugin-promise": "4.2.1",
|
||||
"eslint-plugin-standard": "4.0.1",
|
||||
"fast-html-parser": "1.0.1",
|
||||
"file-loader": "4.3.0",
|
||||
"handlebars": "4.5.3",
|
||||
"handlebars-loader": "1.7.1",
|
||||
"html-webpack-plugin": "3.2.0",
|
||||
"image-webpack-loader": "6.0.0",
|
||||
"jest": "24.9.0",
|
||||
"mini-css-extract-plugin": "0.8.0",
|
||||
"mkdirp": "0.5.1",
|
||||
"nopt": "4.0.1",
|
||||
"postcss-cli": "6.1.3",
|
||||
"postcss-import": "12.0.1",
|
||||
"postcss-loader": "3.0.0",
|
||||
"postcss-normalize": "8.0.1",
|
||||
"postcss-preset-env": "6.7.0",
|
||||
"prettier": "1.19.1",
|
||||
"style-loader": "1.0.0",
|
||||
"ts-jest": "24.2.0",
|
||||
"ts-loader": "6.2.1",
|
||||
"ts-node": "8.5.2",
|
||||
"typescript": "3.7.2",
|
||||
"url-loader": "2.3.0",
|
||||
"webpack": "4.41.2",
|
||||
"webpack-cli": "3.3.10",
|
||||
"webpack-dev-server": "3.9.0",
|
||||
"whatwg-fetch": "3.0.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"lodash": "4.17.14"
|
||||
"lodash": "4.17.15"
|
||||
},
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"src",
|
||||
"dist",
|
||||
"typescript"
|
||||
"bundles",
|
||||
"lib",
|
||||
"lib-esm"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
193
scripts/hulk.js
|
|
@ -1,193 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/*
|
||||
* Copyright 2011 Twitter, Inc.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// dependencies
|
||||
var hogan = require('hogan.js');
|
||||
var path = require('path');
|
||||
var nopt = require('nopt');
|
||||
var mkderp = require('mkdirp');
|
||||
var fs = require('fs');
|
||||
|
||||
// locals
|
||||
var specials = ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\'];
|
||||
var specialsRegExp = new RegExp('(\\' + specials.join('|\\') + ')', 'g');
|
||||
var options = {
|
||||
'namespace': String,
|
||||
'outputdir': path,
|
||||
'variable': String,
|
||||
'wrapper': String,
|
||||
'version': true,
|
||||
'help': true
|
||||
};
|
||||
var shortHand = {
|
||||
'n': ['--namespace'],
|
||||
'o': ['--outputdir'],
|
||||
'vn': ['--variable'],
|
||||
'w': ['--wrapper'],
|
||||
'h': ['--help'],
|
||||
'v': ['--version']
|
||||
};
|
||||
var templates;
|
||||
|
||||
// options
|
||||
options = nopt(options, shortHand);
|
||||
|
||||
// escape special regexp characters
|
||||
function esc(text) {
|
||||
return text.replace(specialsRegExp, '\\$1');
|
||||
}
|
||||
|
||||
// cyan function for rob
|
||||
function cyan(text) {
|
||||
return '\x1B[36m' + text + '\x1B[39m';
|
||||
}
|
||||
|
||||
// check for dirs and correct ext (<3 for windows)
|
||||
function extractFiles(args) {
|
||||
var usage = '\n' +
|
||||
cyan('USAGE:') + ' hulk [--wrapper wrapper] [--outputdir outputdir] ' +
|
||||
'[--namespace namespace] [--variable variable] FILES\n\n' +
|
||||
cyan('OPTIONS:') + ' [-w, --wrapper] :: wraps the template (i.e. amd)\n' +
|
||||
' [-o, --outputdir] :: outputs the templates as individual files to a directory\n\n' +
|
||||
' [-n, --namespace] :: prepend string to template names\n\n' +
|
||||
' [-vn, --variable] :: variable name for non-amd wrapper\n\n' +
|
||||
cyan('EXAMPLE:') + ' hulk --wrapper amd ./templates/*.mustache\n\n' +
|
||||
cyan('NOTE:') + ' hulk supports the "*" wildcard and allows you to target specific extensions too\n';
|
||||
var files = [];
|
||||
|
||||
if (options.version) {
|
||||
console.log(require('../package.json').version);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (!args.length || options.help) {
|
||||
console.log(usage);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
args.forEach(function(arg) {
|
||||
if (/\*/.test(arg)) {
|
||||
arg = arg.split('*');
|
||||
files = files.concat(
|
||||
fs.readdirSync(arg[0] || '.')
|
||||
.map(function(f) {
|
||||
var file = path.join(arg[0], f);
|
||||
return new RegExp(esc(arg[1]) + '$').test(f) && fs.statSync(file).isFile() && file;
|
||||
})
|
||||
.filter(function(f) {
|
||||
return f;
|
||||
})
|
||||
);
|
||||
return files;
|
||||
}
|
||||
|
||||
if (fs.statSync(arg).isFile()) files.push(arg);
|
||||
});
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
// remove utf-8 byte order mark, http://en.wikipedia.org/wiki/Byte_order_mark
|
||||
function removeByteOrderMark(text) {
|
||||
if (text.charCodeAt(0) === 0xfeff) {
|
||||
return text.substring(1);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
// wrap templates
|
||||
function wrap(file, name, openedFile) {
|
||||
switch (options.wrapper) {
|
||||
case 'amd':
|
||||
return 'define(' + (!options.outputdir ? '"' + path.join(path.dirname(file), name) + '", ' : '') +
|
||||
'[ "hogan.js" ], function(Hogan){ return new Hogan.Template(' +
|
||||
hogan.compile(openedFile, {asString: 1}) +
|
||||
');});';
|
||||
case 'node':
|
||||
var globalObj = 'global.' + (options.variable || 'templates') + '["' + name + '"]';
|
||||
var globalStmt = globalObj + ' = new Hogan.Template(' + hogan.compile(openedFile, {asString: 1}) + ');';
|
||||
var nodeOutput = globalStmt;
|
||||
|
||||
// if we have a template per file the export will expose the template directly
|
||||
if (options.outputdir) {
|
||||
nodeOutput = nodeOutput + '\n' + 'module.exports = ' + globalObj + ';';
|
||||
}
|
||||
|
||||
return nodeOutput;
|
||||
default:
|
||||
return (options.variable || 'templates') +
|
||||
'["' + name + '"] = new Hogan.Template(' +
|
||||
hogan.compile(openedFile, {asString: 1}) +
|
||||
');';
|
||||
}
|
||||
}
|
||||
|
||||
function prepareOutput(content) {
|
||||
var variableName = options.variable || 'templates';
|
||||
switch (options.wrapper) {
|
||||
case 'amd':
|
||||
return content;
|
||||
case 'node':
|
||||
var nodeExport = '';
|
||||
|
||||
// if we have aggregated templates the export will expose the template map
|
||||
if (!options.outputdir) {
|
||||
nodeExport = 'module.exports = global.' + variableName + ';\n';
|
||||
}
|
||||
|
||||
return '(function() {\n' +
|
||||
'if (!!!global.' + variableName + ') global.' + variableName + ' = {};\n' +
|
||||
'var Hogan = require("hogan.js");' +
|
||||
content + '\n' +
|
||||
nodeExport +
|
||||
'})();';
|
||||
default:
|
||||
return 'if (!!!' + variableName + ') var ' + variableName + ' = {};\n' + content;
|
||||
}
|
||||
}
|
||||
|
||||
// write the directory
|
||||
if (options.outputdir) {
|
||||
mkderp.sync(options.outputdir);
|
||||
}
|
||||
|
||||
// Prepend namespace to template name
|
||||
function namespace(name) {
|
||||
return (options.namespace || '') + name;
|
||||
}
|
||||
|
||||
// write a template foreach file that matches template extension
|
||||
templates = extractFiles(options.argv.remain)
|
||||
.map(function(file) {
|
||||
var openedFile = fs.readFileSync(file, 'utf-8').trim();
|
||||
var name;
|
||||
if (!openedFile) return;
|
||||
name = namespace(path.basename(file).replace(/\..*$/, ''));
|
||||
openedFile = removeByteOrderMark(openedFile);
|
||||
openedFile = wrap(file, name, openedFile);
|
||||
if (!options.outputdir) return openedFile;
|
||||
fs.writeFileSync(path.join(options.outputdir, name + '.js')
|
||||
, prepareOutput(openedFile));
|
||||
})
|
||||
.filter(function(t) {
|
||||
return t;
|
||||
});
|
||||
|
||||
// output templates
|
||||
if (!templates.length || options.outputdir) process.exit(0);
|
||||
|
||||
console.log(prepareOutput(templates.join('\n')));
|
||||
193
scripts/hulk.ts
Executable file
|
|
@ -0,0 +1,193 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/*
|
||||
* Copyright 2011 Twitter, Inc.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as path from "path";
|
||||
import * as fs from "fs";
|
||||
|
||||
import * as hogan from "hogan.js";
|
||||
import nopt from "nopt";
|
||||
import * as mkderp from "mkdirp";
|
||||
|
||||
const options = nopt(
|
||||
{
|
||||
namespace: String,
|
||||
outputdir: path,
|
||||
variable: String,
|
||||
wrapper: String,
|
||||
version: true,
|
||||
help: true
|
||||
},
|
||||
{
|
||||
n: ["--namespace"],
|
||||
o: ["--outputdir"],
|
||||
vn: ["--variable"],
|
||||
w: ["--wrapper"],
|
||||
h: ["--help"],
|
||||
v: ["--version"]
|
||||
}
|
||||
);
|
||||
|
||||
const specials = ["/", ".", "*", "+", "?", "|", "(", ")", "[", "]", "{", "}", "\\"];
|
||||
const specialsRegExp = new RegExp("(\\" + specials.join("|\\") + ")", "g");
|
||||
function escape(text: string): string {
|
||||
return text.replace(specialsRegExp, "\\$1");
|
||||
}
|
||||
|
||||
function cyan(text: string): string {
|
||||
return "\x1B[36m" + text + "\x1B[39m";
|
||||
}
|
||||
|
||||
function extractFiles(files: string[]): string[] {
|
||||
const usage = `${cyan(
|
||||
"USAGE:"
|
||||
)} hulk [--wrapper wrapper] [--outputdir outputdir] [--namespace namespace] [--variable variable] FILES
|
||||
|
||||
${cyan("OPTIONS:")} [-w, --wrapper] :: wraps the template (i.e. amd)
|
||||
[-o, --outputdir] :: outputs the templates as individual files to a directory
|
||||
|
||||
[-n, --namespace] :: prepend string to template names
|
||||
|
||||
[-vn, --variable] :: variable name for non-amd wrapper
|
||||
|
||||
${cyan("EXAMPLE:")} hulk --wrapper amd ./templates/*.mustache
|
||||
|
||||
${cyan("NOTE:")} hulk supports the "*" wildcard and allows you to target specific extensions too
|
||||
`;
|
||||
|
||||
if (options.version) {
|
||||
console.log(require("../package.json").version);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (!files.length || options.help) {
|
||||
console.log(usage);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const templateFiles = files
|
||||
.map((fileGlob: string) => {
|
||||
if (/\*/.test(fileGlob)) {
|
||||
const [fileGlobPrefix, fileGlobSuffix] = fileGlob.split("*");
|
||||
|
||||
const files = fs.readdirSync(fileGlobPrefix || ".").reduce<string[]>((previousFiles, relativeFilePath) => {
|
||||
const file = path.join(fileGlobPrefix, relativeFilePath);
|
||||
if (new RegExp(`${escape(fileGlobSuffix)}$`).test(relativeFilePath) && fs.statSync(file).isFile()) {
|
||||
previousFiles.push(file);
|
||||
}
|
||||
return previousFiles;
|
||||
}, []);
|
||||
|
||||
return files;
|
||||
} else if (fs.statSync(fileGlob).isFile()) {
|
||||
return [fileGlob];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
})
|
||||
.reduce((previous, current) => previous.concat(current), []);
|
||||
|
||||
return templateFiles;
|
||||
}
|
||||
|
||||
// Remove utf-8 byte order mark, http://en.wikipedia.org/wiki/Byte_order_mark
|
||||
function removeByteOrderMark(text: string): string {
|
||||
if (text.charCodeAt(0) === 0xfeff) {
|
||||
return text.substring(1);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
// Wrap templates
|
||||
function wrap(file: string, name: string, openedFile: string): string {
|
||||
const hoganTemplateString = `new Hogan.Template(${hogan.compile(openedFile, { asString: true })})`;
|
||||
|
||||
const objectName = options.variable || "templates";
|
||||
const objectAccessor = `${objectName}["${name}"]`;
|
||||
const objectStmt = `${objectAccessor} = ${hoganTemplateString};`;
|
||||
|
||||
switch (options.wrapper) {
|
||||
case "amd":
|
||||
return `define(${
|
||||
!options.outputdir ? `"${path.join(path.dirname(file), name)}", ` : ""
|
||||
}["hogan.js"], function(Hogan) { return ${hoganTemplateString}; });`;
|
||||
|
||||
case "node":
|
||||
// If we have a template per file the export will expose the template directly
|
||||
return options.outputdir ? `global.${objectStmt};\nmodule.exports = ${objectAccessor};` : `global.${objectStmt}`;
|
||||
|
||||
case "ts":
|
||||
return `// @ts-ignore\n${objectStmt}`;
|
||||
default:
|
||||
return objectStmt;
|
||||
}
|
||||
}
|
||||
|
||||
function prepareOutput(content: string): string {
|
||||
const variableName = options.variable || "templates";
|
||||
switch (options.wrapper) {
|
||||
case "amd":
|
||||
return content;
|
||||
case "node":
|
||||
return `(function() {
|
||||
if (!!!global.${variableName}) global.${variableName} = {};
|
||||
var Hogan = require("hogan.js");
|
||||
${content}
|
||||
${!options.outputdir ? `module.exports = global.${variableName};\n` : ""})();`;
|
||||
|
||||
case "ts":
|
||||
return `import * as Hogan from "hogan.js";
|
||||
type CompiledTemplates = { [name: string]: Hogan.Template };
|
||||
export const ${variableName}: CompiledTemplates = {};
|
||||
${content}`;
|
||||
|
||||
default:
|
||||
return "if (!!!" + variableName + ") var " + variableName + " = {};\n" + content;
|
||||
}
|
||||
}
|
||||
|
||||
// Write the directory
|
||||
if (options.outputdir) {
|
||||
mkderp.sync(options.outputdir);
|
||||
}
|
||||
|
||||
// Prepend namespace to template name
|
||||
function namespace(name: string): string {
|
||||
return (options.namespace || "") + name;
|
||||
}
|
||||
|
||||
// Write a template foreach file that matches template extension
|
||||
const templates = extractFiles(options.argv.remain)
|
||||
.map(file => {
|
||||
const timmedFileContents = fs.readFileSync(file, "utf8").trim();
|
||||
|
||||
if (!timmedFileContents) return;
|
||||
|
||||
const name = namespace(path.basename(file).replace(/\..*$/, ""));
|
||||
const cleanFileContents = wrap(file, name, removeByteOrderMark(timmedFileContents));
|
||||
|
||||
if (!options.outputdir) return cleanFileContents;
|
||||
|
||||
const fileExtension = options.wrapper === "ts" ? "ts" : "js";
|
||||
|
||||
return fs.writeFileSync(path.join(options.outputdir, `${name}.${fileExtension}`), prepareOutput(cleanFileContents));
|
||||
})
|
||||
.filter(templateContents => typeof templateContents !== "undefined");
|
||||
|
||||
// Output templates
|
||||
if (!templates.length || options.outputdir) process.exit(0);
|
||||
|
||||
console.log(prepareOutput(templates.join("\n")));
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
var fs = require('fs');
|
||||
|
||||
var hogan = require('hogan.js');
|
||||
|
||||
var root = 'website/templates';
|
||||
var pagesRoot = root + '/pages';
|
||||
|
||||
var websitePages = fs.readdirSync(root + '/pages');
|
||||
|
||||
var template = hogan.compile(readFile(root + '/template.mustache'));
|
||||
|
||||
var options = {
|
||||
'all': {
|
||||
'demoUrl': 'demo.html?diff=https://github.com/rtfpessoa/diff2html/pull/106'
|
||||
},
|
||||
'demo': {
|
||||
'extraClass': 'template-index-min'
|
||||
}
|
||||
};
|
||||
|
||||
websitePages.map(function(page) {
|
||||
var pagePartialTemplate = hogan.compile(readFile(pagesRoot + '/' + page + '/' + page + '.partial.mustache'));
|
||||
var pageAssetsTemplate = hogan.compile(readFile(pagesRoot + '/' + page + '/' + page + '-assets.partial.mustache'));
|
||||
var pageScriptsTemplate = hogan.compile(readFile(pagesRoot + '/' + page + '/' + page + '-scripts.partial.mustache'));
|
||||
|
||||
var templateOptions = {};
|
||||
|
||||
var key;
|
||||
|
||||
// Allow the pages to share common options
|
||||
var genericOptions = options['all'] || {};
|
||||
for (key in genericOptions) {
|
||||
if (genericOptions.hasOwnProperty(key)) {
|
||||
templateOptions[key] = genericOptions[key];
|
||||
}
|
||||
}
|
||||
|
||||
// Allow each page to have custom options
|
||||
var pageOptions = options[page] || {};
|
||||
for (key in pageOptions) {
|
||||
if (pageOptions.hasOwnProperty(key)) {
|
||||
templateOptions[key] = pageOptions[key];
|
||||
}
|
||||
}
|
||||
|
||||
var pagePartial = pagePartialTemplate.render(templateOptions);
|
||||
var pageAssets = pageAssetsTemplate.render(templateOptions);
|
||||
var pageScripts = pageScriptsTemplate.render(templateOptions);
|
||||
|
||||
templateOptions.assets = pageAssets;
|
||||
templateOptions.scripts = pageScripts;
|
||||
templateOptions.content = pagePartial;
|
||||
|
||||
var pageHtml = template.render(templateOptions);
|
||||
writeFile('docs/' + page + '.html', pageHtml);
|
||||
});
|
||||
|
||||
function readFile(filePath) {
|
||||
try {
|
||||
return fs.readFileSync(filePath, 'utf8');
|
||||
} catch (_ignore) {
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function writeFile(filePath, content) {
|
||||
return fs.writeFileSync(filePath, content);
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
#
|
||||
# diff2html website release script
|
||||
# by rtfpessoa
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
INPUT_DIR=website
|
||||
INPUT_DEMO_JS=${INPUT_DIR}/templates/pages/demo/demo.js
|
||||
INPUT_CSS_FILE=${INPUT_DIR}/main.css
|
||||
|
||||
OUTPUT_DIR=docs
|
||||
OUTPUT_DEMO_JS=${OUTPUT_DIR}/demo.js
|
||||
OUTPUT_DEMO_MIN_JS=${OUTPUT_DIR}/demo.min.js
|
||||
OUTPUT_CSS_FILE=${OUTPUT_DIR}/main.css
|
||||
OUTPUT_MIN_CSS_FILE=${OUTPUT_DIR}/main.min.css
|
||||
|
||||
echo "Creating diff2html website release ..."
|
||||
|
||||
echo "Cleaning previous versions ..."
|
||||
rm -rf ${OUTPUT_DIR}
|
||||
mkdir -p ${OUTPUT_DIR}
|
||||
|
||||
echo "Minifying ${OUTPUT_CSS_FILE} to ${OUTPUT_MIN_CSS_FILE}"
|
||||
postcss --use autoprefixer -o ${OUTPUT_CSS_FILE} ${INPUT_CSS_FILE}
|
||||
cleancss --advanced --compatibility=ie8 -o ${OUTPUT_MIN_CSS_FILE} ${OUTPUT_CSS_FILE}
|
||||
|
||||
echo "Generating website js aggregation file in ${OUTPUT_DEMO_JS}"
|
||||
browserify -e ${INPUT_DEMO_JS} -o ${OUTPUT_DEMO_JS}
|
||||
|
||||
echo "Minifying ${OUTPUT_DEMO_JS} to ${OUTPUT_DEMO_MIN_JS}"
|
||||
uglifyjs ${OUTPUT_DEMO_JS} -c -o ${OUTPUT_DEMO_MIN_JS}
|
||||
|
||||
echo "Generating HTMLs from templates ..."
|
||||
node ./scripts/release-website.js
|
||||
|
||||
echo "Copying static files ..."
|
||||
cp -rf ${INPUT_DIR}/img ${OUTPUT_DIR}/
|
||||
cp -f ${INPUT_DIR}/CNAME ${OUTPUT_DIR}/
|
||||
cp -f ${INPUT_DIR}/favicon.ico ${OUTPUT_DIR}/
|
||||
cp -f ${INPUT_DIR}/robots.txt ${OUTPUT_DIR}/
|
||||
cp -f ${INPUT_DIR}/sitemap.xml ${OUTPUT_DIR}/
|
||||
|
||||
echo "Creating diff2html assets symlink ..."
|
||||
ln -s ../dist docs/assets
|
||||
|
||||
echo "diff2html website release created successfully!"
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
#
|
||||
# diff2html release script
|
||||
# by rtfpessoa
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
INPUT_DIR=src
|
||||
INTPUT_TEMPLATES_DIR=${INPUT_DIR}/templates
|
||||
INPUT_UI_DIR=${INPUT_DIR}/ui
|
||||
INPUT_JS_FILE=${INPUT_DIR}/diff2html.js
|
||||
INPUT_JS_UI_FILE=${INPUT_UI_DIR}/js/diff2html-ui.js
|
||||
INPUT_CSS_FILE=${INPUT_UI_DIR}/css/diff2html.css
|
||||
INPUT_TYPINGS_D_TS_FILE=${INPUT_DIR}/diff2html.d.ts
|
||||
|
||||
GENERATED_TEMPLATES_FILE=${INTPUT_TEMPLATES_DIR}/diff2html-templates.js
|
||||
|
||||
OUTPUT_DIR=dist
|
||||
OUTPUT_JS_FILE=${OUTPUT_DIR}/diff2html.js
|
||||
OUTPUT_MIN_JS_FILE=${OUTPUT_DIR}/diff2html.min.js
|
||||
OUTPUT_JS_UI_FILE=${OUTPUT_DIR}/diff2html-ui.js
|
||||
OUTPUT_MIN_JS_UI_FILE=${OUTPUT_DIR}/diff2html-ui.min.js
|
||||
OUTPUT_CSS_FILE=${OUTPUT_DIR}/diff2html.css
|
||||
OUTPUT_MIN_CSS_FILE=${OUTPUT_DIR}/diff2html.min.css
|
||||
OUTPUT_TYPINGS_D_TS_FILE=${OUTPUT_DIR}/diff2html.d.ts
|
||||
|
||||
echo "Creating diff2html release ..."
|
||||
|
||||
echo "Cleaning previous versions ..."
|
||||
rm -rf ${OUTPUT_DIR}
|
||||
mkdir -p ${OUTPUT_DIR}
|
||||
|
||||
echo "Minifying ${OUTPUT_CSS_FILE} to ${OUTPUT_MIN_CSS_FILE}"
|
||||
postcss --use autoprefixer -o ${OUTPUT_CSS_FILE} ${INPUT_CSS_FILE}
|
||||
cleancss --advanced --compatibility=ie8 -o ${OUTPUT_MIN_CSS_FILE} ${OUTPUT_CSS_FILE}
|
||||
|
||||
echo "Pre-compile hogan.js templates"
|
||||
yarn run templates
|
||||
|
||||
echo "Generating js aggregation file in ${OUTPUT_JS_FILE}"
|
||||
browserify -e ${INPUT_JS_FILE} -o ${OUTPUT_JS_FILE}
|
||||
|
||||
echo "Minifying ${OUTPUT_JS_FILE} to ${OUTPUT_MIN_JS_FILE}"
|
||||
uglifyjs ${OUTPUT_JS_FILE} -c -o ${OUTPUT_MIN_JS_FILE}
|
||||
|
||||
echo "Generating js ui aggregation file in ${OUTPUT_JS_UI_FILE}"
|
||||
browserify -e ${INPUT_JS_UI_FILE} -o ${OUTPUT_JS_UI_FILE}
|
||||
|
||||
echo "Minifying ${OUTPUT_JS_UI_FILE} to ${OUTPUT_MIN_JS_UI_FILE}"
|
||||
uglifyjs ${OUTPUT_JS_UI_FILE} -c -o ${OUTPUT_MIN_JS_UI_FILE}
|
||||
|
||||
echo "Copying types ${INPUT_TYPINGS_D_TS_FILE} to ${OUTPUT_TYPINGS_D_TS_FILE}"
|
||||
cp -f ${INPUT_TYPINGS_D_TS_FILE} ${OUTPUT_TYPINGS_D_TS_FILE}
|
||||
|
||||
echo "diff2html release created successfully!"
|
||||
732
src/__tests__/diff-parser-tests.ts
Normal file
|
|
@ -0,0 +1,732 @@
|
|||
import { parse } from "../diff-parser";
|
||||
|
||||
function checkDiffSample(diff: string): void {
|
||||
const result = parse(diff);
|
||||
const file1 = result[0];
|
||||
expect(result.length).toEqual(1);
|
||||
expect(file1.addedLines).toEqual(1);
|
||||
expect(file1.deletedLines).toEqual(1);
|
||||
expect(file1.oldName).toEqual("sample");
|
||||
expect(file1.newName).toEqual("sample");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
}
|
||||
|
||||
describe("DiffParser", () => {
|
||||
describe("generateDiffJson", () => {
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it("should parse unix with \n diff", () => {
|
||||
const diff =
|
||||
"diff --git a/sample b/sample\n" +
|
||||
"index 0000001..0ddf2ba\n" +
|
||||
"--- a/sample\n" +
|
||||
"+++ b/sample\n" +
|
||||
"@@ -1 +1 @@\n" +
|
||||
"-test\n" +
|
||||
"+test1r\n";
|
||||
checkDiffSample(diff);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it("should parse windows with \r\n diff", () => {
|
||||
const diff =
|
||||
"diff --git a/sample b/sample\r\n" +
|
||||
"index 0000001..0ddf2ba\r\n" +
|
||||
"--- a/sample\r\n" +
|
||||
"+++ b/sample\r\n" +
|
||||
"@@ -1 +1 @@\r\n" +
|
||||
"-test\r\n" +
|
||||
"+test1r\r\n";
|
||||
checkDiffSample(diff);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it("should parse old os x with \r diff", () => {
|
||||
const diff =
|
||||
"diff --git a/sample b/sample\r" +
|
||||
"index 0000001..0ddf2ba\r" +
|
||||
"--- a/sample\r" +
|
||||
"+++ b/sample\r" +
|
||||
"@@ -1 +1 @@\r" +
|
||||
"-test\r" +
|
||||
"+test1r\r";
|
||||
checkDiffSample(diff);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it("should parse mixed eols diff", () => {
|
||||
const diff =
|
||||
"diff --git a/sample b/sample\n" +
|
||||
"index 0000001..0ddf2ba\r\n" +
|
||||
"--- a/sample\r" +
|
||||
"+++ b/sample\r\n" +
|
||||
"@@ -1 +1 @@\n" +
|
||||
"-test\r" +
|
||||
"+test1r\n";
|
||||
checkDiffSample(diff);
|
||||
});
|
||||
|
||||
it("should parse diff with special characters", () => {
|
||||
const diff =
|
||||
'diff --git "a/bla with \ttab.scala" "b/bla with \ttab.scala"\n' +
|
||||
"index 4c679d7..e9bd385 100644\n" +
|
||||
'--- "a/bla with \ttab.scala"\n' +
|
||||
'+++ "b/bla with \ttab.scala"\n' +
|
||||
"@@ -1 +1,2 @@\n" +
|
||||
"-cenas\n" +
|
||||
"+cenas com ananas\n" +
|
||||
"+bananas";
|
||||
|
||||
const result = parse(diff);
|
||||
const file1 = result[0];
|
||||
expect(result.length).toEqual(1);
|
||||
expect(file1.addedLines).toEqual(2);
|
||||
expect(file1.deletedLines).toEqual(1);
|
||||
expect(file1.oldName).toEqual("bla with \ttab.scala");
|
||||
expect(file1.newName).toEqual("bla with \ttab.scala");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
});
|
||||
|
||||
it("should parse diff with prefix", () => {
|
||||
const diff =
|
||||
'diff --git "\tbla with \ttab.scala" "\tbla with \ttab.scala"\n' +
|
||||
"index 4c679d7..e9bd385 100644\n" +
|
||||
'--- "\tbla with \ttab.scala"\n' +
|
||||
'+++ "\tbla with \ttab.scala"\n' +
|
||||
"@@ -1 +1,2 @@\n" +
|
||||
"-cenas\n" +
|
||||
"+cenas com ananas\n" +
|
||||
"+bananas";
|
||||
|
||||
const result = parse(diff, { srcPrefix: "\t", dstPrefix: "\t" });
|
||||
const file1 = result[0];
|
||||
expect(result.length).toEqual(1);
|
||||
expect(file1.addedLines).toEqual(2);
|
||||
expect(file1.deletedLines).toEqual(1);
|
||||
expect(file1.oldName).toEqual("bla with \ttab.scala");
|
||||
expect(file1.newName).toEqual("bla with \ttab.scala");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
});
|
||||
|
||||
it("should parse diff with deleted file", () => {
|
||||
const diff =
|
||||
"diff --git a/src/var/strundefined.js b/src/var/strundefined.js\n" +
|
||||
"deleted file mode 100644\n" +
|
||||
"index 04e16b0..0000000\n" +
|
||||
"--- a/src/var/strundefined.js\n" +
|
||||
"+++ /dev/null\n" +
|
||||
"@@ -1,3 +0,0 @@\n" +
|
||||
"-define(() => {\n" +
|
||||
"- return typeof undefined;\n" +
|
||||
"-});\n";
|
||||
|
||||
const result = parse(diff);
|
||||
expect(result.length).toEqual(1);
|
||||
|
||||
const file1 = result[0];
|
||||
expect(file1.isCombined).toEqual(false);
|
||||
expect(file1.addedLines).toEqual(0);
|
||||
expect(file1.deletedLines).toEqual(3);
|
||||
expect(file1.oldName).toEqual("src/var/strundefined.js");
|
||||
expect(file1.newName).toEqual("/dev/null");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
expect(file1.isDeleted).toEqual(true);
|
||||
expect(file1.checksumBefore).toEqual("04e16b0");
|
||||
expect(file1.checksumAfter).toEqual("0000000");
|
||||
});
|
||||
|
||||
it("should parse diff with new file", () => {
|
||||
const diff =
|
||||
"diff --git a/test.js b/test.js\n" +
|
||||
"new file mode 100644\n" +
|
||||
"index 0000000..e1e22ec\n" +
|
||||
"--- /dev/null\n" +
|
||||
"+++ b/test.js\n" +
|
||||
"@@ -0,0 +1,5 @@\n" +
|
||||
"+var parser = require('./source/git-parser');\n" +
|
||||
"+\n" +
|
||||
"+var patchLineList = [ false, false, false, false ];\n" +
|
||||
"+\n" +
|
||||
"+console.log(parser.parsePatchDiffResult(text, patchLineList));\n";
|
||||
|
||||
const result = parse(diff);
|
||||
expect(result.length).toEqual(1);
|
||||
|
||||
const file1 = result[0];
|
||||
expect(file1.isCombined).toEqual(false);
|
||||
expect(file1.addedLines).toEqual(5);
|
||||
expect(file1.deletedLines).toEqual(0);
|
||||
expect(file1.oldName).toEqual("/dev/null");
|
||||
expect(file1.newName).toEqual("test.js");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
expect(file1.isNew).toEqual(true);
|
||||
expect(file1.newFileMode).toEqual("100644");
|
||||
expect(file1.checksumBefore).toEqual("0000000");
|
||||
expect(file1.checksumAfter).toEqual("e1e22ec");
|
||||
});
|
||||
|
||||
it("should parse diff with nested diff", () => {
|
||||
const diff =
|
||||
"diff --git a/src/offset.js b/src/offset.js\n" +
|
||||
"index cc6ffb4..fa51f18 100644\n" +
|
||||
"--- a/src/offset.js\n" +
|
||||
"+++ b/src/offset.js\n" +
|
||||
"@@ -1,6 +1,5 @@\n" +
|
||||
"+var parser = require('./source/git-parser');\n" +
|
||||
"+\n" +
|
||||
"+var text = 'diff --git a/components/app/app.html b/components/app/app.html\\nindex ecb7a95..027bd9b 100644\\n--- a/components/app/app.html\\n+++ b/components/app/app.html\\n@@ -52,0 +53,3 @@\\n+\\n+\\n+\\n@@ -56,0 +60,3 @@\\n+\\n+\\n+\\n'\n" +
|
||||
"+var patchLineList = [ false, false, false, false ];\n" +
|
||||
"+\n" +
|
||||
"+console.log(parser.parsePatchDiffResult(text, patchLineList));\n";
|
||||
|
||||
const result = parse(diff);
|
||||
expect(result.length).toEqual(1);
|
||||
|
||||
const file1 = result[0];
|
||||
expect(file1.isCombined).toEqual(false);
|
||||
expect(file1.addedLines).toEqual(6);
|
||||
expect(file1.deletedLines).toEqual(0);
|
||||
expect(file1.oldName).toEqual("src/offset.js");
|
||||
expect(file1.newName).toEqual("src/offset.js");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
expect(file1.blocks[0].lines.length).toEqual(6);
|
||||
expect(file1.checksumBefore).toEqual("cc6ffb4");
|
||||
expect(file1.checksumAfter).toEqual("fa51f18");
|
||||
});
|
||||
|
||||
it("should parse diff with multiple blocks", () => {
|
||||
const diff =
|
||||
"diff --git a/src/attributes/classes.js b/src/attributes/classes.js\n" +
|
||||
"index c617824..c8d1393 100644\n" +
|
||||
"--- a/src/attributes/classes.js\n" +
|
||||
"+++ b/src/attributes/classes.js\n" +
|
||||
"@@ -1,10 +1,9 @@\n" +
|
||||
" define([\n" +
|
||||
' "../core",\n' +
|
||||
' "../var/rnotwhite",\n' +
|
||||
'- "../var/strundefined",\n' +
|
||||
' "../data/var/dataPriv",\n' +
|
||||
' "../core/init"\n' +
|
||||
"-], function( jQuery, rnotwhite, strundefined, dataPriv ) {\n" +
|
||||
"+], function( jQuery, rnotwhite, dataPriv ) {\n" +
|
||||
" \n" +
|
||||
" var rclass = /[\\t\\r\\n\\f]/g;\n" +
|
||||
" \n" +
|
||||
"@@ -128,7 +127,7 @@ jQuery.fn.extend({\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
" // Toggle whole class name\n" +
|
||||
'- } else if ( type === strundefined || type === "boolean" ) {\n' +
|
||||
'+ } else if ( value === undefined || type === "boolean" ) {\n' +
|
||||
" if ( this.className ) {\n" +
|
||||
" // store className if set\n" +
|
||||
' dataPriv.set( this, "__className__", this.className );\n';
|
||||
|
||||
const result = parse(diff);
|
||||
expect(result.length).toEqual(1);
|
||||
|
||||
const file1 = result[0];
|
||||
expect(file1.isCombined).toEqual(false);
|
||||
expect(file1.addedLines).toEqual(2);
|
||||
expect(file1.deletedLines).toEqual(3);
|
||||
expect(file1.oldName).toEqual("src/attributes/classes.js");
|
||||
expect(file1.newName).toEqual("src/attributes/classes.js");
|
||||
expect(file1.blocks.length).toEqual(2);
|
||||
expect(file1.blocks[0].lines.length).toEqual(11);
|
||||
expect(file1.blocks[1].lines.length).toEqual(8);
|
||||
expect(file1.checksumBefore).toEqual("c617824");
|
||||
expect(file1.checksumAfter).toEqual("c8d1393");
|
||||
});
|
||||
|
||||
it("should parse diff with multiple files", () => {
|
||||
const diff =
|
||||
"diff --git a/src/core/init.js b/src/core/init.js\n" +
|
||||
"index e49196a..50f310c 100644\n" +
|
||||
"--- a/src/core/init.js\n" +
|
||||
"+++ b/src/core/init.js\n" +
|
||||
"@@ -101,7 +101,7 @@ var rootjQuery,\n" +
|
||||
" // HANDLE: $(function)\n" +
|
||||
" // Shortcut for document ready\n" +
|
||||
" } else if ( jQuery.isFunction( selector ) ) {\n" +
|
||||
'- return typeof rootjQuery.ready !== "undefined" ?\n' +
|
||||
"+ return rootjQuery.ready !== undefined ?\n" +
|
||||
" rootjQuery.ready( selector ) :\n" +
|
||||
" // Execute immediately if ready is not present\n" +
|
||||
" selector( jQuery );\n" +
|
||||
"diff --git a/src/event.js b/src/event.js\n" +
|
||||
"index 7336f4d..6183f70 100644\n" +
|
||||
"--- a/src/event.js\n" +
|
||||
"+++ b/src/event.js\n" +
|
||||
"@@ -1,6 +1,5 @@\n" +
|
||||
" define([\n" +
|
||||
' "./core",\n' +
|
||||
'- "./var/strundefined",\n' +
|
||||
' "./var/rnotwhite",\n' +
|
||||
' "./var/hasOwn",\n' +
|
||||
' "./var/slice",\n';
|
||||
|
||||
const result = parse(diff);
|
||||
expect(result.length).toEqual(2);
|
||||
|
||||
const file1 = result[0];
|
||||
expect(file1.isCombined).toEqual(false);
|
||||
expect(file1.addedLines).toEqual(1);
|
||||
expect(file1.deletedLines).toEqual(1);
|
||||
expect(file1.oldName).toEqual("src/core/init.js");
|
||||
expect(file1.newName).toEqual("src/core/init.js");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
expect(file1.blocks[0].lines.length).toEqual(8);
|
||||
expect(file1.checksumBefore).toEqual("e49196a");
|
||||
expect(file1.checksumAfter).toEqual("50f310c");
|
||||
|
||||
const file2 = result[1];
|
||||
expect(file2.isCombined).toEqual(false);
|
||||
expect(file2.addedLines).toEqual(0);
|
||||
expect(file2.deletedLines).toEqual(1);
|
||||
expect(file2.oldName).toEqual("src/event.js");
|
||||
expect(file2.newName).toEqual("src/event.js");
|
||||
expect(file2.blocks.length).toEqual(1);
|
||||
expect(file2.blocks[0].lines.length).toEqual(6);
|
||||
expect(file2.checksumBefore).toEqual("7336f4d");
|
||||
expect(file2.checksumAfter).toEqual("6183f70");
|
||||
});
|
||||
|
||||
it("should parse combined diff", () => {
|
||||
const diff =
|
||||
"diff --combined describe.c\n" +
|
||||
"index fabadb8,cc95eb0..4866510\n" +
|
||||
"--- a/describe.c\n" +
|
||||
"+++ b/describe.c\n" +
|
||||
"@@@ -98,20 -98,12 +98,20 @@@\n" +
|
||||
" return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1;\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
"- static void describe(char *arg)\n" +
|
||||
" -static void describe(struct commit *cmit, int last_one)\n" +
|
||||
"++static void describe(char *arg, int last_one)\n" +
|
||||
" {\n" +
|
||||
" + unsigned char sha1[20];\n" +
|
||||
" + struct commit *cmit;\n" +
|
||||
" struct commit_list *list;\n" +
|
||||
" static int initialized = 0;\n" +
|
||||
" struct commit_name *n;\n" +
|
||||
" \n" +
|
||||
" + if (get_sha1(arg, sha1) < 0)\n" +
|
||||
" + usage(describe_usage);\n" +
|
||||
" + cmit = lookup_commit_reference(sha1);\n" +
|
||||
" + if (!cmit)\n" +
|
||||
" + usage(describe_usage);\n" +
|
||||
" +\n" +
|
||||
" if (!initialized) {\n" +
|
||||
" initialized = 1;\n" +
|
||||
" for_each_ref(get_name);\n";
|
||||
|
||||
const result = parse(diff);
|
||||
expect(result.length).toEqual(1);
|
||||
|
||||
const file1 = result[0];
|
||||
expect(file1.isCombined).toEqual(true);
|
||||
expect(9).toEqual(file1.addedLines);
|
||||
expect(2).toEqual(file1.deletedLines);
|
||||
expect(file1.oldName).toEqual("describe.c");
|
||||
expect(file1.newName).toEqual("describe.c");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
expect(file1.blocks[0].lines.length).toEqual(22);
|
||||
expect(file1.checksumBefore).toEqual(["cc95eb0", "4866510"]);
|
||||
expect(file1.checksumAfter).toEqual("fabadb8");
|
||||
});
|
||||
|
||||
it("should parse diffs with copied files", () => {
|
||||
const diff =
|
||||
"diff --git a/index.js b/more-index.js\n" +
|
||||
"dissimilarity index 5%\n" +
|
||||
"copy from index.js\n" +
|
||||
"copy to more-index.js\n";
|
||||
|
||||
const result = parse(diff);
|
||||
expect(result.length).toEqual(1);
|
||||
|
||||
const file1 = result[0];
|
||||
expect(file1.addedLines).toEqual(0);
|
||||
expect(file1.deletedLines).toEqual(0);
|
||||
expect(file1.oldName).toEqual("index.js");
|
||||
expect(file1.newName).toEqual("more-index.js");
|
||||
expect(file1.blocks.length).toEqual(0);
|
||||
expect(file1.isCopy).toEqual(true);
|
||||
expect(file1.changedPercentage).toEqual(5);
|
||||
});
|
||||
|
||||
it("should parse diffs with moved files", () => {
|
||||
const diff =
|
||||
"diff --git a/more-index.js b/other-index.js\n" +
|
||||
"similarity index 86%\n" +
|
||||
"rename from more-index.js\n" +
|
||||
"rename to other-index.js\n";
|
||||
|
||||
const result = parse(diff);
|
||||
expect(result.length).toEqual(1);
|
||||
|
||||
const file1 = result[0];
|
||||
expect(file1.addedLines).toEqual(0);
|
||||
expect(file1.deletedLines).toEqual(0);
|
||||
expect(file1.oldName).toEqual("more-index.js");
|
||||
expect(file1.newName).toEqual("other-index.js");
|
||||
expect(file1.blocks.length).toEqual(0);
|
||||
expect(file1.isRename).toEqual(true);
|
||||
expect(file1.unchangedPercentage).toEqual(86);
|
||||
});
|
||||
|
||||
it("should parse diffs correct line numbers", () => {
|
||||
const diff =
|
||||
"diff --git a/sample b/sample\n" +
|
||||
"index 0000001..0ddf2ba\n" +
|
||||
"--- a/sample\n" +
|
||||
"+++ b/sample\n" +
|
||||
"@@ -1 +1,2 @@\n" +
|
||||
"-test\n" +
|
||||
"+test1r\n";
|
||||
|
||||
const result = parse(diff);
|
||||
expect(result.length).toEqual(1);
|
||||
|
||||
const file1 = result[0];
|
||||
expect(file1.addedLines).toEqual(1);
|
||||
expect(file1.deletedLines).toEqual(1);
|
||||
expect(file1.oldName).toEqual("sample");
|
||||
expect(file1.newName).toEqual("sample");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
expect(file1.blocks[0].lines.length).toEqual(2);
|
||||
expect(file1.blocks[0].lines[0].oldNumber).toEqual(1);
|
||||
expect(file1.blocks[0].lines[0].newNumber).toBeUndefined();
|
||||
expect(file1.blocks[0].lines[1].oldNumber).toBeUndefined();
|
||||
expect(file1.blocks[0].lines[1].newNumber).toEqual(1);
|
||||
});
|
||||
|
||||
it("should parse unified non git diff and strip timestamps off the headers", () => {
|
||||
const diffs = [
|
||||
// 2 hours ahead of GMT
|
||||
"--- a/sample.js 2016-10-25 11:37:14.000000000 +0200\n" +
|
||||
"+++ b/sample.js 2016-10-25 11:37:14.000000000 +0200\n" +
|
||||
"@@ -1 +1,2 @@\n" +
|
||||
"-test\n" +
|
||||
"+test1r\n" +
|
||||
"+test2r\n",
|
||||
// 2 hours behind GMT
|
||||
"--- a/sample.js 2016-10-25 11:37:14.000000000 -0200\n" +
|
||||
"+++ b/sample.js 2016-10-25 11:37:14.000000000 -0200\n" +
|
||||
"@@ -1 +1,2 @@\n" +
|
||||
"-test\n" +
|
||||
"+test1r\n" +
|
||||
"+test2r\n"
|
||||
];
|
||||
|
||||
diffs.forEach(diff => {
|
||||
const result = parse(diff);
|
||||
const file1 = result[0];
|
||||
expect(result.length).toEqual(1);
|
||||
expect(file1.addedLines).toEqual(2);
|
||||
expect(file1.deletedLines).toEqual(1);
|
||||
expect(file1.oldName).toEqual("sample.js");
|
||||
expect(file1.newName).toEqual("sample.js");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
|
||||
const linesContent = file1.blocks[0].lines.map(line => {
|
||||
return line.content;
|
||||
});
|
||||
expect(["-test", "+test1r", "+test2r"]).toEqual(linesContent);
|
||||
});
|
||||
});
|
||||
|
||||
it("should parse unified non git diff", () => {
|
||||
const diff =
|
||||
"--- a/sample.js\n" + "+++ b/sample.js\n" + "@@ -1 +1,2 @@\n" + "-test\n" + "+test1r\n" + "+test2r\n";
|
||||
|
||||
const result = parse(diff);
|
||||
const file1 = result[0];
|
||||
expect(result.length).toEqual(1);
|
||||
expect(file1.addedLines).toEqual(2);
|
||||
expect(file1.deletedLines).toEqual(1);
|
||||
expect(file1.oldName).toEqual("sample.js");
|
||||
expect(file1.newName).toEqual("sample.js");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
|
||||
const linesContent = file1.blocks[0].lines.map(line => {
|
||||
return line.content;
|
||||
});
|
||||
expect(["-test", "+test1r", "+test2r"]).toEqual(linesContent);
|
||||
});
|
||||
|
||||
it("should parse unified diff with multiple hunks and files", () => {
|
||||
const diff =
|
||||
"--- sample.js\n" +
|
||||
"+++ sample.js\n" +
|
||||
"@@ -1 +1,2 @@\n" +
|
||||
"-test\n" +
|
||||
"@@ -10 +20,2 @@\n" +
|
||||
"+test\n" +
|
||||
"--- sample1.js\n" +
|
||||
"+++ sample1.js\n" +
|
||||
"@@ -1 +1,2 @@\n" +
|
||||
"+test1";
|
||||
|
||||
const result = parse(diff);
|
||||
expect(result.length).toEqual(2);
|
||||
|
||||
const file1 = result[0];
|
||||
expect(file1.addedLines).toEqual(1);
|
||||
expect(file1.deletedLines).toEqual(1);
|
||||
expect(file1.oldName).toEqual("sample.js");
|
||||
expect(file1.newName).toEqual("sample.js");
|
||||
expect(file1.blocks.length).toEqual(2);
|
||||
|
||||
const linesContent1 = file1.blocks[0].lines.map(line => {
|
||||
return line.content;
|
||||
});
|
||||
expect(["-test"]).toEqual(linesContent1);
|
||||
|
||||
const linesContent2 = file1.blocks[1].lines.map(line => {
|
||||
return line.content;
|
||||
});
|
||||
expect(["+test"]).toEqual(linesContent2);
|
||||
|
||||
const file2 = result[1];
|
||||
expect(file2.addedLines).toEqual(1);
|
||||
expect(file2.deletedLines).toEqual(0);
|
||||
expect(file2.oldName).toEqual("sample1.js");
|
||||
expect(file2.newName).toEqual("sample1.js");
|
||||
expect(file2.blocks.length).toEqual(1);
|
||||
|
||||
const linesContent = file2.blocks[0].lines.map(line => {
|
||||
return line.content;
|
||||
});
|
||||
expect(["+test1"]).toEqual(linesContent);
|
||||
});
|
||||
|
||||
it("should parse diff with --- and +++ in the context lines", () => {
|
||||
const diff =
|
||||
"--- sample.js\n" +
|
||||
"+++ sample.js\n" +
|
||||
"@@ -1,8 +1,8 @@\n" +
|
||||
" test\n" +
|
||||
" \n" +
|
||||
"-- 1\n" +
|
||||
"--- 1\n" +
|
||||
"---- 1\n" +
|
||||
" \n" +
|
||||
"++ 2\n" +
|
||||
"+++ 2\n" +
|
||||
"++++ 2";
|
||||
|
||||
const result = parse(diff);
|
||||
const file1 = result[0];
|
||||
expect(result.length).toEqual(1);
|
||||
expect(file1.addedLines).toEqual(3);
|
||||
expect(file1.deletedLines).toEqual(3);
|
||||
expect(file1.oldName).toEqual("sample.js");
|
||||
expect(file1.newName).toEqual("sample.js");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
|
||||
const linesContent = file1.blocks[0].lines.map(line => {
|
||||
return line.content;
|
||||
});
|
||||
expect([" test", " ", "-- 1", "--- 1", "---- 1", " ", "++ 2", "+++ 2", "++++ 2"]).toEqual(linesContent);
|
||||
});
|
||||
|
||||
it("should parse diff without proper hunk headers", () => {
|
||||
const diff = "--- sample.js\n" + "+++ sample.js\n" + "@@ @@\n" + " test";
|
||||
|
||||
const result = parse(diff);
|
||||
const file1 = result[0];
|
||||
expect(result.length).toEqual(1);
|
||||
expect(file1.addedLines).toEqual(0);
|
||||
expect(file1.deletedLines).toEqual(0);
|
||||
expect(file1.oldName).toEqual("sample.js");
|
||||
expect(file1.newName).toEqual("sample.js");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
|
||||
const linesContent = file1.blocks[0].lines.map(line => {
|
||||
return line.content;
|
||||
});
|
||||
expect([" test"]).toEqual(linesContent);
|
||||
});
|
||||
|
||||
it("should parse binary file diff", () => {
|
||||
const diff =
|
||||
"diff --git a/last-changes-config.png b/last-changes-config.png\n" +
|
||||
"index 322248b..56fc1f2 100644\n" +
|
||||
"--- a/last-changes-config.png\n" +
|
||||
"+++ b/last-changes-config.png\n" +
|
||||
"Binary files differ";
|
||||
|
||||
const result = parse(diff);
|
||||
const file1 = result[0];
|
||||
expect(result.length).toEqual(1);
|
||||
expect(file1.addedLines).toEqual(0);
|
||||
expect(file1.deletedLines).toEqual(0);
|
||||
expect(file1.oldName).toEqual("last-changes-config.png");
|
||||
expect(file1.newName).toEqual("last-changes-config.png");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
expect(file1.blocks[0].lines.length).toEqual(0);
|
||||
expect(file1.blocks[0].header).toEqual("Binary files differ");
|
||||
});
|
||||
|
||||
it("should parse diff with --find-renames", () => {
|
||||
const diff =
|
||||
"diff --git a/src/test-bar.js b/src/test-baz.js\n" +
|
||||
"similarity index 98%\n" +
|
||||
"rename from src/test-bar.js\n" +
|
||||
"rename to src/test-baz.js\n" +
|
||||
"index e01513b..f14a870 100644\n" +
|
||||
"--- a/src/test-bar.js\n" +
|
||||
"+++ b/src/test-baz.js\n" +
|
||||
"@@ -1,4 +1,32 @@\n" +
|
||||
" function foo() {\n" +
|
||||
'-var bar = "Whoops!";\n' +
|
||||
'+var baz = "Whoops!";\n' +
|
||||
" }\n" +
|
||||
" ";
|
||||
|
||||
const result = parse(diff);
|
||||
const file1 = result[0];
|
||||
expect(result.length).toEqual(1);
|
||||
expect(file1.addedLines).toEqual(1);
|
||||
expect(file1.deletedLines).toEqual(1);
|
||||
expect(file1.oldName).toEqual("src/test-bar.js");
|
||||
expect(file1.newName).toEqual("src/test-baz.js");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
expect(file1.blocks[0].lines.length).toEqual(5);
|
||||
const linesContent = file1.blocks[0].lines.map(line => {
|
||||
return line.content;
|
||||
});
|
||||
expect([" function foo() {", '-var bar = "Whoops!";', '+var baz = "Whoops!";', " }", " "]).toEqual(linesContent);
|
||||
});
|
||||
|
||||
it("should parse diff with prefix 2", () => {
|
||||
const diff =
|
||||
'diff --git "\tTest.scala" "\tScalaTest.scala"\n' +
|
||||
"similarity index 88%\n" +
|
||||
"rename from Test.scala\n" +
|
||||
"rename to ScalaTest.scala\n" +
|
||||
"index 7d1f9bf..8b13271 100644\n" +
|
||||
'--- "\tTest.scala"\n' +
|
||||
'+++ "\tScalaTest.scala"\n' +
|
||||
"@@ -1,6 +1,8 @@\n" +
|
||||
" class Test {\n" +
|
||||
" \n" +
|
||||
" def method1 = ???\n" +
|
||||
"+\n" +
|
||||
"+ def method2 = ???\n" +
|
||||
" \n" +
|
||||
" def myMethod = ???\n" +
|
||||
" \n" +
|
||||
"@@ -10,7 +12,6 @@ class Test {\n" +
|
||||
" \n" +
|
||||
" def + = ???\n" +
|
||||
" \n" +
|
||||
"- def |> = ???\n" +
|
||||
" \n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
'diff --git "\ttardis.png" "\ttardis.png"\n' +
|
||||
"new file mode 100644\n" +
|
||||
"index 0000000..d503a29\n" +
|
||||
'Binary files /dev/null and "\ttardis.png" differ\n' +
|
||||
"diff --git a/src/test-bar.js b/src/test-baz.js\n" +
|
||||
"similarity index 98%\n" +
|
||||
"rename from src/test-bar.js\n" +
|
||||
"rename to src/test-baz.js\n" +
|
||||
"index e01513b..f14a870 100644\n" +
|
||||
"--- a/src/test-bar.js\n" +
|
||||
"+++ b/src/test-baz.js\n" +
|
||||
"@@ -1,4 +1,32 @@\n" +
|
||||
" function foo() {\n" +
|
||||
'-var bar = "Whoops!";\n' +
|
||||
'+var baz = "Whoops!";\n' +
|
||||
" }\n" +
|
||||
" ";
|
||||
|
||||
const result = parse(diff, { srcPrefix: "\t", dstPrefix: "\t" });
|
||||
expect(result.length).toEqual(3);
|
||||
|
||||
const file1 = result[0];
|
||||
expect(file1.addedLines).toEqual(2);
|
||||
expect(file1.deletedLines).toEqual(1);
|
||||
expect(file1.oldName).toEqual("Test.scala");
|
||||
expect(file1.newName).toEqual("ScalaTest.scala");
|
||||
expect(file1.blocks.length).toEqual(2);
|
||||
expect(file1.blocks[0].lines.length).toEqual(8);
|
||||
expect(file1.blocks[1].lines.length).toEqual(7);
|
||||
|
||||
const file2 = result[1];
|
||||
expect(file2.oldName).toEqual("/dev/null");
|
||||
expect(file2.newName).toEqual("tardis.png");
|
||||
|
||||
const file3 = result[2];
|
||||
expect(file3.addedLines).toEqual(1);
|
||||
expect(file3.deletedLines).toEqual(1);
|
||||
expect(file3.oldName).toEqual("src/test-bar.js");
|
||||
expect(file3.newName).toEqual("src/test-baz.js");
|
||||
expect(file3.blocks.length).toEqual(1);
|
||||
expect(file3.blocks[0].lines.length).toEqual(5);
|
||||
const linesContent = file3.blocks[0].lines.map(line => {
|
||||
return line.content;
|
||||
});
|
||||
expect([" function foo() {", '-var bar = "Whoops!";', '+var baz = "Whoops!";', " }", " "]).toEqual(linesContent);
|
||||
});
|
||||
|
||||
it("should parse binary with content", () => {
|
||||
const diff =
|
||||
"diff --git a/favicon.png b/favicon.png\n" +
|
||||
"deleted file mode 100644\n" +
|
||||
"index 2a9d516a5647205d7be510dd0dff93a3663eff6f..0000000000000000000000000000000000000000\n" +
|
||||
"GIT binary patch\n" +
|
||||
"literal 0\n" +
|
||||
"HcmV?d00001\n" +
|
||||
"\n" +
|
||||
"literal 471\n" +
|
||||
"zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf<Z~8yL>4nJ\n" +
|
||||
"za0`Jj<E6WGe}IBwC9V-A&PAz-C7Jno3L%-fsSJk3`UaNzMkcGzh!g=;$beJ?=ckpF\n" +
|
||||
"zCl;kLIHu$$r7E~(7NwTw7iAYKI0u`(*t4mJfq_xq)5S5wqIc=!hrWj$cv|<b{x!c(\n" +
|
||||
"z;3r#y;31Y&=1q>qPVOAS4ANVKzqmCp=Cty@U^(7zk!jHsvT~YI{F^=Ex6g|gox78w\n" +
|
||||
"z+Sn2Du3GS9U7qU`1*NYYlJi3u-!<?H-eky}wyIIL;8VU@wCDrb0``&v(jQ*DWSR4K\n" +
|
||||
"zPq(3;isEyho{emNa=%%!jDPE`l3u;5d=q=<+v8kO-=C`*G#t-*AiE-D>-_B#8k9H0\n" +
|
||||
"zGl{FnZs<2$wz5^=Q2h-1XI^s{LQL1#T4epqNPC%Orl(tD_@!*EY++~^Lt2<2&!&%=\n" +
|
||||
"z`m>(TYj6uS7jDdt=eH>iOyQg(QMR<-Fw8)Dk^ZG)XQTuzEgl{`GpS?Cfq9818R9~=\n" +
|
||||
"z{&h9@9n8F^?|qusoPy{k#%tVHzu7H$t26CR`BJZk*Ixf&u36WuS=?6m2^ho-p00i_\n" +
|
||||
"I>zopr0Nz-&lmGw#\n" +
|
||||
"diff --git a/src/test-bar.js b/src/test-baz.js\n" +
|
||||
"similarity index 98%\n" +
|
||||
"rename from src/test-bar.js\n" +
|
||||
"rename to src/test-baz.js\n" +
|
||||
"index e01513b..f14a870 100644\n" +
|
||||
"--- a/src/test-bar.js\n" +
|
||||
"+++ b/src/test-baz.js\n" +
|
||||
"@@ -1,4 +1,32 @@\n" +
|
||||
" function foo() {\n" +
|
||||
'-var bar = "Whoops!";\n' +
|
||||
'+var baz = "Whoops!";\n' +
|
||||
" }\n" +
|
||||
" ";
|
||||
|
||||
const result = parse(diff);
|
||||
expect(result.length).toEqual(2);
|
||||
|
||||
const file1 = result[0];
|
||||
expect(file1.oldName).toEqual("favicon.png");
|
||||
expect(file1.newName).toEqual("favicon.png");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
expect(file1.blocks[0].lines.length).toEqual(0);
|
||||
|
||||
const file2 = result[1];
|
||||
expect(file2.addedLines).toEqual(1);
|
||||
expect(file2.deletedLines).toEqual(1);
|
||||
expect(file2.oldName).toEqual("src/test-bar.js");
|
||||
expect(file2.newName).toEqual("src/test-baz.js");
|
||||
expect(file2.blocks.length).toEqual(1);
|
||||
expect(file2.blocks[0].lines.length).toEqual(5);
|
||||
const linesContent = file2.blocks[0].lines.map(line => {
|
||||
return line.content;
|
||||
});
|
||||
expect([" function foo() {", '-var bar = "Whoops!";', '+var baz = "Whoops!";', " }", " "]).toEqual(linesContent);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,59 +1,58 @@
|
|||
var assert = require('assert');
|
||||
import { parse, html } from "../diff2html";
|
||||
import { DiffFile, LineType, OutputFormatType } from "../types";
|
||||
|
||||
var Diff2Html = require('../src/diff2html.js').Diff2Html;
|
||||
const diffExample1 =
|
||||
"diff --git a/sample b/sample\n" +
|
||||
"index 0000001..0ddf2ba\n" +
|
||||
"--- a/sample\n" +
|
||||
"+++ b/sample\n" +
|
||||
"@@ -1 +1 @@\n" +
|
||||
"-test\n" +
|
||||
"+test1\n";
|
||||
|
||||
var diffExample1 =
|
||||
'diff --git a/sample b/sample\n' +
|
||||
'index 0000001..0ddf2ba\n' +
|
||||
'--- a/sample\n' +
|
||||
'+++ b/sample\n' +
|
||||
'@@ -1 +1 @@\n' +
|
||||
'-test\n' +
|
||||
'+test1\n';
|
||||
const jsonExample1: DiffFile[] = [
|
||||
{
|
||||
blocks: [
|
||||
{
|
||||
lines: [
|
||||
{
|
||||
content: "-test",
|
||||
type: LineType.DELETE,
|
||||
oldNumber: 1,
|
||||
newNumber: undefined
|
||||
},
|
||||
{
|
||||
content: "+test1",
|
||||
type: LineType.INSERT,
|
||||
oldNumber: undefined,
|
||||
newNumber: 1
|
||||
}
|
||||
],
|
||||
oldStartLine: 1,
|
||||
oldStartLine2: undefined,
|
||||
newStartLine: 1,
|
||||
header: "@@ -1 +1 @@"
|
||||
}
|
||||
],
|
||||
deletedLines: 1,
|
||||
addedLines: 1,
|
||||
checksumBefore: "0000001",
|
||||
checksumAfter: "0ddf2ba",
|
||||
oldName: "sample",
|
||||
newName: "sample",
|
||||
language: "",
|
||||
isCombined: false,
|
||||
isGitDiff: true
|
||||
}
|
||||
];
|
||||
|
||||
var jsonExample1 =
|
||||
[
|
||||
{
|
||||
blocks: [
|
||||
{
|
||||
lines: [
|
||||
{
|
||||
content: '-test',
|
||||
type: 'd2h-del',
|
||||
oldNumber: 1,
|
||||
newNumber: null
|
||||
},
|
||||
{
|
||||
content: '+test1',
|
||||
type: 'd2h-ins',
|
||||
oldNumber: null,
|
||||
newNumber: 1
|
||||
}
|
||||
],
|
||||
oldStartLine: '1',
|
||||
oldStartLine2: null,
|
||||
newStartLine: '1',
|
||||
header: '@@ -1 +1 @@'
|
||||
}
|
||||
],
|
||||
deletedLines: 1,
|
||||
addedLines: 1,
|
||||
checksumBefore: '0000001',
|
||||
checksumAfter: '0ddf2ba',
|
||||
oldName: 'sample',
|
||||
language: undefined,
|
||||
newName: 'sample',
|
||||
isCombined: false
|
||||
}
|
||||
];
|
||||
|
||||
var filesExample1 =
|
||||
const filesExample1 =
|
||||
'<div class="d2h-file-list-wrapper">\n' +
|
||||
' <div class="d2h-file-list-header">\n' +
|
||||
' <span class="d2h-file-list-title">Files changed (1)</span>\n' +
|
||||
' <a class="d2h-file-switch d2h-hide">hide</a>\n' +
|
||||
' <a class="d2h-file-switch d2h-show">show</a>\n' +
|
||||
' </div>\n' +
|
||||
" </div>\n" +
|
||||
' <ol class="d2h-file-list">\n' +
|
||||
' <li class="d2h-file-list-line">\n' +
|
||||
' <span class="d2h-file-name-wrapper">\n' +
|
||||
|
|
@ -64,13 +63,13 @@ var filesExample1 =
|
|||
' <span class="d2h-file-stats">\n' +
|
||||
' <span class="d2h-lines-added">+1</span>\n' +
|
||||
' <span class="d2h-lines-deleted">-1</span>\n' +
|
||||
' </span>\n' +
|
||||
' </span>\n' +
|
||||
'</li>\n' +
|
||||
' </ol>\n' +
|
||||
'</div>';
|
||||
" </span>\n" +
|
||||
" </span>\n" +
|
||||
"</li>\n" +
|
||||
" </ol>\n" +
|
||||
"</div>";
|
||||
|
||||
var htmlLineExample1 =
|
||||
const htmlLineExample1 =
|
||||
'<div class="d2h-wrapper">\n' +
|
||||
' <div id="d2h-675094" class="d2h-file-wrapper" data-lang="">\n' +
|
||||
' <div class="d2h-file-header">\n' +
|
||||
|
|
@ -79,49 +78,49 @@ var htmlLineExample1 =
|
|||
' <path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>\n' +
|
||||
' </svg> <span class="d2h-file-name">sample</span>\n' +
|
||||
' <span class="d2h-tag d2h-changed d2h-changed-tag">CHANGED</span></span>\n' +
|
||||
' </div>\n' +
|
||||
" </div>\n" +
|
||||
' <div class="d2h-file-diff">\n' +
|
||||
' <div class="d2h-code-wrapper">\n' +
|
||||
' <table class="d2h-diff-table">\n' +
|
||||
' <tbody class="d2h-diff-tbody">\n' +
|
||||
' <tr>\n' +
|
||||
" <tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-info"></td>\n' +
|
||||
' <td class="d2h-info">\n' +
|
||||
' <div class="d2h-code-line d2h-info">@@ -1 +1 @@</div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
' <td class="d2h-code-linenumber d2h-del">\n' +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-del d2h-change">\n' +
|
||||
' <div class="line-num1">1</div>\n' +
|
||||
'<div class="line-num2"></div>\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-del">\n' +
|
||||
' <div class="d2h-code-line d2h-del">\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-del d2h-change">\n' +
|
||||
' <div class="d2h-code-line d2h-del d2h-change">\n' +
|
||||
' <span class="d2h-code-line-prefix">-</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"><del>test</del></span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
' <td class="d2h-code-linenumber d2h-ins">\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-ins d2h-change">\n' +
|
||||
' <div class="line-num1"></div>\n' +
|
||||
'<div class="line-num2">1</div>\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-ins">\n' +
|
||||
' <div class="d2h-code-line d2h-ins">\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-ins d2h-change">\n' +
|
||||
' <div class="d2h-code-line d2h-ins d2h-change">\n' +
|
||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"><ins>test1</ins></span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr>\n' +
|
||||
' </tbody>\n' +
|
||||
' </table>\n' +
|
||||
' </div>\n' +
|
||||
' </div>\n' +
|
||||
'</div>\n' +
|
||||
'</div>';
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr>\n" +
|
||||
" </tbody>\n" +
|
||||
" </table>\n" +
|
||||
" </div>\n" +
|
||||
" </div>\n" +
|
||||
"</div>\n" +
|
||||
"</div>";
|
||||
|
||||
var htmlLineExample1WithFilesSummary = filesExample1 + htmlLineExample1;
|
||||
const htmlLineExample1WithFilesSummary = filesExample1 + htmlLineExample1;
|
||||
|
||||
var htmlSideExample1 =
|
||||
const htmlSideExample1 =
|
||||
'<div class="d2h-wrapper">\n' +
|
||||
' <div id="d2h-675094" class="d2h-file-wrapper" data-lang="">\n' +
|
||||
' <div class="d2h-file-header">\n' +
|
||||
|
|
@ -130,187 +129,189 @@ var htmlSideExample1 =
|
|||
' <path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>\n' +
|
||||
' </svg> <span class="d2h-file-name">sample</span>\n' +
|
||||
' <span class="d2h-tag d2h-changed d2h-changed-tag">CHANGED</span></span>\n' +
|
||||
' </div>\n' +
|
||||
" </div>\n" +
|
||||
' <div class="d2h-files-diff">\n' +
|
||||
' <div class="d2h-file-side-diff">\n' +
|
||||
' <div class="d2h-code-wrapper">\n' +
|
||||
' <table class="d2h-diff-table">\n' +
|
||||
' <tbody class="d2h-diff-tbody">\n' +
|
||||
' <tr>\n' +
|
||||
" <tr>\n" +
|
||||
' <td class="d2h-code-side-linenumber d2h-info"></td>\n' +
|
||||
' <td class="d2h-info">\n' +
|
||||
' <div class="d2h-code-side-line d2h-info">@@ -1 +1 @@</div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
' <td class="d2h-code-side-linenumber d2h-del">\n' +
|
||||
' 1\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-del">\n' +
|
||||
' <div class="d2h-code-side-line d2h-del">\n' +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-side-linenumber d2h-del d2h-change">\n' +
|
||||
" 1\n" +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-del d2h-change">\n' +
|
||||
' <div class="d2h-code-side-line d2h-del d2h-change">\n' +
|
||||
' <span class="d2h-code-line-prefix">-</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"><del>test</del></span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr>\n' +
|
||||
' </tbody>\n' +
|
||||
' </table>\n' +
|
||||
' </div>\n' +
|
||||
' </div>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr>\n" +
|
||||
" </tbody>\n" +
|
||||
" </table>\n" +
|
||||
" </div>\n" +
|
||||
" </div>\n" +
|
||||
' <div class="d2h-file-side-diff">\n' +
|
||||
' <div class="d2h-code-wrapper">\n' +
|
||||
' <table class="d2h-diff-table">\n' +
|
||||
' <tbody class="d2h-diff-tbody">\n' +
|
||||
' <tr>\n' +
|
||||
" <tr>\n" +
|
||||
' <td class="d2h-code-side-linenumber d2h-info"></td>\n' +
|
||||
' <td class="d2h-info">\n' +
|
||||
' <div class="d2h-code-side-line d2h-info"></div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
' <td class="d2h-code-side-linenumber d2h-ins">\n' +
|
||||
' 1\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-ins">\n' +
|
||||
' <div class="d2h-code-side-line d2h-ins">\n' +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-side-linenumber d2h-ins d2h-change">\n' +
|
||||
" 1\n" +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-ins d2h-change">\n' +
|
||||
' <div class="d2h-code-side-line d2h-ins d2h-change">\n' +
|
||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"><ins>test1</ins></span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr>\n' +
|
||||
' </tbody>\n' +
|
||||
' </table>\n' +
|
||||
' </div>\n' +
|
||||
' </div>\n' +
|
||||
' </div>\n' +
|
||||
'</div>\n' +
|
||||
'</div>';
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr>\n" +
|
||||
" </tbody>\n" +
|
||||
" </table>\n" +
|
||||
" </div>\n" +
|
||||
" </div>\n" +
|
||||
" </div>\n" +
|
||||
"</div>\n" +
|
||||
"</div>";
|
||||
|
||||
var htmlSideExample1WithFilesSummary = filesExample1 + htmlSideExample1;
|
||||
const htmlSideExample1WithFilesSummary = filesExample1 + htmlSideExample1;
|
||||
|
||||
describe('Diff2Html', function() {
|
||||
describe('getJsonFromDiff', function() {
|
||||
it('should parse simple diff to json', function() {
|
||||
var diff =
|
||||
'diff --git a/sample b/sample\n' +
|
||||
'index 0000001..0ddf2ba\n' +
|
||||
'--- a/sample\n' +
|
||||
'+++ b/sample\n' +
|
||||
'@@ -1 +1 @@\n' +
|
||||
'-test\n' +
|
||||
'+test1\n';
|
||||
var result = Diff2Html.getJsonFromDiff(diff);
|
||||
describe("Diff2Html", () => {
|
||||
describe("getJsonFromDiff", () => {
|
||||
it("should parse simple diff to json", () => {
|
||||
const diff =
|
||||
"diff --git a/sample b/sample\n" +
|
||||
"index 0000001..0ddf2ba\n" +
|
||||
"--- a/sample\n" +
|
||||
"+++ b/sample\n" +
|
||||
"@@ -1 +1 @@\n" +
|
||||
"-test\n" +
|
||||
"+test1\n";
|
||||
const result = parse(diff);
|
||||
|
||||
var file1 = result[0];
|
||||
assert.equal(1, result.length);
|
||||
assert.equal(1, file1.addedLines);
|
||||
assert.equal(1, file1.deletedLines);
|
||||
assert.equal('sample', file1.oldName);
|
||||
assert.equal('sample', file1.newName);
|
||||
assert.equal(1, file1.blocks.length);
|
||||
const file1 = result[0];
|
||||
expect(result.length).toEqual(1);
|
||||
expect(file1.addedLines).toEqual(1);
|
||||
expect(file1.deletedLines).toEqual(1);
|
||||
expect(file1.oldName).toEqual("sample");
|
||||
expect(file1.newName).toEqual("sample");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
});
|
||||
|
||||
// Test case for issue #49
|
||||
it('should parse diff with added EOF', function() {
|
||||
var diff =
|
||||
'diff --git a/sample.scala b/sample.scala\n' +
|
||||
'index b583263..8b2fc3e 100644\n' +
|
||||
'--- a/b583263..8b2fc3e\n' +
|
||||
'+++ b/8b2fc3e\n' +
|
||||
'@@ -50,5 +50,7 @@ case class Response[+A](value: Option[A],\n' +
|
||||
' object ResponseErrorCode extends JsonEnumeration {\n' +
|
||||
' val NoError, ServiceError, JsonError,\n' +
|
||||
' InvalidPermissions, MissingPermissions, GenericError,\n' +
|
||||
'- TokenRevoked, MissingToken = Value\n' +
|
||||
'-}\n' +
|
||||
'\\ No newline at end of file\n' +
|
||||
'+ TokenRevoked, MissingToken,\n' +
|
||||
'+ IndexLock, RepositoryError, NotValidRepo, PullRequestNotMergeable, BranchError,\n' +
|
||||
'+ PluginError, CodeParserError, EngineError = Value\n' +
|
||||
'+}\n';
|
||||
var result = Diff2Html.getJsonFromDiff(diff);
|
||||
it("should parse diff with added EOF", () => {
|
||||
const diff =
|
||||
"diff --git a/sample.scala b/sample.scala\n" +
|
||||
"index b583263..8b2fc3e 100644\n" +
|
||||
"--- a/b583263..8b2fc3e\n" +
|
||||
"+++ b/8b2fc3e\n" +
|
||||
"@@ -50,5 +50,7 @@ case class Response[+A](value: Option[A],\n" +
|
||||
" object ResponseErrorCode extends JsonEnumeration {\n" +
|
||||
" val NoError, ServiceError, JsonError,\n" +
|
||||
" InvalidPermissions, MissingPermissions, GenericError,\n" +
|
||||
"- TokenRevoked, MissingToken = Value\n" +
|
||||
"-}\n" +
|
||||
"\\ No newline at end of file\n" +
|
||||
"+ TokenRevoked, MissingToken,\n" +
|
||||
"+ IndexLock, RepositoryError, NotValidRepo, PullRequestNotMergeable, BranchError,\n" +
|
||||
"+ PluginError, CodeParserError, EngineError = Value\n" +
|
||||
"+}\n";
|
||||
const result = parse(diff);
|
||||
|
||||
assert.equal(50, result[0].blocks[0].lines[0].oldNumber);
|
||||
assert.equal(50, result[0].blocks[0].lines[0].newNumber);
|
||||
expect(result[0].blocks[0].lines[0].oldNumber).toEqual(50);
|
||||
expect(result[0].blocks[0].lines[0].newNumber).toEqual(50);
|
||||
|
||||
assert.equal(51, result[0].blocks[0].lines[1].oldNumber);
|
||||
assert.equal(51, result[0].blocks[0].lines[1].newNumber);
|
||||
expect(result[0].blocks[0].lines[1].oldNumber).toEqual(51);
|
||||
expect(result[0].blocks[0].lines[1].newNumber).toEqual(51);
|
||||
|
||||
assert.equal(52, result[0].blocks[0].lines[2].oldNumber);
|
||||
assert.equal(52, result[0].blocks[0].lines[2].newNumber);
|
||||
expect(result[0].blocks[0].lines[2].oldNumber).toEqual(52);
|
||||
expect(result[0].blocks[0].lines[2].newNumber).toEqual(52);
|
||||
|
||||
assert.equal(53, result[0].blocks[0].lines[3].oldNumber);
|
||||
assert.equal(null, result[0].blocks[0].lines[3].newNumber);
|
||||
expect(result[0].blocks[0].lines[3].oldNumber).toEqual(53);
|
||||
expect(result[0].blocks[0].lines[3].newNumber).toBeUndefined();
|
||||
|
||||
assert.equal(54, result[0].blocks[0].lines[4].oldNumber);
|
||||
assert.equal(null, result[0].blocks[0].lines[4].newNumber);
|
||||
expect(result[0].blocks[0].lines[4].oldNumber).toEqual(54);
|
||||
expect(result[0].blocks[0].lines[4].newNumber).toBeUndefined();
|
||||
|
||||
assert.equal(null, result[0].blocks[0].lines[5].oldNumber);
|
||||
assert.equal(53, result[0].blocks[0].lines[5].newNumber);
|
||||
expect(result[0].blocks[0].lines[5].oldNumber).toBeUndefined();
|
||||
expect(result[0].blocks[0].lines[5].newNumber).toEqual(53);
|
||||
|
||||
assert.equal(null, result[0].blocks[0].lines[6].oldNumber);
|
||||
assert.equal(54, result[0].blocks[0].lines[6].newNumber);
|
||||
expect(result[0].blocks[0].lines[6].oldNumber).toBeUndefined();
|
||||
expect(result[0].blocks[0].lines[6].newNumber).toEqual(54);
|
||||
|
||||
assert.equal(null, result[0].blocks[0].lines[7].oldNumber);
|
||||
assert.equal(55, result[0].blocks[0].lines[7].newNumber);
|
||||
expect(result[0].blocks[0].lines[7].oldNumber).toBeUndefined();
|
||||
expect(result[0].blocks[0].lines[7].newNumber).toEqual(55);
|
||||
|
||||
assert.equal(null, result[0].blocks[0].lines[8].oldNumber);
|
||||
assert.equal(56, result[0].blocks[0].lines[8].newNumber);
|
||||
expect(result[0].blocks[0].lines[8].oldNumber).toBeUndefined();
|
||||
expect(result[0].blocks[0].lines[8].newNumber).toEqual(56);
|
||||
});
|
||||
|
||||
it('should generate pretty line by line html from diff', function() {
|
||||
var result = Diff2Html.getPrettyHtmlFromDiff(diffExample1);
|
||||
assert.equal(htmlLineExample1, result);
|
||||
it("should generate pretty line by line html from diff", () => {
|
||||
const result = html(diffExample1, { drawFileList: false });
|
||||
expect(result).toEqual(htmlLineExample1);
|
||||
});
|
||||
|
||||
it('should generate pretty line by line html from json', function() {
|
||||
var result = Diff2Html.getPrettyHtmlFromJson(jsonExample1);
|
||||
assert.equal(htmlLineExample1, result);
|
||||
it("should generate pretty line by line html from json", () => {
|
||||
const result = html(jsonExample1, { drawFileList: false });
|
||||
expect(result).toEqual(htmlLineExample1);
|
||||
});
|
||||
|
||||
it('should generate pretty diff with files summary', function() {
|
||||
var result = Diff2Html.getPrettyHtmlFromDiff(diffExample1, {showFiles: true});
|
||||
assert.equal(htmlLineExample1WithFilesSummary, result);
|
||||
it("should generate pretty diff with files summary", () => {
|
||||
const result = html(diffExample1, { drawFileList: true });
|
||||
expect(result).toEqual(htmlLineExample1WithFilesSummary);
|
||||
});
|
||||
|
||||
it('should generate pretty side by side html from diff', function() {
|
||||
var result = Diff2Html.getPrettySideBySideHtmlFromDiff(diffExample1);
|
||||
assert.equal(htmlSideExample1, result);
|
||||
it("should generate pretty side by side html from diff", () => {
|
||||
const result = html(diffExample1, { outputFormat: OutputFormatType.SIDE_BY_SIDE, drawFileList: false });
|
||||
expect(result).toEqual(htmlSideExample1);
|
||||
});
|
||||
|
||||
it('should generate pretty side by side html from json', function() {
|
||||
var result = Diff2Html.getPrettySideBySideHtmlFromJson(jsonExample1);
|
||||
assert.equal(htmlSideExample1, result);
|
||||
it("should generate pretty side by side html from json", () => {
|
||||
const result = html(jsonExample1, { outputFormat: OutputFormatType.SIDE_BY_SIDE, drawFileList: false });
|
||||
expect(result).toEqual(htmlSideExample1);
|
||||
});
|
||||
|
||||
it('should generate pretty side by side html from diff', function() {
|
||||
var result = Diff2Html.getPrettySideBySideHtmlFromDiff(diffExample1, {showFiles: true});
|
||||
assert.equal(htmlSideExample1WithFilesSummary, result);
|
||||
it("should generate pretty side by side html from diff 2", () => {
|
||||
const result = html(diffExample1, { outputFormat: OutputFormatType.SIDE_BY_SIDE, drawFileList: true });
|
||||
expect(result).toEqual(htmlSideExample1WithFilesSummary);
|
||||
});
|
||||
|
||||
it('should generate pretty side by side html from diff with html on headers', function() {
|
||||
var diffExample2 = 'diff --git a/CHANGELOG.md b/CHANGELOG.md\n' +
|
||||
'index fc3e3f4..b486d10 100644\n' +
|
||||
'--- a/CHANGELOG.md\n' +
|
||||
'+++ b/CHANGELOG.md\n' +
|
||||
'@@ -1,7 +1,6 @@\n' +
|
||||
' # Change Log\n' +
|
||||
' All notable changes to this project will be documented in this file.\n' +
|
||||
' This project adheres to [Semantic Versioning](http://semver.org/).\n' +
|
||||
it("should generate pretty side by side html from diff with html on headers", () => {
|
||||
const diffExample2 =
|
||||
"diff --git a/CHANGELOG.md b/CHANGELOG.md\n" +
|
||||
"index fc3e3f4..b486d10 100644\n" +
|
||||
"--- a/CHANGELOG.md\n" +
|
||||
"+++ b/CHANGELOG.md\n" +
|
||||
"@@ -1,7 +1,6 @@\n" +
|
||||
" # Change Log\n" +
|
||||
" All notable changes to this project will be documented in this file.\n" +
|
||||
" This project adheres to [Semantic Versioning](http://semver.org/).\n" +
|
||||
'-$a="<table><tr><td>Use the following format for additions: ` - VERSION: [feature/patch (if applicable)] Short description of change. Links to relevant issues/PRs.`\n' +
|
||||
' $a="<table><tr><td>\n' +
|
||||
" $a=\"<table><tr><td>- 1.1.9: Fix around ubuntu's inability to cache promises. [#877](https://github.com/FredrikNoren/ungit/pull/878)\n" +
|
||||
' - 1.1.8:\n' +
|
||||
"@@ -11,7 +10,7 @@ $a=\"<table><tr><td>- 1.1.9: Fix around ubuntu's inability to cache promises. [#8\n" +
|
||||
' - 1.1.7:\n' +
|
||||
' - Fix diff flickering issue and optimization [#865](https://github.com/FredrikNoren/ungit/pull/865)\n' +
|
||||
' - Fix credential dialog issue [#864](https://github.com/FredrikNoren/ungit/pull/864)\n' +
|
||||
'- - Fix HEAD branch order when redraw [#858](https://github.com/FredrikNoren/ungit/issues/858)\n' +
|
||||
'+4 - Fix HEAD branch order when redraw [#858](https://github.com/FredrikNoren/ungit/issues/858)\n' +
|
||||
' - 1.1.6: Fix path auto complete [#861](https://github.com/FredrikNoren/ungit/issues/861)\n' +
|
||||
" - 1.1.8:\n" +
|
||||
"@@ -11,7 +10,7 @@ $a="<table><tr><td>- 1.1.9: Fix around ubuntu's inability to cache promises. [#8\n" +
|
||||
" - 1.1.7:\n" +
|
||||
" - Fix diff flickering issue and optimization [#865](https://github.com/FredrikNoren/ungit/pull/865)\n" +
|
||||
" - Fix credential dialog issue [#864](https://github.com/FredrikNoren/ungit/pull/864)\n" +
|
||||
"- - Fix HEAD branch order when redraw [#858](https://github.com/FredrikNoren/ungit/issues/858)\n" +
|
||||
"+4 - Fix HEAD branch order when redraw [#858](https://github.com/FredrikNoren/ungit/issues/858)\n" +
|
||||
" - 1.1.6: Fix path auto complete [#861](https://github.com/FredrikNoren/ungit/issues/861)\n" +
|
||||
' - 1.1.5: Update "Toggle all" button after commit or changing selected files [#859](https://github.com/FredrikNoren/ungit/issues/859)\n' +
|
||||
' - 1.1.4: [patch] Promise refactoring\n' +
|
||||
' \n';
|
||||
" - 1.1.4: [patch] Promise refactoring\n" +
|
||||
" \n";
|
||||
|
||||
var htmlExample2 = '<div class="d2h-wrapper">\n' +
|
||||
const htmlExample2 =
|
||||
'<div class="d2h-wrapper">\n' +
|
||||
' <div id="d2h-211439" class="d2h-file-wrapper" data-lang="md">\n' +
|
||||
' <div class="d2h-file-header">\n' +
|
||||
' <span class="d2h-file-name-wrapper">\n' +
|
||||
|
|
@ -318,207 +319,207 @@ describe('Diff2Html', function() {
|
|||
' <path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>\n' +
|
||||
' </svg> <span class="d2h-file-name">CHANGELOG.md</span>\n' +
|
||||
' <span class="d2h-tag d2h-changed d2h-changed-tag">CHANGED</span></span>\n' +
|
||||
' </div>\n' +
|
||||
" </div>\n" +
|
||||
' <div class="d2h-file-diff">\n' +
|
||||
' <div class="d2h-code-wrapper">\n' +
|
||||
' <table class="d2h-diff-table">\n' +
|
||||
' <tbody class="d2h-diff-tbody">\n' +
|
||||
' <tr>\n' +
|
||||
" <tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-info"></td>\n' +
|
||||
' <td class="d2h-info">\n' +
|
||||
' <div class="d2h-code-line d2h-info">@@ -1,7 +1,6 @@</div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-cntx">\n' +
|
||||
' <div class="line-num1">1</div>\n' +
|
||||
'<div class="line-num2">1</div>\n' +
|
||||
' </td>\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-cntx">\n' +
|
||||
' <div class="d2h-code-line d2h-cntx">\n' +
|
||||
' <span class="d2h-code-line-prefix"> </span>\n' +
|
||||
' <span class="d2h-code-line-ctn"># Change Log</span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-cntx">\n' +
|
||||
' <div class="line-num1">2</div>\n' +
|
||||
'<div class="line-num2">2</div>\n' +
|
||||
' </td>\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-cntx">\n' +
|
||||
' <div class="d2h-code-line d2h-cntx">\n' +
|
||||
' <span class="d2h-code-line-prefix"> </span>\n' +
|
||||
' <span class="d2h-code-line-ctn">All notable changes to this project will be documented in this file.</span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-cntx">\n' +
|
||||
' <div class="line-num1">3</div>\n' +
|
||||
'<div class="line-num2">3</div>\n' +
|
||||
' </td>\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-cntx">\n' +
|
||||
' <div class="d2h-code-line d2h-cntx">\n' +
|
||||
' <span class="d2h-code-line-prefix"> </span>\n' +
|
||||
' <span class="d2h-code-line-ctn">This project adheres to [Semantic Versioning](http://semver.org/).</span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-del">\n' +
|
||||
' <div class="line-num1">4</div>\n' +
|
||||
'<div class="line-num2"></div>\n' +
|
||||
' </td>\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-del">\n' +
|
||||
' <div class="d2h-code-line d2h-del">\n' +
|
||||
' <span class="d2h-code-line-prefix">-</span>\n' +
|
||||
' <span class="d2h-code-line-ctn">$a="<table><tr><td>Use the following format for additions: ` - VERSION: [feature/patch (if applicable)] Short description of change. Links to relevant issues/PRs.`</span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-cntx">\n' +
|
||||
' <div class="line-num1">5</div>\n' +
|
||||
'<div class="line-num2">4</div>\n' +
|
||||
' </td>\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-cntx">\n' +
|
||||
' <div class="d2h-code-line d2h-cntx">\n' +
|
||||
' <span class="d2h-code-line-prefix"> </span>\n' +
|
||||
' <span class="d2h-code-line-ctn">$a="<table><tr><td></span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-cntx">\n' +
|
||||
' <div class="line-num1">6</div>\n' +
|
||||
'<div class="line-num2">5</div>\n' +
|
||||
' </td>\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-cntx">\n' +
|
||||
' <div class="d2h-code-line d2h-cntx">\n' +
|
||||
' <span class="d2h-code-line-prefix"> </span>\n' +
|
||||
' <span class="d2h-code-line-ctn">$a="<table><tr><td>- 1.1.9: Fix around ubuntu's inability to cache promises. [#877](https://github.com/FredrikNoren/ungit/pull/878)</span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-cntx">\n' +
|
||||
' <div class="line-num1">7</div>\n' +
|
||||
'<div class="line-num2">6</div>\n' +
|
||||
' </td>\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-cntx">\n' +
|
||||
' <div class="d2h-code-line d2h-cntx">\n' +
|
||||
' <span class="d2h-code-line-prefix"> </span>\n' +
|
||||
' <span class="d2h-code-line-ctn">- 1.1.8:</span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr>\n' +
|
||||
'<tr>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr>\n" +
|
||||
"<tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-info"></td>\n' +
|
||||
' <td class="d2h-info">\n' +
|
||||
' <div class="d2h-code-line d2h-info">@@ -11,7 +10,7 @@ $a="<table><tr><td>- 1.1.9: Fix around ubuntu's inability to cache promises. [#8</div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-cntx">\n' +
|
||||
' <div class="line-num1">11</div>\n' +
|
||||
'<div class="line-num2">10</div>\n' +
|
||||
' </td>\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-cntx">\n' +
|
||||
' <div class="d2h-code-line d2h-cntx">\n' +
|
||||
' <span class="d2h-code-line-prefix"> </span>\n' +
|
||||
' <span class="d2h-code-line-ctn">- 1.1.7:</span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-cntx">\n' +
|
||||
' <div class="line-num1">12</div>\n' +
|
||||
'<div class="line-num2">11</div>\n' +
|
||||
' </td>\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-cntx">\n' +
|
||||
' <div class="d2h-code-line d2h-cntx">\n' +
|
||||
' <span class="d2h-code-line-prefix"> </span>\n' +
|
||||
' <span class="d2h-code-line-ctn"> - Fix diff flickering issue and optimization [#865](https://github.com/FredrikNoren/ungit/pull/865)</span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-cntx">\n' +
|
||||
' <div class="line-num1">13</div>\n' +
|
||||
'<div class="line-num2">12</div>\n' +
|
||||
' </td>\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-cntx">\n' +
|
||||
' <div class="d2h-code-line d2h-cntx">\n' +
|
||||
' <span class="d2h-code-line-prefix"> </span>\n' +
|
||||
' <span class="d2h-code-line-ctn"> - Fix credential dialog issue [#864](https://github.com/FredrikNoren/ungit/pull/864)</span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
' <td class="d2h-code-linenumber d2h-del">\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-del d2h-change">\n' +
|
||||
' <div class="line-num1">14</div>\n' +
|
||||
'<div class="line-num2"></div>\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-del">\n' +
|
||||
' <div class="d2h-code-line d2h-del">\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-del d2h-change">\n' +
|
||||
' <div class="d2h-code-line d2h-del d2h-change">\n' +
|
||||
' <span class="d2h-code-line-prefix">-</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"> - Fix HEAD branch order when redraw [#858](https://github.com/FredrikNoren/ungit/issues/858)</span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
' <td class="d2h-code-linenumber d2h-ins">\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-ins d2h-change">\n' +
|
||||
' <div class="line-num1"></div>\n' +
|
||||
'<div class="line-num2">13</div>\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-ins">\n' +
|
||||
' <div class="d2h-code-line d2h-ins">\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-ins d2h-change">\n' +
|
||||
' <div class="d2h-code-line d2h-ins d2h-change">\n' +
|
||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"><ins>4</ins> - Fix HEAD branch order when redraw [#858](https://github.com/FredrikNoren/ungit/issues/858)</span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-cntx">\n' +
|
||||
' <div class="line-num1">15</div>\n' +
|
||||
'<div class="line-num2">14</div>\n' +
|
||||
' </td>\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-cntx">\n' +
|
||||
' <div class="d2h-code-line d2h-cntx">\n' +
|
||||
' <span class="d2h-code-line-prefix"> </span>\n' +
|
||||
' <span class="d2h-code-line-ctn">- 1.1.6: Fix path auto complete [#861](https://github.com/FredrikNoren/ungit/issues/861)</span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-cntx">\n' +
|
||||
' <div class="line-num1">16</div>\n' +
|
||||
'<div class="line-num2">15</div>\n' +
|
||||
' </td>\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-cntx">\n' +
|
||||
' <div class="d2h-code-line d2h-cntx">\n' +
|
||||
' <span class="d2h-code-line-prefix"> </span>\n' +
|
||||
' <span class="d2h-code-line-ctn">- 1.1.5: Update "Toggle all" button after commit or changing selected files [#859](https://github.com/FredrikNoren/ungit/issues/859)</span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-cntx">\n' +
|
||||
' <div class="line-num1">17</div>\n' +
|
||||
'<div class="line-num2">16</div>\n' +
|
||||
' </td>\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-cntx">\n' +
|
||||
' <div class="d2h-code-line d2h-cntx">\n' +
|
||||
' <span class="d2h-code-line-prefix"> </span>\n' +
|
||||
' <span class="d2h-code-line-ctn">- 1.1.4: [patch] Promise refactoring</span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-cntx">\n' +
|
||||
' <div class="line-num1">18</div>\n' +
|
||||
'<div class="line-num2">17</div>\n' +
|
||||
' </td>\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-cntx">\n' +
|
||||
' <div class="d2h-code-line d2h-cntx">\n' +
|
||||
' <span class="d2h-code-line-prefix"> </span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr>\n' +
|
||||
' </tbody>\n' +
|
||||
' </table>\n' +
|
||||
' </div>\n' +
|
||||
' </div>\n' +
|
||||
'</div>\n' +
|
||||
'</div>';
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr>\n" +
|
||||
" </tbody>\n" +
|
||||
" </table>\n" +
|
||||
" </div>\n" +
|
||||
" </div>\n" +
|
||||
"</div>\n" +
|
||||
"</div>";
|
||||
|
||||
var result = Diff2Html.getPrettyHtmlFromDiff(diffExample2);
|
||||
assert.equal(result, htmlExample2);
|
||||
const result = html(diffExample2, { drawFileList: false });
|
||||
expect(result).toEqual(htmlExample2);
|
||||
});
|
||||
});
|
||||
});
|
||||
179
src/__tests__/file-list-printer-tests.ts
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
import { render } from "../file-list-renderer";
|
||||
import HoganJsUtils from "../hoganjs-utils";
|
||||
|
||||
describe("FileListPrinter", () => {
|
||||
describe("generateFileList", () => {
|
||||
it("should expose old and new files to templates", () => {
|
||||
const hoganUtils = new HoganJsUtils({
|
||||
rawTemplates: {
|
||||
"file-summary-wrapper": "{{{files}}}",
|
||||
"file-summary-line": "{{oldName}}, {{newName}}, {{fileName}}"
|
||||
}
|
||||
});
|
||||
const files = [
|
||||
{
|
||||
isCombined: false,
|
||||
isGitDiff: false,
|
||||
blocks: [],
|
||||
addedLines: 12,
|
||||
deletedLines: 41,
|
||||
language: "js",
|
||||
oldName: "my/file/name.js",
|
||||
newName: "my/file/name.js"
|
||||
},
|
||||
{
|
||||
isCombined: false,
|
||||
isGitDiff: false,
|
||||
blocks: [],
|
||||
addedLines: 12,
|
||||
deletedLines: 41,
|
||||
language: "js",
|
||||
oldName: "my/file/name1.js",
|
||||
newName: "my/file/name2.js"
|
||||
},
|
||||
{
|
||||
isCombined: false,
|
||||
isGitDiff: false,
|
||||
blocks: [],
|
||||
addedLines: 12,
|
||||
deletedLines: 0,
|
||||
language: "js",
|
||||
oldName: "dev/null",
|
||||
newName: "my/file/name.js",
|
||||
isNew: true
|
||||
},
|
||||
{
|
||||
isCombined: false,
|
||||
isGitDiff: false,
|
||||
blocks: [],
|
||||
addedLines: 0,
|
||||
deletedLines: 41,
|
||||
language: "js",
|
||||
oldName: "my/file/name.js",
|
||||
newName: "dev/null",
|
||||
isDeleted: true
|
||||
}
|
||||
];
|
||||
|
||||
const fileHtml = render(files, hoganUtils);
|
||||
const expected =
|
||||
"my/file/name.js, my/file/name.js, my/file/name.js\n" +
|
||||
"my/file/name1.js, my/file/name2.js, my/file/{name1.js → name2.js}\n" +
|
||||
"dev/null, my/file/name.js, my/file/name.js\n" +
|
||||
"my/file/name.js, dev/null, my/file/name.js";
|
||||
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
|
||||
it("should work for all kinds of files", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const files = [
|
||||
{
|
||||
isCombined: false,
|
||||
isGitDiff: false,
|
||||
blocks: [],
|
||||
addedLines: 12,
|
||||
deletedLines: 41,
|
||||
language: "js",
|
||||
oldName: "my/file/name.js",
|
||||
newName: "my/file/name.js"
|
||||
},
|
||||
{
|
||||
isCombined: false,
|
||||
isGitDiff: false,
|
||||
blocks: [],
|
||||
addedLines: 12,
|
||||
deletedLines: 41,
|
||||
language: "js",
|
||||
oldName: "my/file/name1.js",
|
||||
newName: "my/file/name2.js"
|
||||
},
|
||||
{
|
||||
isCombined: false,
|
||||
isGitDiff: false,
|
||||
blocks: [],
|
||||
addedLines: 12,
|
||||
deletedLines: 0,
|
||||
language: "js",
|
||||
oldName: "dev/null",
|
||||
newName: "my/file/name.js",
|
||||
isNew: true
|
||||
},
|
||||
{
|
||||
isCombined: false,
|
||||
isGitDiff: false,
|
||||
blocks: [],
|
||||
addedLines: 0,
|
||||
deletedLines: 41,
|
||||
language: "js",
|
||||
oldName: "my/file/name.js",
|
||||
newName: "dev/null",
|
||||
isDeleted: true
|
||||
}
|
||||
];
|
||||
|
||||
const fileHtml = render(files, hoganUtils);
|
||||
|
||||
const expected =
|
||||
'<div class="d2h-file-list-wrapper">\n' +
|
||||
' <div class="d2h-file-list-header">\n' +
|
||||
' <span class="d2h-file-list-title">Files changed (4)</span>\n' +
|
||||
' <a class="d2h-file-switch d2h-hide">hide</a>\n' +
|
||||
' <a class="d2h-file-switch d2h-show">show</a>\n' +
|
||||
" </div>\n" +
|
||||
' <ol class="d2h-file-list">\n' +
|
||||
' <li class="d2h-file-list-line">\n' +
|
||||
' <span class="d2h-file-name-wrapper">\n' +
|
||||
' <svg aria-hidden="true" class="d2h-icon d2h-changed" height="16" title="modified" version="1.1"\n' +
|
||||
' viewBox="0 0 14 16" width="14">\n' +
|
||||
' <path d="M13 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V2c0-0.55-0.45-1-1-1z m0 13H1V2h12v12zM4 8c0-1.66 1.34-3 3-3s3 1.34 3 3-1.34 3-3 3-3-1.34-3-3z"></path>\n' +
|
||||
' </svg> <a href="#d2h-781444" class="d2h-file-name">my/file/name.js</a>\n' +
|
||||
' <span class="d2h-file-stats">\n' +
|
||||
' <span class="d2h-lines-added">+12</span>\n' +
|
||||
' <span class="d2h-lines-deleted">-41</span>\n' +
|
||||
" </span>\n" +
|
||||
" </span>\n" +
|
||||
"</li>\n" +
|
||||
'<li class="d2h-file-list-line">\n' +
|
||||
' <span class="d2h-file-name-wrapper">\n' +
|
||||
' <svg aria-hidden="true" class="d2h-icon d2h-moved" height="16" title="renamed" version="1.1"\n' +
|
||||
' viewBox="0 0 14 16" width="14">\n' +
|
||||
' <path d="M6 9H3V7h3V4l5 4-5 4V9z m8-7v12c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h12c0.55 0 1 0.45 1 1z m-1 0H1v12h12V2z"></path>\n' +
|
||||
' </svg> <a href="#d2h-662683" class="d2h-file-name">my/file/{name1.js → name2.js}</a>\n' +
|
||||
' <span class="d2h-file-stats">\n' +
|
||||
' <span class="d2h-lines-added">+12</span>\n' +
|
||||
' <span class="d2h-lines-deleted">-41</span>\n' +
|
||||
" </span>\n" +
|
||||
" </span>\n" +
|
||||
"</li>\n" +
|
||||
'<li class="d2h-file-list-line">\n' +
|
||||
' <span class="d2h-file-name-wrapper">\n' +
|
||||
' <svg aria-hidden="true" class="d2h-icon d2h-added" height="16" title="added" version="1.1" viewBox="0 0 14 16"\n' +
|
||||
' width="14">\n' +
|
||||
' <path d="M13 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V2c0-0.55-0.45-1-1-1z m0 13H1V2h12v12zM6 9H3V7h3V4h2v3h3v2H8v3H6V9z"></path>\n' +
|
||||
' </svg> <a href="#d2h-781444" class="d2h-file-name">my/file/name.js</a>\n' +
|
||||
' <span class="d2h-file-stats">\n' +
|
||||
' <span class="d2h-lines-added">+12</span>\n' +
|
||||
' <span class="d2h-lines-deleted">-0</span>\n' +
|
||||
" </span>\n" +
|
||||
" </span>\n" +
|
||||
"</li>\n" +
|
||||
'<li class="d2h-file-list-line">\n' +
|
||||
' <span class="d2h-file-name-wrapper">\n' +
|
||||
' <svg aria-hidden="true" class="d2h-icon d2h-deleted" height="16" title="removed" version="1.1"\n' +
|
||||
' viewBox="0 0 14 16" width="14">\n' +
|
||||
' <path d="M13 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V2c0-0.55-0.45-1-1-1z m0 13H1V2h12v12zM11 9H3V7h8v2z"></path>\n' +
|
||||
' </svg> <a href="#d2h-781444" class="d2h-file-name">my/file/name.js</a>\n' +
|
||||
' <span class="d2h-file-stats">\n' +
|
||||
' <span class="d2h-lines-added">+0</span>\n' +
|
||||
' <span class="d2h-lines-deleted">-41</span>\n' +
|
||||
" </span>\n" +
|
||||
" </span>\n" +
|
||||
"</li>\n" +
|
||||
" </ol>\n" +
|
||||
"</div>";
|
||||
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
66
src/__tests__/hogan-cache-tests.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import HoganJsUtils from "../hoganjs-utils";
|
||||
import { CSSLineClass } from "../render-utils";
|
||||
|
||||
describe("HoganJsUtils", () => {
|
||||
describe("render", () => {
|
||||
const emptyDiffHtml =
|
||||
"<tr>\n" +
|
||||
' <td class="d2h-info">\n' +
|
||||
' <div class="d2h-code-line d2h-info">\n' +
|
||||
" File without changes\n" +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr>";
|
||||
|
||||
it("should render view", () => {
|
||||
const hoganJsUtils = new HoganJsUtils({});
|
||||
const result = hoganJsUtils.render("generic", "empty-diff", {
|
||||
contentClass: "d2h-code-line",
|
||||
CSSLineClass: CSSLineClass
|
||||
});
|
||||
expect(result).toEqual(emptyDiffHtml);
|
||||
});
|
||||
|
||||
it("should render view without cache", () => {
|
||||
const hoganJsUtils = new HoganJsUtils({});
|
||||
const result = hoganJsUtils.render("generic", "empty-diff", {
|
||||
contentClass: "d2h-code-line",
|
||||
CSSLineClass: CSSLineClass
|
||||
});
|
||||
expect(result).toEqual(emptyDiffHtml);
|
||||
});
|
||||
|
||||
it("should throw exception if template is missing", () => {
|
||||
const hoganJsUtils = new HoganJsUtils({});
|
||||
expect(() => hoganJsUtils.render("generic", "missing-template", {})).toThrow(Error);
|
||||
});
|
||||
|
||||
it("should allow templates to be overridden with compiled templates", () => {
|
||||
const emptyDiffTemplate = HoganJsUtils.compile("<p>{{myName}}</p>");
|
||||
const hoganJsUtils = new HoganJsUtils({ compiledTemplates: { "generic-empty-diff": emptyDiffTemplate } });
|
||||
|
||||
const result = hoganJsUtils.render("generic", "empty-diff", { myName: "Rodrigo Fernandes" });
|
||||
expect(result).toEqual("<p>Rodrigo Fernandes</p>");
|
||||
});
|
||||
|
||||
it("should allow templates to be overridden with uncompiled templates", () => {
|
||||
const emptyDiffTemplate = "<p>{{myName}}</p>";
|
||||
const hoganJsUtils = new HoganJsUtils({ rawTemplates: { "generic-empty-diff": emptyDiffTemplate } });
|
||||
|
||||
const result = hoganJsUtils.render("generic", "empty-diff", { myName: "Rodrigo Fernandes" });
|
||||
expect(result).toEqual("<p>Rodrigo Fernandes</p>");
|
||||
});
|
||||
|
||||
it("should allow templates to be overridden giving priority to raw templates", () => {
|
||||
const emptyDiffTemplate = HoganJsUtils.compile("<p>Not used!</p>");
|
||||
const emptyDiffTemplateUncompiled = "<p>{{myName}}</p>";
|
||||
const hoganJsUtils = new HoganJsUtils({
|
||||
compiledTemplates: { "generic-empty-diff": emptyDiffTemplate },
|
||||
rawTemplates: { "generic-empty-diff": emptyDiffTemplateUncompiled }
|
||||
});
|
||||
|
||||
const result = hoganJsUtils.render("generic", "empty-diff", { myName: "Rodrigo Fernandes" });
|
||||
expect(result).toEqual("<p>Rodrigo Fernandes</p>");
|
||||
});
|
||||
});
|
||||
});
|
||||
613
src/__tests__/line-by-line-tests.ts
Normal file
|
|
@ -0,0 +1,613 @@
|
|||
import LineByLineRenderer from "../line-by-line-renderer";
|
||||
import HoganJsUtils from "../hoganjs-utils";
|
||||
import { LineType, DiffFile, LineMatchingType } from "../types";
|
||||
import { CSSLineClass } from "../render-utils";
|
||||
|
||||
describe("LineByLineRenderer", () => {
|
||||
describe("_generateEmptyDiff", () => {
|
||||
it("should return an empty diff", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||
const fileHtml = lineByLineRenderer.generateEmptyDiff();
|
||||
const expected =
|
||||
"<tr>\n" +
|
||||
' <td class="d2h-info">\n' +
|
||||
' <div class="d2h-code-line d2h-info">\n' +
|
||||
" File without changes\n" +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr>";
|
||||
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("makeLineHtml", () => {
|
||||
it("should work for insertions", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||
let fileHtml = lineByLineRenderer.generateSingleLineHtml({
|
||||
type: CSSLineClass.INSERTS,
|
||||
prefix: "+",
|
||||
content: "test",
|
||||
oldNumber: undefined,
|
||||
newNumber: 30
|
||||
});
|
||||
fileHtml = fileHtml.replace(/\n\n+/g, "\n");
|
||||
const expected =
|
||||
"<tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-ins">\n' +
|
||||
' <div class="line-num1"></div>\n' +
|
||||
'<div class="line-num2">30</div>\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-ins">\n' +
|
||||
' <div class="d2h-code-line d2h-ins">\n' +
|
||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||
' <span class="d2h-code-line-ctn">test</span>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr>";
|
||||
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
|
||||
it("should work for deletions", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||
let fileHtml = lineByLineRenderer.generateSingleLineHtml({
|
||||
type: CSSLineClass.DELETES,
|
||||
prefix: "-",
|
||||
content: "test",
|
||||
oldNumber: 30,
|
||||
newNumber: undefined
|
||||
});
|
||||
fileHtml = fileHtml.replace(/\n\n+/g, "\n");
|
||||
const expected =
|
||||
"<tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-del">\n' +
|
||||
' <div class="line-num1">30</div>\n' +
|
||||
'<div class="line-num2"></div>\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-del">\n' +
|
||||
' <div class="d2h-code-line d2h-del">\n' +
|
||||
' <span class="d2h-code-line-prefix">-</span>\n' +
|
||||
' <span class="d2h-code-line-ctn">test</span>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr>";
|
||||
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
|
||||
it("should convert indents into non breakin spaces (2 white spaces)", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||
let fileHtml = lineByLineRenderer.generateSingleLineHtml({
|
||||
type: CSSLineClass.INSERTS,
|
||||
prefix: "+",
|
||||
content: " test",
|
||||
oldNumber: undefined,
|
||||
newNumber: 30
|
||||
});
|
||||
fileHtml = fileHtml.replace(/\n\n+/g, "\n");
|
||||
const expected =
|
||||
"<tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-ins">\n' +
|
||||
' <div class="line-num1"></div>\n' +
|
||||
'<div class="line-num2">30</div>\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-ins">\n' +
|
||||
' <div class="d2h-code-line d2h-ins">\n' +
|
||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"> test</span>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr>";
|
||||
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
|
||||
it("should convert indents into non breakin spaces (4 white spaces)", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||
let fileHtml = lineByLineRenderer.generateSingleLineHtml({
|
||||
type: CSSLineClass.INSERTS,
|
||||
prefix: "+",
|
||||
content: " test",
|
||||
oldNumber: undefined,
|
||||
newNumber: 30
|
||||
});
|
||||
fileHtml = fileHtml.replace(/\n\n+/g, "\n");
|
||||
const expected =
|
||||
"<tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-ins">\n' +
|
||||
' <div class="line-num1"></div>\n' +
|
||||
'<div class="line-num2">30</div>\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-ins">\n' +
|
||||
' <div class="d2h-code-line d2h-ins">\n' +
|
||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"> test</span>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr>";
|
||||
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
|
||||
it("should preserve tabs", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||
let fileHtml = lineByLineRenderer.generateSingleLineHtml({
|
||||
type: CSSLineClass.INSERTS,
|
||||
prefix: "+",
|
||||
content: "\ttest",
|
||||
oldNumber: undefined,
|
||||
newNumber: 30
|
||||
});
|
||||
fileHtml = fileHtml.replace(/\n\n+/g, "\n");
|
||||
const expected =
|
||||
"<tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-ins">\n' +
|
||||
' <div class="line-num1"></div>\n' +
|
||||
"" +
|
||||
'<div class="line-num2">30</div>\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-ins">\n' +
|
||||
' <div class="d2h-code-line d2h-ins">\n' +
|
||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||
' <span class="d2h-code-line-ctn">\ttest</span>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr>";
|
||||
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("makeFileDiffHtml", () => {
|
||||
it("should work for simple file", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||
|
||||
const file = {
|
||||
addedLines: 12,
|
||||
deletedLines: 41,
|
||||
language: "js",
|
||||
oldName: "my/file/name.js",
|
||||
newName: "my/file/name.js",
|
||||
isCombined: false,
|
||||
isGitDiff: false,
|
||||
blocks: []
|
||||
};
|
||||
const diffs = "<span>Random Html</span>";
|
||||
|
||||
const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs);
|
||||
|
||||
const expected =
|
||||
'<div id="d2h-781444" class="d2h-file-wrapper" data-lang="js">\n' +
|
||||
' <div class="d2h-file-header">\n' +
|
||||
' <span class="d2h-file-name-wrapper">\n' +
|
||||
' <svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">\n' +
|
||||
' <path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>\n' +
|
||||
' </svg> <span class="d2h-file-name">my/file/name.js</span>\n' +
|
||||
' <span class="d2h-tag d2h-changed d2h-changed-tag">CHANGED</span></span>\n' +
|
||||
" </div>\n" +
|
||||
' <div class="d2h-file-diff">\n' +
|
||||
' <div class="d2h-code-wrapper">\n' +
|
||||
' <table class="d2h-diff-table">\n' +
|
||||
' <tbody class="d2h-diff-tbody">\n' +
|
||||
" <span>Random Html</span>\n" +
|
||||
" </tbody>\n" +
|
||||
" </table>\n" +
|
||||
" </div>\n" +
|
||||
" </div>\n" +
|
||||
"</div>";
|
||||
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
it("should work for simple added file", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||
|
||||
const file = {
|
||||
addedLines: 12,
|
||||
deletedLines: 0,
|
||||
language: "js",
|
||||
oldName: "dev/null",
|
||||
newName: "my/file/name.js",
|
||||
isNew: true,
|
||||
isCombined: false,
|
||||
isGitDiff: false,
|
||||
blocks: []
|
||||
};
|
||||
const diffs = "<span>Random Html</span>";
|
||||
|
||||
const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs);
|
||||
|
||||
const expected =
|
||||
'<div id="d2h-781444" class="d2h-file-wrapper" data-lang="js">\n' +
|
||||
' <div class="d2h-file-header">\n' +
|
||||
' <span class="d2h-file-name-wrapper">\n' +
|
||||
' <svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">\n' +
|
||||
' <path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>\n' +
|
||||
' </svg> <span class="d2h-file-name">my/file/name.js</span>\n' +
|
||||
' <span class="d2h-tag d2h-added d2h-added-tag">ADDED</span></span>\n' +
|
||||
" </div>\n" +
|
||||
' <div class="d2h-file-diff">\n' +
|
||||
' <div class="d2h-code-wrapper">\n' +
|
||||
' <table class="d2h-diff-table">\n' +
|
||||
' <tbody class="d2h-diff-tbody">\n' +
|
||||
" <span>Random Html</span>\n" +
|
||||
" </tbody>\n" +
|
||||
" </table>\n" +
|
||||
" </div>\n" +
|
||||
" </div>\n" +
|
||||
"</div>";
|
||||
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
it("should work for simple deleted file", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||
|
||||
const file = {
|
||||
addedLines: 0,
|
||||
deletedLines: 41,
|
||||
language: "js",
|
||||
oldName: "my/file/name.js",
|
||||
newName: "dev/null",
|
||||
isDeleted: true,
|
||||
isCombined: false,
|
||||
isGitDiff: false,
|
||||
blocks: []
|
||||
};
|
||||
const diffs = "<span>Random Html</span>";
|
||||
|
||||
const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs);
|
||||
|
||||
const expected =
|
||||
'<div id="d2h-781444" class="d2h-file-wrapper" data-lang="js">\n' +
|
||||
' <div class="d2h-file-header">\n' +
|
||||
' <span class="d2h-file-name-wrapper">\n' +
|
||||
' <svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">\n' +
|
||||
' <path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>\n' +
|
||||
' </svg> <span class="d2h-file-name">my/file/name.js</span>\n' +
|
||||
' <span class="d2h-tag d2h-deleted d2h-deleted-tag">DELETED</span></span>\n' +
|
||||
" </div>\n" +
|
||||
' <div class="d2h-file-diff">\n' +
|
||||
' <div class="d2h-code-wrapper">\n' +
|
||||
' <table class="d2h-diff-table">\n' +
|
||||
' <tbody class="d2h-diff-tbody">\n' +
|
||||
" <span>Random Html</span>\n" +
|
||||
" </tbody>\n" +
|
||||
" </table>\n" +
|
||||
" </div>\n" +
|
||||
" </div>\n" +
|
||||
"</div>";
|
||||
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
it("should work for simple renamed file", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||
|
||||
const file = {
|
||||
addedLines: 12,
|
||||
deletedLines: 41,
|
||||
language: "js",
|
||||
oldName: "my/file/name1.js",
|
||||
newName: "my/file/name2.js",
|
||||
isRename: true,
|
||||
isCombined: false,
|
||||
isGitDiff: false,
|
||||
blocks: []
|
||||
};
|
||||
const diffs = "<span>Random Html</span>";
|
||||
|
||||
const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs);
|
||||
|
||||
const expected =
|
||||
'<div id="d2h-662683" class="d2h-file-wrapper" data-lang="js">\n' +
|
||||
' <div class="d2h-file-header">\n' +
|
||||
' <span class="d2h-file-name-wrapper">\n' +
|
||||
' <svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">\n' +
|
||||
' <path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>\n' +
|
||||
' </svg> <span class="d2h-file-name">my/file/{name1.js → name2.js}</span>\n' +
|
||||
' <span class="d2h-tag d2h-moved d2h-moved-tag">RENAMED</span></span>\n' +
|
||||
" </div>\n" +
|
||||
' <div class="d2h-file-diff">\n' +
|
||||
' <div class="d2h-code-wrapper">\n' +
|
||||
' <table class="d2h-diff-table">\n' +
|
||||
' <tbody class="d2h-diff-tbody">\n' +
|
||||
" <span>Random Html</span>\n" +
|
||||
" </tbody>\n" +
|
||||
" </table>\n" +
|
||||
" </div>\n" +
|
||||
" </div>\n" +
|
||||
"</div>";
|
||||
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
it("should return empty when option renderNothingWhenEmpty is true and file blocks not present", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {
|
||||
renderNothingWhenEmpty: true
|
||||
});
|
||||
|
||||
const file = {
|
||||
addedLines: 0,
|
||||
deletedLines: 0,
|
||||
language: "js",
|
||||
oldName: "my/file/name1.js",
|
||||
newName: "my/file/name2.js",
|
||||
isRename: true,
|
||||
isCombined: false,
|
||||
isGitDiff: false,
|
||||
blocks: []
|
||||
};
|
||||
|
||||
const diffs = "<span>Random Html</span>";
|
||||
|
||||
const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs);
|
||||
|
||||
const expected = "";
|
||||
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("generateLineByLineJsonHtml", () => {
|
||||
it("should work for list of files", () => {
|
||||
const exampleJson: DiffFile[] = [
|
||||
{
|
||||
blocks: [
|
||||
{
|
||||
lines: [
|
||||
{
|
||||
content: "-test",
|
||||
type: LineType.DELETE,
|
||||
oldNumber: 1,
|
||||
newNumber: undefined
|
||||
},
|
||||
{
|
||||
content: "+test1r",
|
||||
type: LineType.INSERT,
|
||||
oldNumber: undefined,
|
||||
newNumber: 1
|
||||
}
|
||||
],
|
||||
oldStartLine: 1,
|
||||
oldStartLine2: undefined,
|
||||
newStartLine: 1,
|
||||
header: "@@ -1 +1 @@"
|
||||
}
|
||||
],
|
||||
deletedLines: 1,
|
||||
addedLines: 1,
|
||||
checksumBefore: "0000001",
|
||||
checksumAfter: "0ddf2ba",
|
||||
oldName: "sample",
|
||||
newName: "sample",
|
||||
language: "txt",
|
||||
isCombined: false,
|
||||
isGitDiff: true
|
||||
}
|
||||
];
|
||||
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {
|
||||
matching: LineMatchingType.LINES
|
||||
});
|
||||
const html = lineByLineRenderer.render(exampleJson);
|
||||
const expected =
|
||||
'<div class="d2h-wrapper">\n' +
|
||||
' <div id="d2h-675094" class="d2h-file-wrapper" data-lang="txt">\n' +
|
||||
' <div class="d2h-file-header">\n' +
|
||||
' <span class="d2h-file-name-wrapper">\n' +
|
||||
' <svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">\n' +
|
||||
' <path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>\n' +
|
||||
' </svg> <span class="d2h-file-name">sample</span>\n' +
|
||||
' <span class="d2h-tag d2h-changed d2h-changed-tag">CHANGED</span></span>\n' +
|
||||
" </div>\n" +
|
||||
' <div class="d2h-file-diff">\n' +
|
||||
' <div class="d2h-code-wrapper">\n' +
|
||||
' <table class="d2h-diff-table">\n' +
|
||||
' <tbody class="d2h-diff-tbody">\n' +
|
||||
" <tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-info"></td>\n' +
|
||||
' <td class="d2h-info">\n' +
|
||||
' <div class="d2h-code-line d2h-info">@@ -1 +1 @@</div>\n' +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-del d2h-change">\n' +
|
||||
' <div class="line-num1">1</div>\n' +
|
||||
'<div class="line-num2"></div>\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-del d2h-change">\n' +
|
||||
' <div class="d2h-code-line d2h-del d2h-change">\n' +
|
||||
' <span class="d2h-code-line-prefix">-</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"><del>test</del></span>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-ins d2h-change">\n' +
|
||||
' <div class="line-num1"></div>\n' +
|
||||
'<div class="line-num2">1</div>\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-ins d2h-change">\n' +
|
||||
' <div class="d2h-code-line d2h-ins d2h-change">\n' +
|
||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"><ins>test1r</ins></span>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr>\n" +
|
||||
" </tbody>\n" +
|
||||
" </table>\n" +
|
||||
" </div>\n" +
|
||||
" </div>\n" +
|
||||
"</div>\n" +
|
||||
"</div>";
|
||||
|
||||
expect(html).toEqual(expected);
|
||||
});
|
||||
|
||||
it("should work for empty blocks", () => {
|
||||
const exampleJson = [
|
||||
{
|
||||
blocks: [],
|
||||
deletedLines: 0,
|
||||
addedLines: 0,
|
||||
oldName: "sample",
|
||||
language: "js",
|
||||
newName: "sample",
|
||||
isCombined: false,
|
||||
isGitDiff: false
|
||||
}
|
||||
];
|
||||
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {
|
||||
renderNothingWhenEmpty: false
|
||||
});
|
||||
const html = lineByLineRenderer.render(exampleJson);
|
||||
const expected =
|
||||
'<div class="d2h-wrapper">\n' +
|
||||
' <div id="d2h-675094" class="d2h-file-wrapper" data-lang="js">\n' +
|
||||
' <div class="d2h-file-header">\n' +
|
||||
' <span class="d2h-file-name-wrapper">\n' +
|
||||
' <svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">\n' +
|
||||
' <path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>\n' +
|
||||
' </svg> <span class="d2h-file-name">sample</span>\n' +
|
||||
' <span class="d2h-tag d2h-changed d2h-changed-tag">CHANGED</span></span>\n' +
|
||||
" </div>\n" +
|
||||
' <div class="d2h-file-diff">\n' +
|
||||
' <div class="d2h-code-wrapper">\n' +
|
||||
' <table class="d2h-diff-table">\n' +
|
||||
' <tbody class="d2h-diff-tbody">\n' +
|
||||
" <tr>\n" +
|
||||
' <td class="d2h-info">\n' +
|
||||
' <div class="d2h-code-line d2h-info">\n' +
|
||||
" File without changes\n" +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr>\n" +
|
||||
" </tbody>\n" +
|
||||
" </table>\n" +
|
||||
" </div>\n" +
|
||||
" </div>\n" +
|
||||
"</div>\n" +
|
||||
"</div>";
|
||||
|
||||
expect(html).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("_generateFileHtml", () => {
|
||||
it("should work for simple file", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||
const file: DiffFile = {
|
||||
blocks: [
|
||||
{
|
||||
lines: [
|
||||
{
|
||||
content: " one context line",
|
||||
type: LineType.CONTEXT,
|
||||
oldNumber: 1,
|
||||
newNumber: 1
|
||||
},
|
||||
{
|
||||
content: "-test",
|
||||
type: LineType.DELETE,
|
||||
oldNumber: 2,
|
||||
newNumber: undefined
|
||||
},
|
||||
{
|
||||
content: "+test1r",
|
||||
type: LineType.INSERT,
|
||||
oldNumber: undefined,
|
||||
newNumber: 2
|
||||
},
|
||||
{
|
||||
content: "+test2r",
|
||||
type: LineType.INSERT,
|
||||
oldNumber: undefined,
|
||||
newNumber: 3
|
||||
}
|
||||
],
|
||||
oldStartLine: 1,
|
||||
oldStartLine2: undefined,
|
||||
newStartLine: 1,
|
||||
header: "@@ -1 +1 @@"
|
||||
}
|
||||
],
|
||||
deletedLines: 1,
|
||||
addedLines: 1,
|
||||
checksumBefore: "0000001",
|
||||
checksumAfter: "0ddf2ba",
|
||||
oldName: "sample",
|
||||
language: "txt",
|
||||
newName: "sample",
|
||||
isCombined: false,
|
||||
isGitDiff: true
|
||||
};
|
||||
|
||||
const html = lineByLineRenderer.generateFileHtml(file);
|
||||
|
||||
const expected =
|
||||
"<tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-info"></td>\n' +
|
||||
' <td class="d2h-info">\n' +
|
||||
' <div class="d2h-code-line d2h-info">@@ -1 +1 @@</div>\n' +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-cntx">\n' +
|
||||
' <div class="line-num1">1</div>\n' +
|
||||
'<div class="line-num2">1</div>\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-cntx">\n' +
|
||||
' <div class="d2h-code-line d2h-cntx">\n' +
|
||||
' <span class="d2h-code-line-prefix"> </span>\n' +
|
||||
' <span class="d2h-code-line-ctn">one context line</span>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-del d2h-change">\n' +
|
||||
' <div class="line-num1">2</div>\n' +
|
||||
'<div class="line-num2"></div>\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-del d2h-change">\n' +
|
||||
' <div class="d2h-code-line d2h-del d2h-change">\n' +
|
||||
' <span class="d2h-code-line-prefix">-</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"><del>test</del></span>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-ins d2h-change">\n' +
|
||||
' <div class="line-num1"></div>\n' +
|
||||
'<div class="line-num2">2</div>\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-ins d2h-change">\n' +
|
||||
' <div class="d2h-code-line d2h-ins d2h-change">\n' +
|
||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"><ins>test1r</ins></span>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-ins">\n' +
|
||||
' <div class="line-num1"></div>\n' +
|
||||
'<div class="line-num2">3</div>\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-ins">\n' +
|
||||
' <div class="d2h-code-line d2h-ins">\n' +
|
||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||
' <span class="d2h-code-line-ctn">test2r</span>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr>";
|
||||
|
||||
expect(html).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
159
src/__tests__/printer-utils-tests.ts
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
import { escapeForHtml, getHtmlId, filenameDiff, diffHighlight } from "../render-utils";
|
||||
import { DiffStyleType, LineMatchingType } from "../types";
|
||||
|
||||
describe("Utils", () => {
|
||||
describe("escapeForHtml", () => {
|
||||
it("should escape & with &", () => {
|
||||
const result = escapeForHtml("&");
|
||||
expect(result).toEqual("&");
|
||||
});
|
||||
it("should escape < with <", () => {
|
||||
const result = escapeForHtml("<");
|
||||
expect(result).toEqual("<");
|
||||
});
|
||||
it("should escape > with >", () => {
|
||||
const result = escapeForHtml(">");
|
||||
expect(result).toEqual(">");
|
||||
});
|
||||
it('should escape " with "', () => {
|
||||
const result = escapeForHtml('"');
|
||||
expect(result).toEqual(""");
|
||||
});
|
||||
it("should escape ' with '", () => {
|
||||
const result = escapeForHtml("'");
|
||||
expect(result).toEqual("'");
|
||||
});
|
||||
it("should escape / with /", () => {
|
||||
const result = escapeForHtml("/");
|
||||
expect(result).toEqual("/");
|
||||
});
|
||||
it("should escape a string containing HTML code", () => {
|
||||
const result = escapeForHtml(`<a href="/search?q=diff2html">Search 'Diff2Html'</a>`);
|
||||
expect(result).toEqual(
|
||||
"<a href="/search?q=diff2html">Search 'Diff2Html'</a>"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getHtmlId", () => {
|
||||
it("should generate file unique id", () => {
|
||||
const result = getHtmlId({
|
||||
oldName: "sample.js",
|
||||
newName: "sample.js"
|
||||
});
|
||||
expect("d2h-960013").toEqual(result);
|
||||
});
|
||||
it("should generate file unique id for empty hashes", () => {
|
||||
const result = getHtmlId({
|
||||
oldName: "sample.js",
|
||||
newName: "sample.js"
|
||||
});
|
||||
expect("d2h-960013").toEqual(result);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getDiffName", () => {
|
||||
it("should generate the file name for a changed file", () => {
|
||||
const result = filenameDiff({
|
||||
oldName: "sample.js",
|
||||
newName: "sample.js"
|
||||
});
|
||||
expect("sample.js").toEqual(result);
|
||||
});
|
||||
it("should generate the file name for a changed file and full rename", () => {
|
||||
const result = filenameDiff({
|
||||
oldName: "sample1.js",
|
||||
newName: "sample2.js"
|
||||
});
|
||||
expect("sample1.js → sample2.js").toEqual(result);
|
||||
});
|
||||
it("should generate the file name for a changed file and prefix rename", () => {
|
||||
const result = filenameDiff({
|
||||
oldName: "src/path/sample.js",
|
||||
newName: "source/path/sample.js"
|
||||
});
|
||||
expect("{src → source}/path/sample.js").toEqual(result);
|
||||
});
|
||||
it("should generate the file name for a changed file and suffix rename", () => {
|
||||
const result = filenameDiff({
|
||||
oldName: "src/path/sample1.js",
|
||||
newName: "src/path/sample2.js"
|
||||
});
|
||||
expect("src/path/{sample1.js → sample2.js}").toEqual(result);
|
||||
});
|
||||
it("should generate the file name for a changed file and middle rename", () => {
|
||||
const result = filenameDiff({
|
||||
oldName: "src/really/big/path/sample.js",
|
||||
newName: "src/small/path/sample.js"
|
||||
});
|
||||
expect("src/{really/big → small}/path/sample.js").toEqual(result);
|
||||
});
|
||||
it("should generate the file name for a deleted file", () => {
|
||||
const result = filenameDiff({
|
||||
oldName: "src/my/file.js",
|
||||
newName: "/dev/null"
|
||||
});
|
||||
expect("src/my/file.js").toEqual(result);
|
||||
});
|
||||
it("should generate the file name for a new file", () => {
|
||||
const result = filenameDiff({
|
||||
oldName: "/dev/null",
|
||||
newName: "src/my/file.js"
|
||||
});
|
||||
expect("src/my/file.js").toEqual(result);
|
||||
});
|
||||
});
|
||||
|
||||
describe("diffHighlight", () => {
|
||||
it("should highlight two lines", () => {
|
||||
const result = diffHighlight("-var myVar = 2;", "+var myVariable = 3;", false, {
|
||||
matching: LineMatchingType.WORDS
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
oldLine: {
|
||||
prefix: "-",
|
||||
content: "var <del>myVar</del> = <del>2</del>;"
|
||||
},
|
||||
newLine: {
|
||||
prefix: "+",
|
||||
content: "var <ins>myVariable</ins> = <ins>3</ins>;"
|
||||
}
|
||||
});
|
||||
});
|
||||
it("should highlight two lines char by char", () => {
|
||||
const result = diffHighlight("-var myVar = 2;", "+var myVariable = 3;", false, {
|
||||
diffStyle: DiffStyleType.CHAR
|
||||
});
|
||||
|
||||
expect({
|
||||
oldLine: {
|
||||
prefix: "-",
|
||||
content: "var myVar = <del>2</del>;"
|
||||
},
|
||||
newLine: {
|
||||
prefix: "+",
|
||||
content: "var myVar<ins>iable</ins> = <ins>3</ins>;"
|
||||
}
|
||||
}).toEqual(result);
|
||||
});
|
||||
it("should highlight combined diff lines", () => {
|
||||
const result = diffHighlight(" -var myVar = 2;", " +var myVariable = 3;", true, {
|
||||
diffStyle: DiffStyleType.WORD,
|
||||
matching: LineMatchingType.WORDS,
|
||||
matchWordsThreshold: 1.0
|
||||
});
|
||||
|
||||
expect({
|
||||
oldLine: {
|
||||
prefix: " -",
|
||||
content: 'var <del class="d2h-change">myVar</del> = <del class="d2h-change">2</del>;'
|
||||
},
|
||||
newLine: {
|
||||
prefix: " +",
|
||||
content: 'var <ins class="d2h-change">myVariable</ins> = <ins class="d2h-change">3</ins>;'
|
||||
}
|
||||
}).toEqual(result);
|
||||
});
|
||||
});
|
||||
});
|
||||
464
src/__tests__/side-by-side-printer-tests.ts
Normal file
|
|
@ -0,0 +1,464 @@
|
|||
import SideBySideRenderer from "../side-by-side-renderer";
|
||||
import HoganJsUtils from "../hoganjs-utils";
|
||||
import { LineType, DiffLine, DiffFile, LineMatchingType } from "../types";
|
||||
import { CSSLineClass } from "../render-utils";
|
||||
|
||||
describe("SideBySideRenderer", () => {
|
||||
describe("generateEmptyDiff", () => {
|
||||
it("should return an empty diff", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
|
||||
const fileHtml = sideBySideRenderer.generateEmptyDiff();
|
||||
const expectedRight = "";
|
||||
const expectedLeft =
|
||||
"<tr>\n" +
|
||||
' <td class="d2h-info">\n' +
|
||||
' <div class="d2h-code-side-line d2h-info">\n' +
|
||||
" File without changes\n" +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr>";
|
||||
|
||||
expect(fileHtml.right).toEqual(expectedRight);
|
||||
expect(fileHtml.left).toEqual(expectedLeft);
|
||||
});
|
||||
});
|
||||
|
||||
describe("generateSideBySideFileHtml", () => {
|
||||
it("should generate lines with the right prefixes", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
|
||||
|
||||
const file: DiffFile = {
|
||||
isGitDiff: true,
|
||||
blocks: [
|
||||
{
|
||||
lines: [
|
||||
{
|
||||
content: " context",
|
||||
type: LineType.CONTEXT,
|
||||
oldNumber: 19,
|
||||
newNumber: 19
|
||||
},
|
||||
{
|
||||
content: "-removed",
|
||||
type: LineType.DELETE,
|
||||
oldNumber: 20,
|
||||
newNumber: undefined
|
||||
},
|
||||
{
|
||||
content: "+added",
|
||||
type: LineType.INSERT,
|
||||
oldNumber: undefined,
|
||||
newNumber: 20
|
||||
},
|
||||
{
|
||||
content: "+another added",
|
||||
type: LineType.INSERT,
|
||||
oldNumber: undefined,
|
||||
newNumber: 21
|
||||
}
|
||||
],
|
||||
oldStartLine: 19,
|
||||
newStartLine: 19,
|
||||
header: "@@ -19,7 +19,7 @@"
|
||||
}
|
||||
],
|
||||
deletedLines: 1,
|
||||
addedLines: 2,
|
||||
checksumBefore: "fc56817",
|
||||
checksumAfter: "e8e7e49",
|
||||
mode: "100644",
|
||||
oldName: "coverage.init",
|
||||
language: "init",
|
||||
newName: "coverage.init",
|
||||
isCombined: false
|
||||
};
|
||||
|
||||
const fileHtml = sideBySideRenderer.generateFileHtml(file);
|
||||
|
||||
const expectedLeft =
|
||||
"<tr>\n" +
|
||||
' <td class="d2h-code-side-linenumber d2h-info"></td>\n' +
|
||||
' <td class="d2h-info">\n' +
|
||||
' <div class="d2h-code-side-line d2h-info">@@ -19,7 +19,7 @@</div>\n' +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-side-linenumber d2h-cntx">\n' +
|
||||
" 19\n" +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-cntx">\n' +
|
||||
' <div class="d2h-code-side-line d2h-cntx">\n' +
|
||||
' <span class="d2h-code-line-prefix"> </span>\n' +
|
||||
' <span class="d2h-code-line-ctn">context</span>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-side-linenumber d2h-del d2h-change">\n' +
|
||||
" 20\n" +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-del d2h-change">\n' +
|
||||
' <div class="d2h-code-side-line d2h-del d2h-change">\n' +
|
||||
' <span class="d2h-code-line-prefix">-</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"><del>removed</del></span>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-side-linenumber d2h-code-side-emptyplaceholder d2h-cntx d2h-emptyplaceholder">\n' +
|
||||
" " +
|
||||
"\n" +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-cntx d2h-emptyplaceholder">\n' +
|
||||
' <div class="d2h-code-side-line d2h-code-side-emptyplaceholder d2h-cntx d2h-emptyplaceholder">\n' +
|
||||
' <span class="d2h-code-line-prefix"> </span>\n' +
|
||||
' <span class="d2h-code-line-ctn"> </span>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr>";
|
||||
|
||||
const expectedRight =
|
||||
"<tr>\n" +
|
||||
' <td class="d2h-code-side-linenumber d2h-info"></td>\n' +
|
||||
' <td class="d2h-info">\n' +
|
||||
' <div class="d2h-code-side-line d2h-info"></div>\n' +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-side-linenumber d2h-cntx">\n' +
|
||||
" 19\n" +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-cntx">\n' +
|
||||
' <div class="d2h-code-side-line d2h-cntx">\n' +
|
||||
' <span class="d2h-code-line-prefix"> </span>\n' +
|
||||
' <span class="d2h-code-line-ctn">context</span>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-side-linenumber d2h-ins d2h-change">\n' +
|
||||
" 20\n" +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-ins d2h-change">\n' +
|
||||
' <div class="d2h-code-side-line d2h-ins d2h-change">\n' +
|
||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"><ins>added</ins></span>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-side-linenumber d2h-ins">\n' +
|
||||
" 21\n" +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-ins">\n' +
|
||||
' <div class="d2h-code-side-line d2h-ins">\n' +
|
||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||
' <span class="d2h-code-line-ctn">another added</span>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr>";
|
||||
|
||||
expect(fileHtml.left).toEqual(expectedLeft);
|
||||
expect(fileHtml.right).toEqual(expectedRight);
|
||||
});
|
||||
});
|
||||
|
||||
describe("generateSingleLineHtml", () => {
|
||||
it("should work for insertions", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
|
||||
const fileHtml = sideBySideRenderer.generateLineHtml(undefined, {
|
||||
type: CSSLineClass.INSERTS,
|
||||
prefix: "+",
|
||||
content: "test",
|
||||
number: 30
|
||||
});
|
||||
|
||||
const expected = {
|
||||
left:
|
||||
"<tr>\n" +
|
||||
' <td class="d2h-code-side-linenumber d2h-code-side-emptyplaceholder d2h-cntx d2h-emptyplaceholder">\n' +
|
||||
" \n" +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-cntx d2h-emptyplaceholder">\n' +
|
||||
' <div class="d2h-code-side-line d2h-code-side-emptyplaceholder d2h-cntx d2h-emptyplaceholder">\n' +
|
||||
' <span class="d2h-code-line-prefix"> </span>\n' +
|
||||
' <span class="d2h-code-line-ctn"> </span>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr>",
|
||||
right:
|
||||
"<tr>\n" +
|
||||
' <td class="d2h-code-side-linenumber d2h-ins">\n' +
|
||||
" 30\n" +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-ins">\n' +
|
||||
' <div class="d2h-code-side-line d2h-ins">\n' +
|
||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||
' <span class="d2h-code-line-ctn">test</span>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr>"
|
||||
};
|
||||
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
it("should work for deletions", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
|
||||
const fileHtml = sideBySideRenderer.generateLineHtml(
|
||||
{
|
||||
type: CSSLineClass.DELETES,
|
||||
prefix: "-",
|
||||
content: "test",
|
||||
number: 30
|
||||
},
|
||||
undefined
|
||||
);
|
||||
const expected = {
|
||||
left:
|
||||
"<tr>\n" +
|
||||
' <td class="d2h-code-side-linenumber d2h-del">\n' +
|
||||
" 30\n" +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-del">\n' +
|
||||
' <div class="d2h-code-side-line d2h-del">\n' +
|
||||
' <span class="d2h-code-line-prefix">-</span>\n' +
|
||||
' <span class="d2h-code-line-ctn">test</span>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr>",
|
||||
right:
|
||||
"<tr>\n" +
|
||||
' <td class="d2h-code-side-linenumber d2h-code-side-emptyplaceholder d2h-cntx d2h-emptyplaceholder">\n' +
|
||||
" \n" +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-cntx d2h-emptyplaceholder">\n' +
|
||||
' <div class="d2h-code-side-line d2h-code-side-emptyplaceholder d2h-cntx d2h-emptyplaceholder">\n' +
|
||||
' <span class="d2h-code-line-prefix"> </span>\n' +
|
||||
' <span class="d2h-code-line-ctn"> </span>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr>"
|
||||
};
|
||||
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("generateSideBySideJsonHtml", () => {
|
||||
it("should work for list of files", () => {
|
||||
const exampleJson: DiffFile[] = [
|
||||
{
|
||||
blocks: [
|
||||
{
|
||||
lines: [
|
||||
{
|
||||
content: "-test",
|
||||
type: LineType.DELETE,
|
||||
oldNumber: 1,
|
||||
newNumber: undefined
|
||||
},
|
||||
{
|
||||
content: "+test1r",
|
||||
type: LineType.INSERT,
|
||||
oldNumber: undefined,
|
||||
newNumber: 1
|
||||
}
|
||||
],
|
||||
oldStartLine: 1,
|
||||
oldStartLine2: undefined,
|
||||
newStartLine: 1,
|
||||
header: "@@ -1 +1 @@"
|
||||
}
|
||||
],
|
||||
deletedLines: 1,
|
||||
addedLines: 1,
|
||||
checksumBefore: "0000001",
|
||||
checksumAfter: "0ddf2ba",
|
||||
oldName: "sample",
|
||||
language: "txt",
|
||||
newName: "sample",
|
||||
isCombined: false,
|
||||
isGitDiff: true
|
||||
}
|
||||
];
|
||||
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, { matching: LineMatchingType.LINES });
|
||||
const html = sideBySideRenderer.render(exampleJson);
|
||||
const expected =
|
||||
'<div class="d2h-wrapper">\n' +
|
||||
' <div id="d2h-675094" class="d2h-file-wrapper" data-lang="txt">\n' +
|
||||
' <div class="d2h-file-header">\n' +
|
||||
' <span class="d2h-file-name-wrapper">\n' +
|
||||
' <svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">\n' +
|
||||
' <path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>\n' +
|
||||
' </svg> <span class="d2h-file-name">sample</span>\n' +
|
||||
' <span class="d2h-tag d2h-changed d2h-changed-tag">CHANGED</span></span>\n' +
|
||||
" </div>\n" +
|
||||
' <div class="d2h-files-diff">\n' +
|
||||
' <div class="d2h-file-side-diff">\n' +
|
||||
' <div class="d2h-code-wrapper">\n' +
|
||||
' <table class="d2h-diff-table">\n' +
|
||||
' <tbody class="d2h-diff-tbody">\n' +
|
||||
" <tr>\n" +
|
||||
' <td class="d2h-code-side-linenumber d2h-info"></td>\n' +
|
||||
' <td class="d2h-info">\n' +
|
||||
' <div class="d2h-code-side-line d2h-info">@@ -1 +1 @@</div>\n' +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-side-linenumber d2h-del d2h-change">\n' +
|
||||
" 1\n" +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-del d2h-change">\n' +
|
||||
' <div class="d2h-code-side-line d2h-del d2h-change">\n' +
|
||||
' <span class="d2h-code-line-prefix">-</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"><del>test</del></span>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr>\n" +
|
||||
" </tbody>\n" +
|
||||
" </table>\n" +
|
||||
" </div>\n" +
|
||||
" </div>\n" +
|
||||
' <div class="d2h-file-side-diff">\n' +
|
||||
' <div class="d2h-code-wrapper">\n' +
|
||||
' <table class="d2h-diff-table">\n' +
|
||||
' <tbody class="d2h-diff-tbody">\n' +
|
||||
" <tr>\n" +
|
||||
' <td class="d2h-code-side-linenumber d2h-info"></td>\n' +
|
||||
' <td class="d2h-info">\n' +
|
||||
' <div class="d2h-code-side-line d2h-info"></div>\n' +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-side-linenumber d2h-ins d2h-change">\n' +
|
||||
" 1\n" +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-ins d2h-change">\n' +
|
||||
' <div class="d2h-code-side-line d2h-ins d2h-change">\n' +
|
||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"><ins>test1r</ins></span>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr>\n" +
|
||||
" </tbody>\n" +
|
||||
" </table>\n" +
|
||||
" </div>\n" +
|
||||
" </div>\n" +
|
||||
" </div>\n" +
|
||||
"</div>\n" +
|
||||
"</div>";
|
||||
|
||||
expect(html).toEqual(expected);
|
||||
});
|
||||
it("should work for files without blocks", () => {
|
||||
const exampleJson: DiffFile[] = [
|
||||
{
|
||||
blocks: [],
|
||||
oldName: "sample",
|
||||
language: "js",
|
||||
newName: "sample",
|
||||
isCombined: false,
|
||||
addedLines: 0,
|
||||
deletedLines: 0,
|
||||
isGitDiff: false
|
||||
}
|
||||
];
|
||||
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
|
||||
const html = sideBySideRenderer.render(exampleJson);
|
||||
const expected =
|
||||
'<div class="d2h-wrapper">\n' +
|
||||
' <div id="d2h-675094" class="d2h-file-wrapper" data-lang="js">\n' +
|
||||
' <div class="d2h-file-header">\n' +
|
||||
' <span class="d2h-file-name-wrapper">\n' +
|
||||
' <svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">\n' +
|
||||
' <path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>\n' +
|
||||
' </svg> <span class="d2h-file-name">sample</span>\n' +
|
||||
' <span class="d2h-tag d2h-changed d2h-changed-tag">CHANGED</span></span>\n' +
|
||||
" </div>\n" +
|
||||
' <div class="d2h-files-diff">\n' +
|
||||
' <div class="d2h-file-side-diff">\n' +
|
||||
' <div class="d2h-code-wrapper">\n' +
|
||||
' <table class="d2h-diff-table">\n' +
|
||||
' <tbody class="d2h-diff-tbody">\n' +
|
||||
" <tr>\n" +
|
||||
' <td class="d2h-info">\n' +
|
||||
' <div class="d2h-code-side-line d2h-info">\n' +
|
||||
" File without changes\n" +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr>\n" +
|
||||
" </tbody>\n" +
|
||||
" </table>\n" +
|
||||
" </div>\n" +
|
||||
" </div>\n" +
|
||||
' <div class="d2h-file-side-diff">\n' +
|
||||
' <div class="d2h-code-wrapper">\n' +
|
||||
' <table class="d2h-diff-table">\n' +
|
||||
' <tbody class="d2h-diff-tbody">\n' +
|
||||
" \n" +
|
||||
" </tbody>\n" +
|
||||
" </table>\n" +
|
||||
" </div>\n" +
|
||||
" </div>\n" +
|
||||
" </div>\n" +
|
||||
"</div>\n" +
|
||||
"</div>";
|
||||
|
||||
expect(html).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("processLines", () => {
|
||||
it("should process file lines", () => {
|
||||
const oldLines: DiffLine[] = [
|
||||
{
|
||||
content: "-test",
|
||||
type: LineType.DELETE,
|
||||
oldNumber: 1,
|
||||
newNumber: undefined
|
||||
}
|
||||
];
|
||||
|
||||
const newLines: DiffLine[] = [
|
||||
{
|
||||
content: "+test1r",
|
||||
type: LineType.INSERT,
|
||||
oldNumber: undefined,
|
||||
newNumber: 1
|
||||
}
|
||||
];
|
||||
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, { matching: LineMatchingType.LINES });
|
||||
const html = sideBySideRenderer.processChangedLines(false, oldLines, newLines);
|
||||
const expected = {
|
||||
left:
|
||||
"<tr>\n" +
|
||||
' <td class="d2h-code-side-linenumber d2h-del d2h-change">\n' +
|
||||
" 1\n" +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-del d2h-change">\n' +
|
||||
' <div class="d2h-code-side-line d2h-del d2h-change">\n' +
|
||||
' <span class="d2h-code-line-prefix">-</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"><del>test</del></span>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr>",
|
||||
right:
|
||||
"<tr>\n" +
|
||||
' <td class="d2h-code-side-linenumber d2h-ins d2h-change">\n' +
|
||||
" 1\n" +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-ins d2h-change">\n' +
|
||||
' <div class="d2h-code-side-line d2h-ins d2h-change">\n' +
|
||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"><ins>test1r</ins></span>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr>"
|
||||
};
|
||||
|
||||
expect(html).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
31
src/__tests__/utils-tests.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { escapeForRegExp, unifyPath, hashCode } from "../utils";
|
||||
|
||||
describe("Utils", () => {
|
||||
describe("escapeForRegExp", () => {
|
||||
it("should escape markdown link text", () => {
|
||||
const result = escapeForRegExp("[Link](https://diff2html.xyz)");
|
||||
expect(result).toEqual("\\[Link\\]\\(https:\\/\\/diff2html\\.xyz\\)");
|
||||
});
|
||||
it("should escape all dangerous characters", () => {
|
||||
const result = escapeForRegExp("-[]/{}()*+?.\\^$|");
|
||||
expect(result).toEqual("\\-\\[\\]\\/\\{\\}\\(\\)\\*\\+\\?\\.\\\\\\^\\$\\|");
|
||||
});
|
||||
});
|
||||
|
||||
describe("unifyPath", () => {
|
||||
it("should unify windows style path", () => {
|
||||
const result = unifyPath("\\Users\\Downloads\\diff.html");
|
||||
expect(result).toEqual("/Users/Downloads/diff.html");
|
||||
});
|
||||
});
|
||||
|
||||
describe("hashCode", () => {
|
||||
it("should create consistent hash for a text piece", () => {
|
||||
const string = "/home/diff2html/diff.html";
|
||||
expect(hashCode(string)).toEqual(hashCode(string));
|
||||
});
|
||||
it("should create different hash for different text pieces", () => {
|
||||
expect(hashCode("/home/diff2html/diff1.html")).not.toEqual(hashCode("/home/diff2html/diff2.html"));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,445 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* Diff Parser (diff-parser.js)
|
||||
* Author: rtfpessoa
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var utils = require('./utils.js').Utils;
|
||||
|
||||
var LINE_TYPE = {
|
||||
INSERTS: 'd2h-ins',
|
||||
DELETES: 'd2h-del',
|
||||
INSERT_CHANGES: 'd2h-ins d2h-change',
|
||||
DELETE_CHANGES: 'd2h-del d2h-change',
|
||||
CONTEXT: 'd2h-cntx',
|
||||
INFO: 'd2h-info'
|
||||
};
|
||||
|
||||
function DiffParser() {
|
||||
}
|
||||
|
||||
DiffParser.prototype.LINE_TYPE = LINE_TYPE;
|
||||
|
||||
DiffParser.prototype.generateDiffJson = function(diffInput, configuration) {
|
||||
var config = configuration || {};
|
||||
|
||||
var files = [];
|
||||
var currentFile = null;
|
||||
var currentBlock = null;
|
||||
var oldLine = null;
|
||||
var oldLine2 = null; // Used for combined diff
|
||||
var newLine = null;
|
||||
|
||||
var possibleOldName;
|
||||
var possibleNewName;
|
||||
|
||||
/* Diff Header */
|
||||
var oldFileNameHeader = '--- ';
|
||||
var newFileNameHeader = '+++ ';
|
||||
var hunkHeaderPrefix = '@@';
|
||||
|
||||
/* Add previous block(if exists) before start a new file */
|
||||
function saveBlock() {
|
||||
if (currentBlock) {
|
||||
currentFile.blocks.push(currentBlock);
|
||||
currentBlock = null;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Add previous file(if exists) before start a new one
|
||||
* if it has name (to avoid binary files errors)
|
||||
*/
|
||||
function saveFile() {
|
||||
if (currentFile) {
|
||||
if (!currentFile.oldName) {
|
||||
currentFile.oldName = possibleOldName;
|
||||
}
|
||||
|
||||
if (!currentFile.newName) {
|
||||
currentFile.newName = possibleNewName;
|
||||
}
|
||||
|
||||
if (currentFile.newName) {
|
||||
files.push(currentFile);
|
||||
currentFile = null;
|
||||
}
|
||||
}
|
||||
|
||||
possibleOldName = undefined;
|
||||
possibleNewName = undefined;
|
||||
}
|
||||
|
||||
/* Create file structure */
|
||||
function startFile() {
|
||||
saveBlock();
|
||||
saveFile();
|
||||
|
||||
currentFile = {};
|
||||
currentFile.blocks = [];
|
||||
currentFile.deletedLines = 0;
|
||||
currentFile.addedLines = 0;
|
||||
}
|
||||
|
||||
function startBlock(line) {
|
||||
saveBlock();
|
||||
|
||||
var values;
|
||||
|
||||
/**
|
||||
* From Range:
|
||||
* -<start line>[,<number of lines>]
|
||||
*
|
||||
* To Range:
|
||||
* +<start line>[,<number of lines>]
|
||||
*
|
||||
* @@ from-file-range to-file-range @@
|
||||
*
|
||||
* @@@ from-file-range from-file-range to-file-range @@@
|
||||
*
|
||||
* number of lines is optional, if omited consider 0
|
||||
*/
|
||||
|
||||
if ((values = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@.*/.exec(line))) {
|
||||
currentFile.isCombined = false;
|
||||
oldLine = values[1];
|
||||
newLine = values[2];
|
||||
} else if ((values = /^@@@ -(\d+)(?:,\d+)? -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@@.*/.exec(line))) {
|
||||
currentFile.isCombined = true;
|
||||
oldLine = values[1];
|
||||
oldLine2 = values[2];
|
||||
newLine = values[3];
|
||||
} else {
|
||||
if (utils.startsWith(line, hunkHeaderPrefix)) {
|
||||
console.error('Failed to parse lines, starting in 0!');
|
||||
}
|
||||
|
||||
oldLine = 0;
|
||||
newLine = 0;
|
||||
currentFile.isCombined = false;
|
||||
}
|
||||
|
||||
/* Create block metadata */
|
||||
currentBlock = {};
|
||||
currentBlock.lines = [];
|
||||
currentBlock.oldStartLine = oldLine;
|
||||
currentBlock.oldStartLine2 = oldLine2;
|
||||
currentBlock.newStartLine = newLine;
|
||||
currentBlock.header = line;
|
||||
}
|
||||
|
||||
function createLine(line) {
|
||||
var currentLine = {};
|
||||
currentLine.content = line;
|
||||
|
||||
var newLinePrefixes = !currentFile.isCombined ? ['+'] : ['+', ' +'];
|
||||
var delLinePrefixes = !currentFile.isCombined ? ['-'] : ['-', ' -'];
|
||||
|
||||
/* Fill the line data */
|
||||
if (utils.startsWith(line, newLinePrefixes)) {
|
||||
currentFile.addedLines++;
|
||||
|
||||
currentLine.type = LINE_TYPE.INSERTS;
|
||||
currentLine.oldNumber = null;
|
||||
currentLine.newNumber = newLine++;
|
||||
|
||||
currentBlock.lines.push(currentLine);
|
||||
} else if (utils.startsWith(line, delLinePrefixes)) {
|
||||
currentFile.deletedLines++;
|
||||
|
||||
currentLine.type = LINE_TYPE.DELETES;
|
||||
currentLine.oldNumber = oldLine++;
|
||||
currentLine.newNumber = null;
|
||||
|
||||
currentBlock.lines.push(currentLine);
|
||||
} else {
|
||||
currentLine.type = LINE_TYPE.CONTEXT;
|
||||
currentLine.oldNumber = oldLine++;
|
||||
currentLine.newNumber = newLine++;
|
||||
|
||||
currentBlock.lines.push(currentLine);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if there is a hunk header coming before a new file starts
|
||||
*
|
||||
* Hunk header is a group of three lines started by ( `--- ` , `+++ ` , `@@` )
|
||||
*/
|
||||
function existHunkHeader(line, lineIdx) {
|
||||
var idx = lineIdx;
|
||||
|
||||
while (idx < diffLines.length - 3) {
|
||||
if (utils.startsWith(line, 'diff')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
utils.startsWith(diffLines[idx], oldFileNameHeader) &&
|
||||
utils.startsWith(diffLines[idx + 1], newFileNameHeader) &&
|
||||
utils.startsWith(diffLines[idx + 2], hunkHeaderPrefix)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var diffLines =
|
||||
diffInput.replace(/\\ No newline at end of file/g, '')
|
||||
.replace(/\r\n?/g, '\n')
|
||||
.split('\n');
|
||||
|
||||
/* Diff */
|
||||
var oldMode = /^old mode (\d{6})/;
|
||||
var newMode = /^new mode (\d{6})/;
|
||||
var deletedFileMode = /^deleted file mode (\d{6})/;
|
||||
var newFileMode = /^new file mode (\d{6})/;
|
||||
|
||||
var copyFrom = /^copy from "?(.+)"?/;
|
||||
var copyTo = /^copy to "?(.+)"?/;
|
||||
|
||||
var renameFrom = /^rename from "?(.+)"?/;
|
||||
var renameTo = /^rename to "?(.+)"?/;
|
||||
|
||||
var similarityIndex = /^similarity index (\d+)%/;
|
||||
var dissimilarityIndex = /^dissimilarity index (\d+)%/;
|
||||
var index = /^index ([0-9a-z]+)\.\.([0-9a-z]+)\s*(\d{6})?/;
|
||||
|
||||
var binaryFiles = /^Binary files (.*) and (.*) differ/;
|
||||
var binaryDiff = /^GIT binary patch/;
|
||||
|
||||
/* Combined Diff */
|
||||
var combinedIndex = /^index ([0-9a-z]+),([0-9a-z]+)\.\.([0-9a-z]+)/;
|
||||
var combinedMode = /^mode (\d{6}),(\d{6})\.\.(\d{6})/;
|
||||
var combinedNewFile = /^new file mode (\d{6})/;
|
||||
var combinedDeletedFile = /^deleted file mode (\d{6}),(\d{6})/;
|
||||
|
||||
diffLines.forEach(function(line, lineIndex) {
|
||||
// Unmerged paths, and possibly other non-diffable files
|
||||
// https://github.com/scottgonzalez/pretty-diff/issues/11
|
||||
// Also, remove some useless lines
|
||||
if (!line || utils.startsWith(line, '*')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Used to store regex capture groups
|
||||
var values;
|
||||
|
||||
var prevLine = diffLines[lineIndex - 1];
|
||||
var nxtLine = diffLines[lineIndex + 1];
|
||||
var afterNxtLine = diffLines[lineIndex + 2];
|
||||
|
||||
if (utils.startsWith(line, 'diff')) {
|
||||
startFile();
|
||||
|
||||
// diff --git a/blocked_delta_results.png b/blocked_delta_results.png
|
||||
var gitDiffStart = /^diff --git "?(.+)"? "?(.+)"?/;
|
||||
if ((values = gitDiffStart.exec(line))) {
|
||||
possibleOldName = _getFilename(null, values[1], config.dstPrefix);
|
||||
possibleNewName = _getFilename(null, values[2], config.srcPrefix);
|
||||
}
|
||||
|
||||
currentFile.isGitDiff = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentFile || // If we do not have a file yet, we should crete one
|
||||
(
|
||||
!currentFile.isGitDiff && currentFile && // If we already have some file in progress and
|
||||
(
|
||||
utils.startsWith(line, oldFileNameHeader) && // If we get to an old file path header line
|
||||
// And is followed by the new file path header line and the hunk header line
|
||||
utils.startsWith(nxtLine, newFileNameHeader) && utils.startsWith(afterNxtLine, hunkHeaderPrefix)
|
||||
)
|
||||
)
|
||||
) {
|
||||
startFile();
|
||||
}
|
||||
|
||||
/*
|
||||
* We need to make sure that we have the three lines of the header.
|
||||
* This avoids cases like the ones described in:
|
||||
* - https://github.com/rtfpessoa/diff2html/issues/87
|
||||
*/
|
||||
if (
|
||||
(utils.startsWith(line, oldFileNameHeader) &&
|
||||
utils.startsWith(nxtLine, newFileNameHeader)) ||
|
||||
|
||||
(utils.startsWith(line, newFileNameHeader) &&
|
||||
utils.startsWith(prevLine, oldFileNameHeader))
|
||||
) {
|
||||
/*
|
||||
* --- Date Timestamp[FractionalSeconds] TimeZone
|
||||
* --- 2002-02-21 23:30:39.942229878 -0800
|
||||
*/
|
||||
if (currentFile && !currentFile.oldName &&
|
||||
utils.startsWith(line, '--- ') && (values = getSrcFilename(line, config))) {
|
||||
currentFile.oldName = values;
|
||||
currentFile.language = getExtension(currentFile.oldName, currentFile.language);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* +++ Date Timestamp[FractionalSeconds] TimeZone
|
||||
* +++ 2002-02-21 23:30:39.942229878 -0800
|
||||
*/
|
||||
if (currentFile && !currentFile.newName &&
|
||||
utils.startsWith(line, '+++ ') && (values = getDstFilename(line, config))) {
|
||||
currentFile.newName = values;
|
||||
currentFile.language = getExtension(currentFile.newName, currentFile.language);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(currentFile && utils.startsWith(line, hunkHeaderPrefix)) ||
|
||||
(currentFile.isGitDiff && currentFile && currentFile.oldName && currentFile.newName && !currentBlock)
|
||||
) {
|
||||
startBlock(line);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* There are three types of diff lines. These lines are defined by the way they start.
|
||||
* 1. New line starts with: +
|
||||
* 2. Old line starts with: -
|
||||
* 3. Context line starts with: <SPACE>
|
||||
*/
|
||||
if (currentBlock && (utils.startsWith(line, '+') || utils.startsWith(line, '-') || utils.startsWith(line, ' '))) {
|
||||
createLine(line);
|
||||
return;
|
||||
}
|
||||
|
||||
var doesNotExistHunkHeader = !existHunkHeader(line, lineIndex);
|
||||
|
||||
/*
|
||||
* Git diffs provide more information regarding files modes, renames, copies,
|
||||
* commits between changes and similarity indexes
|
||||
*/
|
||||
if ((values = oldMode.exec(line))) {
|
||||
currentFile.oldMode = values[1];
|
||||
} else if ((values = newMode.exec(line))) {
|
||||
currentFile.newMode = values[1];
|
||||
} else if ((values = deletedFileMode.exec(line))) {
|
||||
currentFile.deletedFileMode = values[1];
|
||||
currentFile.isDeleted = true;
|
||||
} else if ((values = newFileMode.exec(line))) {
|
||||
currentFile.newFileMode = values[1];
|
||||
currentFile.isNew = true;
|
||||
} else if ((values = copyFrom.exec(line))) {
|
||||
if (doesNotExistHunkHeader) {
|
||||
currentFile.oldName = values[1];
|
||||
}
|
||||
currentFile.isCopy = true;
|
||||
} else if ((values = copyTo.exec(line))) {
|
||||
if (doesNotExistHunkHeader) {
|
||||
currentFile.newName = values[1];
|
||||
}
|
||||
currentFile.isCopy = true;
|
||||
} else if ((values = renameFrom.exec(line))) {
|
||||
if (doesNotExistHunkHeader) {
|
||||
currentFile.oldName = values[1];
|
||||
}
|
||||
currentFile.isRename = true;
|
||||
} else if ((values = renameTo.exec(line))) {
|
||||
if (doesNotExistHunkHeader) {
|
||||
currentFile.newName = values[1];
|
||||
}
|
||||
currentFile.isRename = true;
|
||||
} else if ((values = binaryFiles.exec(line))) {
|
||||
currentFile.isBinary = true;
|
||||
currentFile.oldName = _getFilename(null, values[1], config.srcPrefix);
|
||||
currentFile.newName = _getFilename(null, values[2], config.dstPrefix);
|
||||
startBlock('Binary file');
|
||||
} else if ((values = binaryDiff.exec(line))) {
|
||||
currentFile.isBinary = true;
|
||||
startBlock(line);
|
||||
} else if ((values = similarityIndex.exec(line))) {
|
||||
currentFile.unchangedPercentage = values[1];
|
||||
} else if ((values = dissimilarityIndex.exec(line))) {
|
||||
currentFile.changedPercentage = values[1];
|
||||
} else if ((values = index.exec(line))) {
|
||||
currentFile.checksumBefore = values[1];
|
||||
currentFile.checksumAfter = values[2];
|
||||
values[3] && (currentFile.mode = values[3]);
|
||||
} else if ((values = combinedIndex.exec(line))) {
|
||||
currentFile.checksumBefore = [values[2], values[3]];
|
||||
currentFile.checksumAfter = values[1];
|
||||
} else if ((values = combinedMode.exec(line))) {
|
||||
currentFile.oldMode = [values[2], values[3]];
|
||||
currentFile.newMode = values[1];
|
||||
} else if ((values = combinedNewFile.exec(line))) {
|
||||
currentFile.newFileMode = values[1];
|
||||
currentFile.isNew = true;
|
||||
} else if ((values = combinedDeletedFile.exec(line))) {
|
||||
currentFile.deletedFileMode = values[1];
|
||||
currentFile.isDeleted = true;
|
||||
}
|
||||
});
|
||||
|
||||
saveBlock();
|
||||
saveFile();
|
||||
|
||||
return files;
|
||||
};
|
||||
|
||||
function getExtension(filename, language) {
|
||||
var nameSplit = filename.split('.');
|
||||
if (nameSplit.length > 1) {
|
||||
return nameSplit[nameSplit.length - 1];
|
||||
}
|
||||
|
||||
return language;
|
||||
}
|
||||
|
||||
function getSrcFilename(line, cfg) {
|
||||
return _getFilename('---', line, cfg.srcPrefix);
|
||||
}
|
||||
|
||||
function getDstFilename(line, cfg) {
|
||||
return _getFilename('\\+\\+\\+', line, cfg.dstPrefix);
|
||||
}
|
||||
|
||||
function _getFilename(linePrefix, line, extraPrefix) {
|
||||
var prefixes = ['a/', 'b/', 'i/', 'w/', 'c/', 'o/'];
|
||||
if (extraPrefix) {
|
||||
prefixes.push(extraPrefix);
|
||||
}
|
||||
|
||||
var FilenameRegExp;
|
||||
if (linePrefix) {
|
||||
FilenameRegExp = new RegExp('^' + linePrefix + ' "?(.+?)"?$');
|
||||
} else {
|
||||
FilenameRegExp = new RegExp('^"?(.+?)"?$');
|
||||
}
|
||||
|
||||
var filename;
|
||||
var values = FilenameRegExp.exec(line);
|
||||
if (values && values[1]) {
|
||||
filename = values[1];
|
||||
var matchingPrefixes = prefixes.filter(function(p) {
|
||||
return filename.indexOf(p) === 0;
|
||||
});
|
||||
|
||||
if (matchingPrefixes[0]) {
|
||||
// Remove prefix if exists
|
||||
filename = filename.slice(matchingPrefixes[0].length);
|
||||
}
|
||||
|
||||
// Cleanup timestamps generated by the unified diff (diff command) as specified in
|
||||
// https://www.gnu.org/software/diffutils/manual/html_node/Detailed-Unified.html
|
||||
// Ie: 2016-10-25 11:37:14.000000000 +0200
|
||||
filename = filename.replace(/\s+\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)? [-+]\d{4}.*$/, '');
|
||||
}
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
module.exports.DiffParser = new DiffParser();
|
||||
})();
|
||||
433
src/diff-parser.ts
Normal file
|
|
@ -0,0 +1,433 @@
|
|||
import { DiffFile, DiffBlock, DiffLine, LineType } from "./types";
|
||||
import { escapeForRegExp } from "./utils";
|
||||
|
||||
export interface DiffParserConfig {
|
||||
srcPrefix?: string;
|
||||
dstPrefix?: string;
|
||||
}
|
||||
|
||||
function getExtension(filename: string, language: string): string {
|
||||
const filenameParts = filename.split(".");
|
||||
return filenameParts.length > 1 ? filenameParts[filenameParts.length - 1] : language;
|
||||
}
|
||||
|
||||
function startsWithAny(str: string, prefixes: string[]): boolean {
|
||||
return prefixes.reduce<boolean>((startsWith, prefix) => startsWith || str.startsWith(prefix), false);
|
||||
}
|
||||
|
||||
const baseDiffFilenamePrefixes = ["a/", "b/", "i/", "w/", "c/", "o/"];
|
||||
function getFilename(line: string, linePrefix?: string, extraPrefix?: string): string {
|
||||
const prefixes = extraPrefix !== undefined ? [...baseDiffFilenamePrefixes, extraPrefix] : baseDiffFilenamePrefixes;
|
||||
|
||||
const FilenameRegExp = linePrefix
|
||||
? new RegExp(`^${escapeForRegExp(linePrefix)} "?(.+?)"?$`)
|
||||
: new RegExp('^"?(.+?)"?$');
|
||||
|
||||
const [, filename = ""] = FilenameRegExp.exec(line) || [];
|
||||
const matchingPrefix = prefixes.find(p => filename.indexOf(p) === 0);
|
||||
const fnameWithoutPrefix = matchingPrefix ? filename.slice(matchingPrefix.length) : filename;
|
||||
|
||||
// Cleanup timestamps generated by the unified diff (diff command) as specified in
|
||||
// https://www.gnu.org/software/diffutils/manual/html_node/Detailed-Unified.html
|
||||
// Ie: 2016-10-25 11:37:14.000000000 +0200
|
||||
return fnameWithoutPrefix.replace(/\s+\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)? [-+]\d{4}.*$/, "");
|
||||
}
|
||||
|
||||
function getSrcFilename(line: string, srcPrefix?: string): string | undefined {
|
||||
return getFilename(line, "---", srcPrefix);
|
||||
}
|
||||
|
||||
function getDstFilename(line: string, dstPrefix?: string): string | undefined {
|
||||
return getFilename(line, "+++", dstPrefix);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Docs:
|
||||
* - Unified: https://www.gnu.org/software/diffutils/manual/html_node/Unified-Format.html
|
||||
* - Git Diff: https://git-scm.com/docs/git-diff-tree#_raw_output_format
|
||||
* - Git Combined Diff: https://git-scm.com/docs/git-diff-tree#_combined_diff_format
|
||||
*
|
||||
*/
|
||||
export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFile[] {
|
||||
const files: DiffFile[] = [];
|
||||
let currentFile: DiffFile | null = null;
|
||||
let currentBlock: DiffBlock | null = null;
|
||||
let oldLine: number | null = null;
|
||||
let oldLine2: number | null = null; // Used for combined diff
|
||||
let newLine: number | null = null;
|
||||
|
||||
let possibleOldName: string | null = null;
|
||||
let possibleNewName: string | null = null;
|
||||
|
||||
/* Diff Header */
|
||||
const oldFileNameHeader = "--- ";
|
||||
const newFileNameHeader = "+++ ";
|
||||
const hunkHeaderPrefix = "@@";
|
||||
|
||||
/* Diff */
|
||||
const oldMode = /^old mode (\d{6})/;
|
||||
const newMode = /^new mode (\d{6})/;
|
||||
const deletedFileMode = /^deleted file mode (\d{6})/;
|
||||
const newFileMode = /^new file mode (\d{6})/;
|
||||
|
||||
const copyFrom = /^copy from "?(.+)"?/;
|
||||
const copyTo = /^copy to "?(.+)"?/;
|
||||
|
||||
const renameFrom = /^rename from "?(.+)"?/;
|
||||
const renameTo = /^rename to "?(.+)"?/;
|
||||
|
||||
const similarityIndex = /^similarity index (\d+)%/;
|
||||
const dissimilarityIndex = /^dissimilarity index (\d+)%/;
|
||||
const index = /^index ([0-9a-z]+)\.\.([0-9a-z]+)\s*(\d{6})?/;
|
||||
|
||||
const binaryFiles = /^Binary files (.*) and (.*) differ/;
|
||||
const binaryDiff = /^GIT binary patch/;
|
||||
|
||||
/* Combined Diff */
|
||||
const combinedIndex = /^index ([0-9a-z]+),([0-9a-z]+)\.\.([0-9a-z]+)/;
|
||||
const combinedMode = /^mode (\d{6}),(\d{6})\.\.(\d{6})/;
|
||||
const combinedNewFile = /^new file mode (\d{6})/;
|
||||
const combinedDeletedFile = /^deleted file mode (\d{6}),(\d{6})/;
|
||||
|
||||
const diffLines = diffInput
|
||||
.replace(/\\ No newline at end of file/g, "")
|
||||
.replace(/\r\n?/g, "\n")
|
||||
.split("\n");
|
||||
|
||||
/* Add previous block(if exists) before start a new file */
|
||||
function saveBlock(): void {
|
||||
if (currentBlock !== null && currentFile !== null) {
|
||||
currentFile.blocks.push(currentBlock);
|
||||
currentBlock = null;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Add previous file(if exists) before start a new one
|
||||
* if it has name (to avoid binary files errors)
|
||||
*/
|
||||
function saveFile(): void {
|
||||
if (currentFile !== null) {
|
||||
if (!currentFile.oldName && possibleOldName !== null) {
|
||||
currentFile.oldName = possibleOldName;
|
||||
}
|
||||
|
||||
if (!currentFile.newName && possibleNewName !== null) {
|
||||
currentFile.newName = possibleNewName;
|
||||
}
|
||||
|
||||
if (currentFile.newName) {
|
||||
files.push(currentFile);
|
||||
currentFile = null;
|
||||
}
|
||||
}
|
||||
|
||||
possibleOldName = null;
|
||||
possibleNewName = null;
|
||||
}
|
||||
|
||||
/* Create file structure */
|
||||
function startFile(): void {
|
||||
saveBlock();
|
||||
saveFile();
|
||||
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
currentFile = {
|
||||
blocks: [],
|
||||
deletedLines: 0,
|
||||
addedLines: 0
|
||||
};
|
||||
}
|
||||
|
||||
function startBlock(line: string): void {
|
||||
saveBlock();
|
||||
|
||||
let values;
|
||||
|
||||
/**
|
||||
* From Range:
|
||||
* -<start line>[,<number of lines>]
|
||||
*
|
||||
* To Range:
|
||||
* +<start line>[,<number of lines>]
|
||||
*
|
||||
* @@ from-file-range to-file-range @@
|
||||
*
|
||||
* @@@ from-file-range from-file-range to-file-range @@@
|
||||
*
|
||||
* number of lines is optional, if omited consider 0
|
||||
*/
|
||||
|
||||
if (currentFile !== null) {
|
||||
if ((values = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@.*/.exec(line))) {
|
||||
currentFile.isCombined = false;
|
||||
oldLine = parseInt(values[1], 10);
|
||||
newLine = parseInt(values[2], 10);
|
||||
} else if ((values = /^@@@ -(\d+)(?:,\d+)? -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@@.*/.exec(line))) {
|
||||
currentFile.isCombined = true;
|
||||
oldLine = parseInt(values[1], 10);
|
||||
oldLine2 = parseInt(values[2], 10);
|
||||
newLine = parseInt(values[3], 10);
|
||||
} else {
|
||||
if (line.startsWith(hunkHeaderPrefix)) {
|
||||
console.error("Failed to parse lines, starting in 0!");
|
||||
}
|
||||
|
||||
oldLine = 0;
|
||||
newLine = 0;
|
||||
currentFile.isCombined = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Create block metadata */
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
currentBlock = {
|
||||
lines: [],
|
||||
oldStartLine: oldLine,
|
||||
oldStartLine2: oldLine2,
|
||||
newStartLine: newLine,
|
||||
header: line
|
||||
};
|
||||
}
|
||||
|
||||
function createLine(line: string): void {
|
||||
if (currentFile === null || currentBlock === null || oldLine === null || newLine === null) return;
|
||||
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
const currentLine: DiffLine = {
|
||||
content: line
|
||||
};
|
||||
|
||||
const addedPrefixes = currentFile.isCombined ? ["+ ", " +", "++"] : ["+"];
|
||||
const deletedPrefixes = currentFile.isCombined ? ["- ", " -", "--"] : ["-"];
|
||||
|
||||
if (startsWithAny(line, addedPrefixes)) {
|
||||
currentFile.addedLines++;
|
||||
currentLine.type = LineType.INSERT;
|
||||
currentLine.oldNumber = undefined;
|
||||
currentLine.newNumber = newLine++;
|
||||
} else if (startsWithAny(line, deletedPrefixes)) {
|
||||
currentFile.deletedLines++;
|
||||
currentLine.type = LineType.DELETE;
|
||||
currentLine.oldNumber = oldLine++;
|
||||
currentLine.newNumber = undefined;
|
||||
} else {
|
||||
currentLine.type = LineType.CONTEXT;
|
||||
currentLine.oldNumber = oldLine++;
|
||||
currentLine.newNumber = newLine++;
|
||||
}
|
||||
currentBlock.lines.push(currentLine);
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if there is a hunk header coming before a new file starts
|
||||
*
|
||||
* Hunk header is a group of three lines started by ( `--- ` , `+++ ` , `@@` )
|
||||
*/
|
||||
function existHunkHeader(line: string, lineIdx: number): boolean {
|
||||
let idx = lineIdx;
|
||||
|
||||
while (idx < diffLines.length - 3) {
|
||||
if (line.startsWith("diff")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
diffLines[idx].startsWith(oldFileNameHeader) &&
|
||||
diffLines[idx + 1].startsWith(newFileNameHeader) &&
|
||||
diffLines[idx + 2].startsWith(hunkHeaderPrefix)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
diffLines.forEach((line, lineIndex) => {
|
||||
// Unmerged paths, and possibly other non-diffable files
|
||||
// https://github.com/scottgonzalez/pretty-diff/issues/11
|
||||
// Also, remove some useless lines
|
||||
if (!line || line.startsWith("*")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Used to store regex capture groups
|
||||
let values;
|
||||
|
||||
const prevLine = diffLines[lineIndex - 1];
|
||||
const nxtLine = diffLines[lineIndex + 1];
|
||||
const afterNxtLine = diffLines[lineIndex + 2];
|
||||
|
||||
if (line.startsWith("diff")) {
|
||||
startFile();
|
||||
|
||||
// diff --git a/blocked_delta_results.png b/blocked_delta_results.png
|
||||
const gitDiffStart = /^diff --git "?(.+)"? "?(.+)"?/;
|
||||
if ((values = gitDiffStart.exec(line))) {
|
||||
possibleOldName = getFilename(values[1], undefined, config.dstPrefix);
|
||||
possibleNewName = getFilename(values[2], undefined, config.srcPrefix);
|
||||
}
|
||||
|
||||
if (currentFile === null) {
|
||||
throw new Error("Where is my file !!!");
|
||||
}
|
||||
|
||||
currentFile.isGitDiff = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!currentFile || // If we do not have a file yet, we should crete one
|
||||
(!currentFile.isGitDiff &&
|
||||
currentFile && // If we already have some file in progress and
|
||||
line.startsWith(oldFileNameHeader) && // If we get to an old file path header line
|
||||
// And is followed by the new file path header line and the hunk header line
|
||||
nxtLine.startsWith(newFileNameHeader) &&
|
||||
afterNxtLine.startsWith(hunkHeaderPrefix))
|
||||
) {
|
||||
startFile();
|
||||
}
|
||||
|
||||
/*
|
||||
* We need to make sure that we have the three lines of the header.
|
||||
* This avoids cases like the ones described in:
|
||||
* - https://github.com/rtfpessoa/diff2html/issues/87
|
||||
*/
|
||||
if (
|
||||
(line.startsWith(oldFileNameHeader) && nxtLine.startsWith(newFileNameHeader)) ||
|
||||
(line.startsWith(newFileNameHeader) && prevLine.startsWith(oldFileNameHeader))
|
||||
) {
|
||||
/*
|
||||
* --- Date Timestamp[FractionalSeconds] TimeZone
|
||||
* --- 2002-02-21 23:30:39.942229878 -0800
|
||||
*/
|
||||
if (
|
||||
currentFile &&
|
||||
!currentFile.oldName &&
|
||||
line.startsWith("--- ") &&
|
||||
(values = getSrcFilename(line, config.srcPrefix))
|
||||
) {
|
||||
currentFile.oldName = values;
|
||||
currentFile.language = getExtension(currentFile.oldName, currentFile.language);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* +++ Date Timestamp[FractionalSeconds] TimeZone
|
||||
* +++ 2002-02-21 23:30:39.942229878 -0800
|
||||
*/
|
||||
if (
|
||||
currentFile &&
|
||||
!currentFile.newName &&
|
||||
line.startsWith("+++ ") &&
|
||||
(values = getDstFilename(line, config.dstPrefix))
|
||||
) {
|
||||
currentFile.newName = values;
|
||||
currentFile.language = getExtension(currentFile.newName, currentFile.language);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
currentFile &&
|
||||
(line.startsWith(hunkHeaderPrefix) ||
|
||||
(currentFile.isGitDiff && currentFile.oldName && currentFile.newName && !currentBlock))
|
||||
) {
|
||||
startBlock(line);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* There are three types of diff lines. These lines are defined by the way they start.
|
||||
* 1. New line starts with: +
|
||||
* 2. Old line starts with: -
|
||||
* 3. Context line starts with: <SPACE>
|
||||
*/
|
||||
if (currentBlock && (line.startsWith("+") || line.startsWith("-") || line.startsWith(" "))) {
|
||||
createLine(line);
|
||||
return;
|
||||
}
|
||||
|
||||
const doesNotExistHunkHeader = !existHunkHeader(line, lineIndex);
|
||||
|
||||
if (currentFile === null) {
|
||||
throw new Error("Where is my file !!!");
|
||||
}
|
||||
|
||||
/*
|
||||
* Git diffs provide more information regarding files modes, renames, copies,
|
||||
* commits between changes and similarity indexes
|
||||
*/
|
||||
if ((values = oldMode.exec(line))) {
|
||||
currentFile.oldMode = values[1];
|
||||
} else if ((values = newMode.exec(line))) {
|
||||
currentFile.newMode = values[1];
|
||||
} else if ((values = deletedFileMode.exec(line))) {
|
||||
currentFile.deletedFileMode = values[1];
|
||||
currentFile.isDeleted = true;
|
||||
} else if ((values = newFileMode.exec(line))) {
|
||||
currentFile.newFileMode = values[1];
|
||||
currentFile.isNew = true;
|
||||
} else if ((values = copyFrom.exec(line))) {
|
||||
if (doesNotExistHunkHeader) {
|
||||
currentFile.oldName = values[1];
|
||||
}
|
||||
currentFile.isCopy = true;
|
||||
} else if ((values = copyTo.exec(line))) {
|
||||
if (doesNotExistHunkHeader) {
|
||||
currentFile.newName = values[1];
|
||||
}
|
||||
currentFile.isCopy = true;
|
||||
} else if ((values = renameFrom.exec(line))) {
|
||||
if (doesNotExistHunkHeader) {
|
||||
currentFile.oldName = values[1];
|
||||
}
|
||||
currentFile.isRename = true;
|
||||
} else if ((values = renameTo.exec(line))) {
|
||||
if (doesNotExistHunkHeader) {
|
||||
currentFile.newName = values[1];
|
||||
}
|
||||
currentFile.isRename = true;
|
||||
} else if ((values = binaryFiles.exec(line))) {
|
||||
currentFile.isBinary = true;
|
||||
currentFile.oldName = getFilename(values[1], undefined, config.srcPrefix);
|
||||
currentFile.newName = getFilename(values[2], undefined, config.dstPrefix);
|
||||
startBlock("Binary file");
|
||||
} else if (binaryDiff.test(line)) {
|
||||
currentFile.isBinary = true;
|
||||
startBlock(line);
|
||||
} else if ((values = similarityIndex.exec(line))) {
|
||||
currentFile.unchangedPercentage = parseInt(values[1], 10);
|
||||
} else if ((values = dissimilarityIndex.exec(line))) {
|
||||
currentFile.changedPercentage = parseInt(values[1], 10);
|
||||
} else if ((values = index.exec(line))) {
|
||||
currentFile.checksumBefore = values[1];
|
||||
currentFile.checksumAfter = values[2];
|
||||
values[3] && (currentFile.mode = values[3]);
|
||||
} else if ((values = combinedIndex.exec(line))) {
|
||||
currentFile.checksumBefore = [values[2], values[3]];
|
||||
currentFile.checksumAfter = values[1];
|
||||
} else if ((values = combinedMode.exec(line))) {
|
||||
currentFile.oldMode = [values[2], values[3]];
|
||||
currentFile.newMode = values[1];
|
||||
} else if ((values = combinedNewFile.exec(line))) {
|
||||
currentFile.newFileMode = values[1];
|
||||
currentFile.isNew = true;
|
||||
} else if ((values = combinedDeletedFile.exec(line))) {
|
||||
currentFile.deletedFileMode = values[1];
|
||||
currentFile.isDeleted = true;
|
||||
}
|
||||
});
|
||||
|
||||
saveBlock();
|
||||
saveFile();
|
||||
|
||||
return files;
|
||||
}
|
||||
71
src/diff2html.d.ts
vendored
|
|
@ -1,71 +0,0 @@
|
|||
// Type definitions for diff2html
|
||||
// Project: https://github.com/rtfpessoa/diff2html
|
||||
// Definitions by: rtfpessoa <https://github.com/rtfpessoa/>
|
||||
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
||||
|
||||
declare namespace Diff2Html {
|
||||
|
||||
export interface Options {
|
||||
inputFormat?: 'diff' | 'json';
|
||||
outputFormat?: 'line-by-line' | 'side-by-side';
|
||||
showFiles?: boolean;
|
||||
diffStyle?: 'word' | 'char';
|
||||
matching?: 'lines' | 'words' | 'none';
|
||||
matchWordsThreshold?: number;
|
||||
matchingMaxComparisons?: number;
|
||||
maxLineSizeInBlockForComparison?: number;
|
||||
maxLineLengthHighlight?: number;
|
||||
templates?: object;
|
||||
rawTemplates?: object;
|
||||
renderNothingWhenEmpty?: boolean;
|
||||
}
|
||||
|
||||
export interface Line {
|
||||
content: string;
|
||||
type: string;
|
||||
oldNumber: number;
|
||||
newNumber: number;
|
||||
}
|
||||
|
||||
export interface Block {
|
||||
oldStartLine: number;
|
||||
oldStartLine2?: number;
|
||||
newStartLine: number;
|
||||
header: string;
|
||||
lines: Line[];
|
||||
}
|
||||
|
||||
export interface Result {
|
||||
addedLines: number;
|
||||
deletedLines: number;
|
||||
isCombined: boolean;
|
||||
isGitDiff: boolean;
|
||||
oldName: string;
|
||||
newName: string;
|
||||
language: string;
|
||||
blocks: Block[];
|
||||
oldMode?: string;
|
||||
newMode?: string;
|
||||
deletedFileMode?: string;
|
||||
newFileMode?: string;
|
||||
isDeleted?: boolean;
|
||||
isNew?: boolean;
|
||||
isCopy?: boolean;
|
||||
isRename?: boolean;
|
||||
unchangedPercentage?: number;
|
||||
changedPercentage?: number;
|
||||
checksumBefore?: string;
|
||||
checksumAfter?: string;
|
||||
mode?: string;
|
||||
}
|
||||
|
||||
export interface Diff2Html {
|
||||
getJsonFromDiff(input: string, configuration?: Options): Result[];
|
||||
getPrettyHtml(input: any, configuration?: Options): string;
|
||||
}
|
||||
}
|
||||
|
||||
declare module "diff2html" {
|
||||
var d2h: { "Diff2Html": Diff2Html.Diff2Html };
|
||||
export = d2h;
|
||||
}
|
||||
114
src/diff2html.js
|
|
@ -1,114 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* Diff to HTML (diff2html.js)
|
||||
* Author: rtfpessoa
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var diffParser = require('./diff-parser.js').DiffParser;
|
||||
var htmlPrinter = require('./html-printer.js').HtmlPrinter;
|
||||
var utils = require('./utils.js').Utils;
|
||||
|
||||
function Diff2Html() {
|
||||
}
|
||||
|
||||
var defaultConfig = {
|
||||
inputFormat: 'diff',
|
||||
outputFormat: 'line-by-line',
|
||||
showFiles: false,
|
||||
diffStyle: 'word',
|
||||
matching: 'none',
|
||||
matchWordsThreshold: 0.25,
|
||||
matchingMaxComparisons: 2500,
|
||||
maxLineSizeInBlockForComparison: 200,
|
||||
maxLineLengthHighlight: 10000,
|
||||
templates: {},
|
||||
rawTemplates: {},
|
||||
renderNothingWhenEmpty: false
|
||||
};
|
||||
|
||||
/*
|
||||
* Generates json object from string diff input
|
||||
*/
|
||||
Diff2Html.prototype.getJsonFromDiff = function(diffInput, config) {
|
||||
var cfg = utils.safeConfig(config, defaultConfig);
|
||||
return diffParser.generateDiffJson(diffInput, cfg);
|
||||
};
|
||||
|
||||
/*
|
||||
* Generates the html diff. The config parameter configures the output/input formats and other options
|
||||
*/
|
||||
Diff2Html.prototype.getPrettyHtml = function(diffInput, config) {
|
||||
var cfg = utils.safeConfig(config, defaultConfig);
|
||||
|
||||
var diffJson = diffInput;
|
||||
if (!cfg.inputFormat || cfg.inputFormat === 'diff') {
|
||||
diffJson = diffParser.generateDiffJson(diffInput, cfg);
|
||||
}
|
||||
|
||||
var fileList = '';
|
||||
if (cfg.showFiles === true) {
|
||||
fileList = htmlPrinter.generateFileListSummary(diffJson, cfg);
|
||||
}
|
||||
|
||||
var diffOutput = '';
|
||||
if (cfg.outputFormat === 'side-by-side') {
|
||||
diffOutput = htmlPrinter.generateSideBySideJsonHtml(diffJson, cfg);
|
||||
} else {
|
||||
diffOutput = htmlPrinter.generateLineByLineJsonHtml(diffJson, cfg);
|
||||
}
|
||||
|
||||
return fileList + diffOutput;
|
||||
};
|
||||
|
||||
/*
|
||||
* Deprecated methods - The following methods exist only to maintain compatibility with previous versions
|
||||
*/
|
||||
|
||||
/*
|
||||
* Generates pretty html from string diff input
|
||||
*/
|
||||
Diff2Html.prototype.getPrettyHtmlFromDiff = function(diffInput, config) {
|
||||
var cfg = utils.safeConfig(config, defaultConfig);
|
||||
cfg.inputFormat = 'diff';
|
||||
cfg.outputFormat = 'line-by-line';
|
||||
return this.getPrettyHtml(diffInput, cfg);
|
||||
};
|
||||
|
||||
/*
|
||||
* Generates pretty html from a json object
|
||||
*/
|
||||
Diff2Html.prototype.getPrettyHtmlFromJson = function(diffJson, config) {
|
||||
var cfg = utils.safeConfig(config, defaultConfig);
|
||||
cfg.inputFormat = 'json';
|
||||
cfg.outputFormat = 'line-by-line';
|
||||
return this.getPrettyHtml(diffJson, cfg);
|
||||
};
|
||||
|
||||
/*
|
||||
* Generates pretty side by side html from string diff input
|
||||
*/
|
||||
Diff2Html.prototype.getPrettySideBySideHtmlFromDiff = function(diffInput, config) {
|
||||
var cfg = utils.safeConfig(config, defaultConfig);
|
||||
cfg.inputFormat = 'diff';
|
||||
cfg.outputFormat = 'side-by-side';
|
||||
return this.getPrettyHtml(diffInput, cfg);
|
||||
};
|
||||
|
||||
/*
|
||||
* Generates pretty side by side html from a json object
|
||||
*/
|
||||
Diff2Html.prototype.getPrettySideBySideHtmlFromJson = function(diffJson, config) {
|
||||
var cfg = utils.safeConfig(config, defaultConfig);
|
||||
cfg.inputFormat = 'json';
|
||||
cfg.outputFormat = 'side-by-side';
|
||||
return this.getPrettyHtml(diffJson, cfg);
|
||||
};
|
||||
|
||||
var diffObject = new Diff2Html();
|
||||
module.exports.Diff2Html = diffObject;
|
||||
|
||||
// Expose diff2html in the browser
|
||||
global.Diff2Html = diffObject;
|
||||
})();
|
||||
43
src/diff2html.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import * as DiffParser from "./diff-parser";
|
||||
import * as fileListPrinter from "./file-list-renderer";
|
||||
import LineByLineRenderer, { LineByLineRendererConfig, defaultLineByLineRendererConfig } from "./line-by-line-renderer";
|
||||
import SideBySideRenderer, { SideBySideRendererConfig, defaultSideBySideRendererConfig } from "./side-by-side-renderer";
|
||||
import { DiffFile, OutputFormatType } from "./types";
|
||||
import HoganJsUtils, { HoganJsUtilsConfig } from "./hoganjs-utils";
|
||||
|
||||
export interface Diff2HtmlConfig
|
||||
extends DiffParser.DiffParserConfig,
|
||||
LineByLineRendererConfig,
|
||||
SideBySideRendererConfig,
|
||||
HoganJsUtilsConfig {
|
||||
outputFormat?: OutputFormatType;
|
||||
drawFileList?: boolean;
|
||||
}
|
||||
|
||||
export const defaultDiff2HtmlConfig = {
|
||||
...defaultLineByLineRendererConfig,
|
||||
...defaultSideBySideRendererConfig,
|
||||
outputFormat: OutputFormatType.LINE_BY_LINE,
|
||||
drawFileList: true
|
||||
};
|
||||
|
||||
export function parse(diffInput: string, configuration: Diff2HtmlConfig = {}): DiffFile[] {
|
||||
return DiffParser.parse(diffInput, { ...defaultDiff2HtmlConfig, ...configuration });
|
||||
}
|
||||
|
||||
export function html(diffInput: string | DiffFile[], configuration: Diff2HtmlConfig = {}): string {
|
||||
const config = { ...defaultDiff2HtmlConfig, ...configuration };
|
||||
|
||||
const diffJson = typeof diffInput === "string" ? DiffParser.parse(diffInput, config) : diffInput;
|
||||
|
||||
const hoganUtils = new HoganJsUtils(config);
|
||||
|
||||
const fileList = config.drawFileList ? fileListPrinter.render(diffJson, hoganUtils) : "";
|
||||
|
||||
const diffOutput =
|
||||
config.outputFormat === "side-by-side"
|
||||
? new SideBySideRenderer(hoganUtils, config).render(diffJson)
|
||||
: new LineByLineRenderer(hoganUtils, config).render(diffJson);
|
||||
|
||||
return fileList + diffOutput;
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* FileListPrinter (file-list-printer.js)
|
||||
* Author: nmatpt
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var printerUtils = require('./printer-utils.js').PrinterUtils;
|
||||
|
||||
var hoganUtils;
|
||||
|
||||
var baseTemplatesPath = 'file-summary';
|
||||
var iconsBaseTemplatesPath = 'icon';
|
||||
|
||||
function FileListPrinter(config) {
|
||||
this.config = config;
|
||||
|
||||
var HoganJsUtils = require('./hoganjs-utils.js').HoganJsUtils;
|
||||
hoganUtils = new HoganJsUtils(config);
|
||||
}
|
||||
|
||||
FileListPrinter.prototype.generateFileList = function(diffFiles) {
|
||||
var lineTemplate = hoganUtils.template(baseTemplatesPath, 'line');
|
||||
|
||||
var files = diffFiles.map(function(file) {
|
||||
var fileTypeName = printerUtils.getFileTypeIcon(file);
|
||||
var iconTemplate = hoganUtils.template(iconsBaseTemplatesPath, fileTypeName);
|
||||
|
||||
return lineTemplate.render({
|
||||
fileHtmlId: printerUtils.getHtmlId(file),
|
||||
oldName: file.oldName,
|
||||
newName: file.newName,
|
||||
fileName: printerUtils.getDiffName(file),
|
||||
deletedLines: '-' + file.deletedLines,
|
||||
addedLines: '+' + file.addedLines
|
||||
}, {
|
||||
fileIcon: iconTemplate
|
||||
});
|
||||
}).join('\n');
|
||||
|
||||
return hoganUtils.render(baseTemplatesPath, 'wrapper', {
|
||||
filesNumber: diffFiles.length,
|
||||
files: files
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.FileListPrinter = FileListPrinter;
|
||||
})();
|
||||
33
src/file-list-renderer.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import * as renderUtils from "./render-utils";
|
||||
import HoganJsUtils from "./hoganjs-utils";
|
||||
import { DiffFile } from "./types";
|
||||
|
||||
const baseTemplatesPath = "file-summary";
|
||||
const iconsBaseTemplatesPath = "icon";
|
||||
|
||||
export function render(diffFiles: DiffFile[], hoganUtils: HoganJsUtils): string {
|
||||
const files = diffFiles
|
||||
.map(file =>
|
||||
hoganUtils.render(
|
||||
baseTemplatesPath,
|
||||
"line",
|
||||
{
|
||||
fileHtmlId: renderUtils.getHtmlId(file),
|
||||
oldName: file.oldName,
|
||||
newName: file.newName,
|
||||
fileName: renderUtils.filenameDiff(file),
|
||||
deletedLines: "-" + file.deletedLines,
|
||||
addedLines: "+" + file.addedLines
|
||||
},
|
||||
{
|
||||
fileIcon: hoganUtils.template(iconsBaseTemplatesPath, renderUtils.getFileIcon(file))
|
||||
}
|
||||
)
|
||||
)
|
||||
.join("\n");
|
||||
|
||||
return hoganUtils.render(baseTemplatesPath, "wrapper", {
|
||||
filesNumber: diffFiles.length,
|
||||
files: files
|
||||
});
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* Utils (hoganjs-utils.js)
|
||||
* Author: rtfpessoa
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var hogan = require('hogan.js');
|
||||
|
||||
var hoganTemplates = require('./templates/diff2html-templates.js');
|
||||
|
||||
var extraTemplates;
|
||||
|
||||
function HoganJsUtils(configuration) {
|
||||
this.config = configuration || {};
|
||||
extraTemplates = this.config.templates || {};
|
||||
|
||||
var rawTemplates = this.config.rawTemplates || {};
|
||||
for (var templateName in rawTemplates) {
|
||||
if (rawTemplates.hasOwnProperty(templateName)) {
|
||||
if (!extraTemplates[templateName]) extraTemplates[templateName] = this.compile(rawTemplates[templateName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HoganJsUtils.prototype.render = function(namespace, view, params) {
|
||||
var template = this.template(namespace, view);
|
||||
if (template) {
|
||||
return template.render(params);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
HoganJsUtils.prototype.template = function(namespace, view) {
|
||||
var templateKey = this._templateKey(namespace, view);
|
||||
|
||||
return this._getTemplate(templateKey);
|
||||
};
|
||||
|
||||
HoganJsUtils.prototype._getTemplate = function(templateKey) {
|
||||
var template;
|
||||
|
||||
if (!this.config.noCache) {
|
||||
template = this._readFromCache(templateKey);
|
||||
}
|
||||
|
||||
if (!template) {
|
||||
template = this._loadTemplate(templateKey);
|
||||
}
|
||||
|
||||
return template;
|
||||
};
|
||||
|
||||
HoganJsUtils.prototype._loadTemplate = function(templateKey) {
|
||||
var template;
|
||||
|
||||
try {
|
||||
if (fs.readFileSync) {
|
||||
var templatesPath = path.resolve(__dirname, 'templates');
|
||||
var templatePath = path.join(templatesPath, templateKey);
|
||||
var templateContent = fs.readFileSync(templatePath + '.mustache', 'utf8');
|
||||
template = hogan.compile(templateContent);
|
||||
hoganTemplates[templateKey] = template;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to read (template: ' + templateKey + ') from fs: ' + e.message);
|
||||
}
|
||||
|
||||
return template;
|
||||
};
|
||||
|
||||
HoganJsUtils.prototype._readFromCache = function(templateKey) {
|
||||
return extraTemplates[templateKey] || hoganTemplates[templateKey];
|
||||
};
|
||||
|
||||
HoganJsUtils.prototype._templateKey = function(namespace, view) {
|
||||
return namespace + '-' + view;
|
||||
};
|
||||
|
||||
HoganJsUtils.prototype.compile = function(templateStr) {
|
||||
return hogan.compile(templateStr);
|
||||
};
|
||||
|
||||
module.exports.HoganJsUtils = HoganJsUtils;
|
||||
})();
|
||||
54
src/hoganjs-utils.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import * as Hogan from "hogan.js";
|
||||
|
||||
import { defaultTemplates } from "./diff2html-templates";
|
||||
|
||||
export interface RawTemplates {
|
||||
[name: string]: string;
|
||||
}
|
||||
|
||||
export interface CompiledTemplates {
|
||||
[name: string]: Hogan.Template;
|
||||
}
|
||||
|
||||
export interface HoganJsUtilsConfig {
|
||||
compiledTemplates?: CompiledTemplates;
|
||||
rawTemplates?: RawTemplates;
|
||||
}
|
||||
|
||||
export default class HoganJsUtils {
|
||||
private preCompiledTemplates: CompiledTemplates;
|
||||
|
||||
constructor({ compiledTemplates = {}, rawTemplates = {} }: HoganJsUtilsConfig) {
|
||||
const compiledRawTemplates = Object.entries(rawTemplates).reduce<CompiledTemplates>(
|
||||
(previousTemplates, [name, templateString]) => {
|
||||
const compiledTemplate: Hogan.Template = Hogan.compile(templateString, { asString: false });
|
||||
return { ...previousTemplates, [name]: compiledTemplate };
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
this.preCompiledTemplates = { ...defaultTemplates, ...compiledTemplates, ...compiledRawTemplates };
|
||||
}
|
||||
|
||||
static compile(templateString: string): Hogan.Template {
|
||||
return Hogan.compile(templateString, { asString: false });
|
||||
}
|
||||
|
||||
render(namespace: string, view: string, params: Hogan.Context, partials?: Hogan.Partials, indent?: string): string {
|
||||
const templateKey = this.templateKey(namespace, view);
|
||||
try {
|
||||
const template = this.preCompiledTemplates[templateKey];
|
||||
return template.render(params, partials, indent);
|
||||
} catch (e) {
|
||||
throw new Error(`Could not find template to render '${templateKey}'`);
|
||||
}
|
||||
}
|
||||
|
||||
template(namespace: string, view: string): Hogan.Template {
|
||||
return this.preCompiledTemplates[this.templateKey(namespace, view)];
|
||||
}
|
||||
|
||||
private templateKey(namespace: string, view: string): string {
|
||||
return `${namespace}-${view}`;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* HtmlPrinter (html-printer.js)
|
||||
* Author: rtfpessoa
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var LineByLinePrinter = require('./line-by-line-printer.js').LineByLinePrinter;
|
||||
var SideBySidePrinter = require('./side-by-side-printer.js').SideBySidePrinter;
|
||||
var FileListPrinter = require('./file-list-printer.js').FileListPrinter;
|
||||
|
||||
function HtmlPrinter() {
|
||||
}
|
||||
|
||||
HtmlPrinter.prototype.generateLineByLineJsonHtml = function(diffFiles, config) {
|
||||
var lineByLinePrinter = new LineByLinePrinter(config);
|
||||
return lineByLinePrinter.generateLineByLineJsonHtml(diffFiles);
|
||||
};
|
||||
|
||||
HtmlPrinter.prototype.generateSideBySideJsonHtml = function(diffFiles, config) {
|
||||
var sideBySidePrinter = new SideBySidePrinter(config);
|
||||
return sideBySidePrinter.generateSideBySideJsonHtml(diffFiles);
|
||||
};
|
||||
|
||||
HtmlPrinter.prototype.generateFileListSummary = function(diffJson, config) {
|
||||
var fileListPrinter = new FileListPrinter(config);
|
||||
return fileListPrinter.generateFileList(diffJson);
|
||||
};
|
||||
|
||||
module.exports.HtmlPrinter = new HtmlPrinter();
|
||||
})();
|
||||
|
|
@ -1,237 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* LineByLinePrinter (line-by-line-printer.js)
|
||||
* Author: rtfpessoa
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var diffParser = require('./diff-parser.js').DiffParser;
|
||||
var printerUtils = require('./printer-utils.js').PrinterUtils;
|
||||
var utils = require('./utils.js').Utils;
|
||||
var Rematch = require('./rematch.js').Rematch;
|
||||
|
||||
var hoganUtils;
|
||||
|
||||
var genericTemplatesPath = 'generic';
|
||||
var baseTemplatesPath = 'line-by-line';
|
||||
var iconsBaseTemplatesPath = 'icon';
|
||||
var tagsBaseTemplatesPath = 'tag';
|
||||
|
||||
function LineByLinePrinter(config) {
|
||||
this.config = config;
|
||||
|
||||
var HoganJsUtils = require('./hoganjs-utils.js').HoganJsUtils;
|
||||
hoganUtils = new HoganJsUtils(config);
|
||||
}
|
||||
|
||||
LineByLinePrinter.prototype.makeFileDiffHtml = function(file, diffs) {
|
||||
if (this.config.renderNothingWhenEmpty && file.blocks && !file.blocks.length) return '';
|
||||
|
||||
var fileDiffTemplate = hoganUtils.template(baseTemplatesPath, 'file-diff');
|
||||
var filePathTemplate = hoganUtils.template(genericTemplatesPath, 'file-path');
|
||||
var fileIconTemplate = hoganUtils.template(iconsBaseTemplatesPath, 'file');
|
||||
var fileTagTemplate = hoganUtils.template(tagsBaseTemplatesPath, printerUtils.getFileTypeIcon(file));
|
||||
|
||||
return fileDiffTemplate.render({
|
||||
file: file,
|
||||
fileHtmlId: printerUtils.getHtmlId(file),
|
||||
diffs: diffs,
|
||||
filePath: filePathTemplate.render({
|
||||
fileDiffName: printerUtils.getDiffName(file)
|
||||
}, {
|
||||
fileIcon: fileIconTemplate,
|
||||
fileTag: fileTagTemplate
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
LineByLinePrinter.prototype.makeLineByLineHtmlWrapper = function(content) {
|
||||
return hoganUtils.render(genericTemplatesPath, 'wrapper', {'content': content});
|
||||
};
|
||||
|
||||
LineByLinePrinter.prototype.generateLineByLineJsonHtml = function(diffFiles) {
|
||||
var that = this;
|
||||
var htmlDiffs = diffFiles.map(function(file) {
|
||||
var diffs;
|
||||
if (file.blocks.length) {
|
||||
diffs = that._generateFileHtml(file);
|
||||
} else {
|
||||
diffs = that._generateEmptyDiff();
|
||||
}
|
||||
return that.makeFileDiffHtml(file, diffs);
|
||||
});
|
||||
|
||||
return this.makeLineByLineHtmlWrapper(htmlDiffs.join('\n'));
|
||||
};
|
||||
|
||||
var matcher = Rematch.rematch(function(a, b) {
|
||||
var amod = a.content.substr(1);
|
||||
var bmod = b.content.substr(1);
|
||||
|
||||
return Rematch.distance(amod, bmod);
|
||||
});
|
||||
|
||||
LineByLinePrinter.prototype.makeColumnLineNumberHtml = function(block) {
|
||||
return hoganUtils.render(genericTemplatesPath, 'column-line-number', {
|
||||
diffParser: diffParser,
|
||||
blockHeader: utils.escape(block.header),
|
||||
lineClass: 'd2h-code-linenumber',
|
||||
contentClass: 'd2h-code-line'
|
||||
});
|
||||
};
|
||||
|
||||
LineByLinePrinter.prototype._generateFileHtml = function(file) {
|
||||
var that = this;
|
||||
return file.blocks.map(function(block) {
|
||||
var lines = that.makeColumnLineNumberHtml(block);
|
||||
var oldLines = [];
|
||||
var newLines = [];
|
||||
|
||||
function processChangeBlock() {
|
||||
var matches;
|
||||
var insertType;
|
||||
var deleteType;
|
||||
|
||||
var comparisons = oldLines.length * newLines.length;
|
||||
|
||||
var maxLineSizeInBlock = Math.max.apply(null,
|
||||
[0].concat((oldLines.concat(newLines)).map(
|
||||
function(elem) {
|
||||
return elem.content.length;
|
||||
}
|
||||
)));
|
||||
|
||||
var doMatching = comparisons < that.config.matchingMaxComparisons &&
|
||||
maxLineSizeInBlock < that.config.maxLineSizeInBlockForComparison &&
|
||||
(that.config.matching === 'lines' || that.config.matching === 'words');
|
||||
|
||||
if (doMatching) {
|
||||
matches = matcher(oldLines, newLines);
|
||||
insertType = diffParser.LINE_TYPE.INSERT_CHANGES;
|
||||
deleteType = diffParser.LINE_TYPE.DELETE_CHANGES;
|
||||
} else {
|
||||
matches = [[oldLines, newLines]];
|
||||
insertType = diffParser.LINE_TYPE.INSERTS;
|
||||
deleteType = diffParser.LINE_TYPE.DELETES;
|
||||
}
|
||||
|
||||
matches.forEach(function(match) {
|
||||
oldLines = match[0];
|
||||
newLines = match[1];
|
||||
|
||||
var processedOldLines = [];
|
||||
var processedNewLines = [];
|
||||
|
||||
var common = Math.min(oldLines.length, newLines.length);
|
||||
|
||||
var oldLine, newLine;
|
||||
for (var j = 0; j < common; j++) {
|
||||
oldLine = oldLines[j];
|
||||
newLine = newLines[j];
|
||||
|
||||
that.config.isCombined = file.isCombined;
|
||||
var diff = printerUtils.diffHighlight(oldLine.content, newLine.content, that.config);
|
||||
|
||||
processedOldLines +=
|
||||
that.makeLineHtml(file.isCombined, deleteType, oldLine.oldNumber, oldLine.newNumber,
|
||||
diff.first.line, diff.first.prefix);
|
||||
processedNewLines +=
|
||||
that.makeLineHtml(file.isCombined, insertType, newLine.oldNumber, newLine.newNumber,
|
||||
diff.second.line, diff.second.prefix);
|
||||
}
|
||||
|
||||
lines += processedOldLines + processedNewLines;
|
||||
lines += that._processLines(file.isCombined, oldLines.slice(common), newLines.slice(common));
|
||||
});
|
||||
|
||||
oldLines = [];
|
||||
newLines = [];
|
||||
}
|
||||
|
||||
for (var i = 0; i < block.lines.length; i++) {
|
||||
var line = block.lines[i];
|
||||
var escapedLine = utils.escape(line.content);
|
||||
|
||||
if (line.type !== diffParser.LINE_TYPE.INSERTS &&
|
||||
(newLines.length > 0 || (line.type !== diffParser.LINE_TYPE.DELETES && oldLines.length > 0))) {
|
||||
processChangeBlock();
|
||||
}
|
||||
|
||||
if (line.type === diffParser.LINE_TYPE.CONTEXT) {
|
||||
lines += that.makeLineHtml(file.isCombined, line.type, line.oldNumber, line.newNumber, escapedLine);
|
||||
} else if (line.type === diffParser.LINE_TYPE.INSERTS && !oldLines.length) {
|
||||
lines += that.makeLineHtml(file.isCombined, line.type, line.oldNumber, line.newNumber, escapedLine);
|
||||
} else if (line.type === diffParser.LINE_TYPE.DELETES) {
|
||||
oldLines.push(line);
|
||||
} else if (line.type === diffParser.LINE_TYPE.INSERTS && Boolean(oldLines.length)) {
|
||||
newLines.push(line);
|
||||
} else {
|
||||
console.error('Unknown state in html line-by-line generator');
|
||||
processChangeBlock();
|
||||
}
|
||||
}
|
||||
|
||||
processChangeBlock();
|
||||
|
||||
return lines;
|
||||
}).join('\n');
|
||||
};
|
||||
|
||||
LineByLinePrinter.prototype._processLines = function(isCombined, oldLines, newLines) {
|
||||
var lines = '';
|
||||
|
||||
for (var i = 0; i < oldLines.length; i++) {
|
||||
var oldLine = oldLines[i];
|
||||
var oldEscapedLine = utils.escape(oldLine.content);
|
||||
lines += this.makeLineHtml(isCombined, oldLine.type, oldLine.oldNumber, oldLine.newNumber, oldEscapedLine);
|
||||
}
|
||||
|
||||
for (var j = 0; j < newLines.length; j++) {
|
||||
var newLine = newLines[j];
|
||||
var newEscapedLine = utils.escape(newLine.content);
|
||||
lines += this.makeLineHtml(isCombined, newLine.type, newLine.oldNumber, newLine.newNumber, newEscapedLine);
|
||||
}
|
||||
|
||||
return lines;
|
||||
};
|
||||
|
||||
LineByLinePrinter.prototype.makeLineHtml = function(isCombined, type, oldNumber, newNumber, content, possiblePrefix) {
|
||||
var lineNumberTemplate = hoganUtils.render(baseTemplatesPath, 'numbers', {
|
||||
oldNumber: utils.valueOrEmpty(oldNumber),
|
||||
newNumber: utils.valueOrEmpty(newNumber)
|
||||
});
|
||||
|
||||
var lineWithoutPrefix = content;
|
||||
var prefix = possiblePrefix;
|
||||
|
||||
if (!prefix) {
|
||||
var lineWithPrefix = printerUtils.separatePrefix(isCombined, content);
|
||||
prefix = lineWithPrefix.prefix;
|
||||
lineWithoutPrefix = lineWithPrefix.line;
|
||||
}
|
||||
|
||||
if (prefix === ' ') {
|
||||
prefix = ' ';
|
||||
}
|
||||
|
||||
return hoganUtils.render(genericTemplatesPath, 'line',
|
||||
{
|
||||
type: type,
|
||||
lineClass: 'd2h-code-linenumber',
|
||||
contentClass: 'd2h-code-line',
|
||||
prefix: prefix,
|
||||
content: lineWithoutPrefix,
|
||||
lineNumber: lineNumberTemplate
|
||||
});
|
||||
};
|
||||
|
||||
LineByLinePrinter.prototype._generateEmptyDiff = function() {
|
||||
return hoganUtils.render(genericTemplatesPath, 'empty-diff', {
|
||||
contentClass: 'd2h-code-line',
|
||||
diffParser: diffParser
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.LineByLinePrinter = LineByLinePrinter;
|
||||
})();
|
||||
296
src/line-by-line-renderer.ts
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
import HoganJsUtils from "./hoganjs-utils";
|
||||
import * as Rematch from "./rematch";
|
||||
import * as renderUtils from "./render-utils";
|
||||
import {
|
||||
DiffFile,
|
||||
DiffLine,
|
||||
LineType,
|
||||
DiffBlock,
|
||||
DiffLineDeleted,
|
||||
DiffLineContent,
|
||||
DiffLineContext,
|
||||
DiffLineInserted
|
||||
} from "./types";
|
||||
|
||||
export interface LineByLineRendererConfig extends renderUtils.RenderConfig {
|
||||
renderNothingWhenEmpty?: boolean;
|
||||
matchingMaxComparisons?: number;
|
||||
maxLineSizeInBlockForComparison?: number;
|
||||
}
|
||||
|
||||
export const defaultLineByLineRendererConfig = {
|
||||
...renderUtils.defaultRenderConfig,
|
||||
renderNothingWhenEmpty: false,
|
||||
matchingMaxComparisons: 2500,
|
||||
maxLineSizeInBlockForComparison: 200
|
||||
};
|
||||
|
||||
const genericTemplatesPath = "generic";
|
||||
const baseTemplatesPath = "line-by-line";
|
||||
const iconsBaseTemplatesPath = "icon";
|
||||
const tagsBaseTemplatesPath = "tag";
|
||||
|
||||
export default class LineByLineRenderer {
|
||||
private readonly hoganUtils: HoganJsUtils;
|
||||
private readonly config: typeof defaultLineByLineRendererConfig;
|
||||
|
||||
constructor(hoganUtils: HoganJsUtils, config: LineByLineRendererConfig = {}) {
|
||||
this.hoganUtils = hoganUtils;
|
||||
this.config = { ...defaultLineByLineRendererConfig, ...config };
|
||||
}
|
||||
|
||||
render(diffFiles: DiffFile[]): string {
|
||||
const diffsHtml = diffFiles
|
||||
.map(file => {
|
||||
let diffs;
|
||||
if (file.blocks.length) {
|
||||
diffs = this.generateFileHtml(file);
|
||||
} else {
|
||||
diffs = this.generateEmptyDiff();
|
||||
}
|
||||
return this.makeFileDiffHtml(file, diffs);
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
return this.hoganUtils.render(genericTemplatesPath, "wrapper", { content: diffsHtml });
|
||||
}
|
||||
|
||||
makeFileDiffHtml(file: DiffFile, diffs: string): string {
|
||||
if (this.config.renderNothingWhenEmpty && Array.isArray(file.blocks) && file.blocks.length === 0) return "";
|
||||
|
||||
const fileDiffTemplate = this.hoganUtils.template(baseTemplatesPath, "file-diff");
|
||||
const filePathTemplate = this.hoganUtils.template(genericTemplatesPath, "file-path");
|
||||
const fileIconTemplate = this.hoganUtils.template(iconsBaseTemplatesPath, "file");
|
||||
const fileTagTemplate = this.hoganUtils.template(tagsBaseTemplatesPath, renderUtils.getFileIcon(file));
|
||||
|
||||
return fileDiffTemplate.render({
|
||||
file: file,
|
||||
fileHtmlId: renderUtils.getHtmlId(file),
|
||||
diffs: diffs,
|
||||
filePath: filePathTemplate.render(
|
||||
{
|
||||
fileDiffName: renderUtils.filenameDiff(file)
|
||||
},
|
||||
{
|
||||
fileIcon: fileIconTemplate,
|
||||
fileTag: fileTagTemplate
|
||||
}
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
generateEmptyDiff(): string {
|
||||
return this.hoganUtils.render(genericTemplatesPath, "empty-diff", {
|
||||
contentClass: "d2h-code-line",
|
||||
CSSLineClass: renderUtils.CSSLineClass
|
||||
});
|
||||
}
|
||||
|
||||
generateFileHtml(file: DiffFile): string {
|
||||
const matcher = Rematch.newMatcherFn(
|
||||
Rematch.newDistanceFn((e: DiffLine) => renderUtils.deconstructLine(e.content, file.isCombined).content)
|
||||
);
|
||||
|
||||
return file.blocks
|
||||
.map(block => {
|
||||
let lines = this.hoganUtils.render(genericTemplatesPath, "block-header", {
|
||||
CSSLineClass: renderUtils.CSSLineClass,
|
||||
blockHeader: block.header,
|
||||
lineClass: "d2h-code-linenumber",
|
||||
contentClass: "d2h-code-line"
|
||||
});
|
||||
|
||||
this.applyLineGroupping(block).forEach(([contextLines, oldLines, newLines]) => {
|
||||
if (oldLines.length && newLines.length && !contextLines.length) {
|
||||
this.applyRematchMatching(oldLines, newLines, matcher).map(([oldLines, newLines]) => {
|
||||
const { left, right } = this.processChangedLines(file.isCombined, oldLines, newLines);
|
||||
lines += left;
|
||||
lines += right;
|
||||
});
|
||||
} else if (contextLines.length) {
|
||||
contextLines.forEach(line => {
|
||||
const { prefix, content } = renderUtils.deconstructLine(line.content, file.isCombined);
|
||||
lines += this.generateSingleLineHtml({
|
||||
type: renderUtils.CSSLineClass.CONTEXT,
|
||||
prefix: prefix,
|
||||
content: content,
|
||||
oldNumber: line.oldNumber,
|
||||
newNumber: line.newNumber
|
||||
});
|
||||
});
|
||||
} else if (oldLines.length || newLines.length) {
|
||||
const { left, right } = this.processChangedLines(file.isCombined, oldLines, newLines);
|
||||
lines += left;
|
||||
lines += right;
|
||||
} else {
|
||||
console.error("Unknown state reached while processing groups of lines", contextLines, oldLines, newLines);
|
||||
}
|
||||
});
|
||||
|
||||
return lines;
|
||||
})
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
applyLineGroupping(block: DiffBlock): DiffLineGroups {
|
||||
const blockLinesGroups: DiffLineGroups = [];
|
||||
|
||||
let oldLines: (DiffLineDeleted & DiffLineContent)[] = [];
|
||||
let newLines: (DiffLineInserted & DiffLineContent)[] = [];
|
||||
|
||||
for (let i = 0; i < block.lines.length; i++) {
|
||||
const diffLine = block.lines[i];
|
||||
|
||||
if (
|
||||
(diffLine.type !== LineType.INSERT && newLines.length) ||
|
||||
(diffLine.type === LineType.CONTEXT && oldLines.length > 0)
|
||||
) {
|
||||
blockLinesGroups.push([[], oldLines, newLines]);
|
||||
oldLines = [];
|
||||
newLines = [];
|
||||
}
|
||||
|
||||
if (diffLine.type === LineType.CONTEXT) {
|
||||
blockLinesGroups.push([[diffLine], [], []]);
|
||||
} else if (diffLine.type === LineType.INSERT && oldLines.length === 0) {
|
||||
blockLinesGroups.push([[], [], [diffLine]]);
|
||||
} else if (diffLine.type === LineType.INSERT && oldLines.length > 0) {
|
||||
newLines.push(diffLine);
|
||||
} else if (diffLine.type === LineType.DELETE) {
|
||||
oldLines.push(diffLine);
|
||||
}
|
||||
}
|
||||
|
||||
if (oldLines.length || newLines.length) {
|
||||
blockLinesGroups.push([[], oldLines, newLines]);
|
||||
oldLines = [];
|
||||
newLines = [];
|
||||
}
|
||||
|
||||
return blockLinesGroups;
|
||||
}
|
||||
|
||||
applyRematchMatching(
|
||||
oldLines: DiffLine[],
|
||||
newLines: DiffLine[],
|
||||
matcher: Rematch.MatcherFn<DiffLine>
|
||||
): DiffLine[][][] {
|
||||
const comparisons = oldLines.length * newLines.length;
|
||||
const maxLineSizeInBlock = Math.max.apply(
|
||||
null,
|
||||
[0].concat(oldLines.concat(newLines).map(elem => elem.content.length))
|
||||
);
|
||||
const doMatching =
|
||||
comparisons < this.config.matchingMaxComparisons &&
|
||||
maxLineSizeInBlock < this.config.maxLineSizeInBlockForComparison &&
|
||||
(this.config.matching === "lines" || this.config.matching === "words");
|
||||
|
||||
const matches = doMatching ? matcher(oldLines, newLines) : [[oldLines, newLines]];
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
processChangedLines(isCombined: boolean, oldLines: DiffLine[], newLines: DiffLine[]): FileHtml {
|
||||
const fileHtml = {
|
||||
right: "",
|
||||
left: ""
|
||||
};
|
||||
|
||||
const maxLinesNumber = Math.max(oldLines.length, newLines.length);
|
||||
for (let i = 0; i < maxLinesNumber; i++) {
|
||||
const oldLine = oldLines[i];
|
||||
const newLine = newLines[i];
|
||||
|
||||
const diff =
|
||||
oldLine !== undefined && newLine !== undefined
|
||||
? renderUtils.diffHighlight(oldLine.content, newLine.content, isCombined, this.config)
|
||||
: undefined;
|
||||
|
||||
const preparedOldLine =
|
||||
oldLine !== undefined && oldLine.oldNumber !== undefined
|
||||
? {
|
||||
...(diff !== undefined
|
||||
? {
|
||||
prefix: diff.oldLine.prefix,
|
||||
content: diff.oldLine.content,
|
||||
type: renderUtils.CSSLineClass.DELETE_CHANGES
|
||||
}
|
||||
: {
|
||||
...renderUtils.deconstructLine(oldLine.content, isCombined),
|
||||
type: renderUtils.toCSSClass(oldLine.type)
|
||||
}),
|
||||
oldNumber: oldLine.oldNumber,
|
||||
newNumber: oldLine.newNumber
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const preparedNewLine =
|
||||
newLine !== undefined && newLine.newNumber !== undefined
|
||||
? {
|
||||
...(diff !== undefined
|
||||
? {
|
||||
prefix: diff.newLine.prefix,
|
||||
content: diff.newLine.content,
|
||||
type: renderUtils.CSSLineClass.INSERT_CHANGES
|
||||
}
|
||||
: {
|
||||
...renderUtils.deconstructLine(newLine.content, isCombined),
|
||||
type: renderUtils.toCSSClass(newLine.type)
|
||||
}),
|
||||
oldNumber: newLine.oldNumber,
|
||||
newNumber: newLine.newNumber
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const { left, right } = this.generateLineHtml(preparedOldLine, preparedNewLine);
|
||||
fileHtml.left += left;
|
||||
fileHtml.right += right;
|
||||
}
|
||||
|
||||
return fileHtml;
|
||||
}
|
||||
|
||||
generateLineHtml(oldLine?: DiffPreparedLine, newLine?: DiffPreparedLine): FileHtml {
|
||||
return {
|
||||
left: this.generateSingleLineHtml(oldLine),
|
||||
right: this.generateSingleLineHtml(newLine)
|
||||
};
|
||||
}
|
||||
|
||||
generateSingleLineHtml(line?: DiffPreparedLine): string {
|
||||
if (line === undefined) return "";
|
||||
|
||||
const lineNumberHtml = this.hoganUtils.render(baseTemplatesPath, "numbers", {
|
||||
oldNumber: line.oldNumber || "",
|
||||
newNumber: line.newNumber || ""
|
||||
});
|
||||
|
||||
return this.hoganUtils.render(genericTemplatesPath, "line", {
|
||||
type: line.type,
|
||||
lineClass: "d2h-code-linenumber",
|
||||
contentClass: "d2h-code-line",
|
||||
prefix: line.prefix === " " ? " " : line.prefix,
|
||||
content: line.content,
|
||||
lineNumber: lineNumberHtml
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
type DiffLineGroups = [
|
||||
(DiffLineContext & DiffLineContent)[],
|
||||
(DiffLineDeleted & DiffLineContent)[],
|
||||
(DiffLineInserted & DiffLineContent)[]
|
||||
][];
|
||||
|
||||
type DiffPreparedLine = {
|
||||
type: renderUtils.CSSLineClass;
|
||||
prefix: string;
|
||||
content: string;
|
||||
oldNumber?: number;
|
||||
newNumber?: number;
|
||||
};
|
||||
|
||||
type FileHtml = {
|
||||
left: string;
|
||||
right: string;
|
||||
};
|
||||
|
|
@ -1,250 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* PrinterUtils (printer-utils.js)
|
||||
* Author: rtfpessoa
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var jsDiff = require('diff');
|
||||
var utils = require('./utils.js').Utils;
|
||||
var Rematch = require('./rematch.js').Rematch;
|
||||
|
||||
var separator = '/';
|
||||
|
||||
function PrinterUtils() {
|
||||
}
|
||||
|
||||
PrinterUtils.prototype.separatePrefix = function(isCombined, line) {
|
||||
var prefix;
|
||||
var lineWithoutPrefix;
|
||||
|
||||
if (isCombined) {
|
||||
prefix = line.substring(0, 2);
|
||||
lineWithoutPrefix = line.substring(2);
|
||||
} else {
|
||||
prefix = line.substring(0, 1);
|
||||
lineWithoutPrefix = line.substring(1);
|
||||
}
|
||||
|
||||
return {
|
||||
'prefix': prefix,
|
||||
'line': lineWithoutPrefix
|
||||
};
|
||||
};
|
||||
|
||||
PrinterUtils.prototype.getHtmlId = function(file) {
|
||||
var hashCode = function(text) {
|
||||
var i, chr, len;
|
||||
var hash = 0;
|
||||
|
||||
for (i = 0, len = text.length; i < len; i++) {
|
||||
chr = text.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + chr;
|
||||
hash |= 0; // Convert to 32bit integer
|
||||
}
|
||||
|
||||
return hash;
|
||||
};
|
||||
|
||||
return 'd2h-' + hashCode(this.getDiffName(file)).toString().slice(-6);
|
||||
};
|
||||
|
||||
PrinterUtils.prototype.getDiffName = function(file) {
|
||||
var oldFilename = unifyPath(file.oldName);
|
||||
var newFilename = unifyPath(file.newName);
|
||||
|
||||
if (oldFilename && newFilename && oldFilename !== newFilename && !isDevNullName(oldFilename) && !isDevNullName(newFilename)) {
|
||||
var prefixPaths = [];
|
||||
var suffixPaths = [];
|
||||
|
||||
var oldFilenameParts = oldFilename.split(separator);
|
||||
var newFilenameParts = newFilename.split(separator);
|
||||
|
||||
var oldFilenamePartsSize = oldFilenameParts.length;
|
||||
var newFilenamePartsSize = newFilenameParts.length;
|
||||
|
||||
var i = 0;
|
||||
var j = oldFilenamePartsSize - 1;
|
||||
var k = newFilenamePartsSize - 1;
|
||||
|
||||
while (i < j && i < k) {
|
||||
if (oldFilenameParts[i] === newFilenameParts[i]) {
|
||||
prefixPaths.push(newFilenameParts[i]);
|
||||
i += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (j > i && k > i) {
|
||||
if (oldFilenameParts[j] === newFilenameParts[k]) {
|
||||
suffixPaths.unshift(newFilenameParts[k]);
|
||||
j -= 1;
|
||||
k -= 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var finalPrefix = prefixPaths.join(separator);
|
||||
var finalSuffix = suffixPaths.join(separator);
|
||||
|
||||
var oldRemainingPath = oldFilenameParts.slice(i, j + 1).join(separator);
|
||||
var newRemainingPath = newFilenameParts.slice(i, k + 1).join(separator);
|
||||
|
||||
if (finalPrefix.length && finalSuffix.length) {
|
||||
return finalPrefix + separator + '{' + oldRemainingPath + ' → ' + newRemainingPath + '}' + separator + finalSuffix;
|
||||
} else if (finalPrefix.length) {
|
||||
return finalPrefix + separator + '{' + oldRemainingPath + ' → ' + newRemainingPath + '}';
|
||||
} else if (finalSuffix.length) {
|
||||
return '{' + oldRemainingPath + ' → ' + newRemainingPath + '}' + separator + finalSuffix;
|
||||
}
|
||||
|
||||
return oldFilename + ' → ' + newFilename;
|
||||
} else if (newFilename && !isDevNullName(newFilename)) {
|
||||
return newFilename;
|
||||
} else if (oldFilename) {
|
||||
return oldFilename;
|
||||
}
|
||||
|
||||
return 'unknown/file/path';
|
||||
};
|
||||
|
||||
PrinterUtils.prototype.getFileTypeIcon = function(file) {
|
||||
var templateName = 'file-changed';
|
||||
|
||||
if (file.isRename) {
|
||||
templateName = 'file-renamed';
|
||||
} else if (file.isCopy) {
|
||||
templateName = 'file-renamed';
|
||||
} else if (file.isNew) {
|
||||
templateName = 'file-added';
|
||||
} else if (file.isDeleted) {
|
||||
templateName = 'file-deleted';
|
||||
} else if (file.newName !== file.oldName) {
|
||||
// If file is not Added, not Deleted and the names changed it must be a rename :)
|
||||
templateName = 'file-renamed';
|
||||
}
|
||||
|
||||
return templateName;
|
||||
};
|
||||
|
||||
PrinterUtils.prototype.diffHighlight = function(diffLine1, diffLine2, config) {
|
||||
var linePrefix1, linePrefix2, unprefixedLine1, unprefixedLine2;
|
||||
|
||||
var prefixSize = 1;
|
||||
|
||||
if (config.isCombined) {
|
||||
prefixSize = 2;
|
||||
}
|
||||
|
||||
linePrefix1 = diffLine1.substr(0, prefixSize);
|
||||
linePrefix2 = diffLine2.substr(0, prefixSize);
|
||||
unprefixedLine1 = diffLine1.substr(prefixSize);
|
||||
unprefixedLine2 = diffLine2.substr(prefixSize);
|
||||
|
||||
if (unprefixedLine1.length > config.maxLineLengthHighlight ||
|
||||
unprefixedLine2.length > config.maxLineLengthHighlight) {
|
||||
return {
|
||||
first: {
|
||||
prefix: linePrefix1,
|
||||
line: utils.escape(unprefixedLine1)
|
||||
},
|
||||
second: {
|
||||
prefix: linePrefix2,
|
||||
line: utils.escape(unprefixedLine2)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var diff;
|
||||
if (config.diffStyle === 'char') {
|
||||
diff = jsDiff.diffChars(unprefixedLine1, unprefixedLine2);
|
||||
} else {
|
||||
diff = jsDiff.diffWordsWithSpace(unprefixedLine1, unprefixedLine2);
|
||||
}
|
||||
|
||||
var highlightedLine = '';
|
||||
|
||||
var changedWords = [];
|
||||
if (config.diffStyle === 'word' && config.matching === 'words') {
|
||||
var treshold = 0.25;
|
||||
|
||||
if (typeof (config.matchWordsThreshold) !== 'undefined') {
|
||||
treshold = config.matchWordsThreshold;
|
||||
}
|
||||
|
||||
var matcher = Rematch.rematch(function(a, b) {
|
||||
var amod = a.value;
|
||||
var bmod = b.value;
|
||||
|
||||
return Rematch.distance(amod, bmod);
|
||||
});
|
||||
|
||||
var removed = diff.filter(function isRemoved(element) {
|
||||
return element.removed;
|
||||
});
|
||||
|
||||
var added = diff.filter(function isAdded(element) {
|
||||
return element.added;
|
||||
});
|
||||
|
||||
var chunks = matcher(added, removed);
|
||||
chunks.forEach(function(chunk) {
|
||||
if (chunk[0].length === 1 && chunk[1].length === 1) {
|
||||
var dist = Rematch.distance(chunk[0][0].value, chunk[1][0].value);
|
||||
if (dist < treshold) {
|
||||
changedWords.push(chunk[0][0]);
|
||||
changedWords.push(chunk[1][0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
diff.forEach(function(part) {
|
||||
var addClass = changedWords.indexOf(part) > -1 ? ' class="d2h-change"' : '';
|
||||
var elemType = part.added ? 'ins' : part.removed ? 'del' : null;
|
||||
var escapedValue = utils.escape(part.value);
|
||||
|
||||
if (elemType !== null) {
|
||||
highlightedLine += '<' + elemType + addClass + '>' + escapedValue + '</' + elemType + '>';
|
||||
} else {
|
||||
highlightedLine += escapedValue;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
first: {
|
||||
prefix: linePrefix1,
|
||||
line: removeIns(highlightedLine)
|
||||
},
|
||||
second: {
|
||||
prefix: linePrefix2,
|
||||
line: removeDel(highlightedLine)
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function unifyPath(path) {
|
||||
if (path) {
|
||||
return path.replace('\\', '/');
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
function isDevNullName(name) {
|
||||
return name.indexOf('dev/null') !== -1;
|
||||
}
|
||||
|
||||
function removeIns(line) {
|
||||
return line.replace(/(<ins[^>]*>((.|\n)*?)<\/ins>)/g, '');
|
||||
}
|
||||
|
||||
function removeDel(line) {
|
||||
return line.replace(/(<del[^>]*>((.|\n)*?)<\/del>)/g, '');
|
||||
}
|
||||
|
||||
module.exports.PrinterUtils = new PrinterUtils();
|
||||
})();
|
||||
141
src/rematch.js
|
|
@ -1,141 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* Rematch (rematch.js)
|
||||
* Matching two sequences of objects by similarity
|
||||
* Author: W. Illmeyer, Nexxar GmbH
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var Rematch = {};
|
||||
|
||||
/*
|
||||
Copyright (c) 2011 Andrei Mackenzie
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
||||
THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
function levenshtein(a, b) {
|
||||
if (a.length === 0) {
|
||||
return b.length;
|
||||
}
|
||||
if (b.length === 0) {
|
||||
return a.length;
|
||||
}
|
||||
|
||||
var matrix = [];
|
||||
|
||||
// Increment along the first column of each row
|
||||
var i;
|
||||
for (i = 0; i <= b.length; i++) {
|
||||
matrix[i] = [i];
|
||||
}
|
||||
|
||||
// Increment each column in the first row
|
||||
var j;
|
||||
for (j = 0; j <= a.length; j++) {
|
||||
matrix[0][j] = j;
|
||||
}
|
||||
|
||||
// Fill in the rest of the matrix
|
||||
for (i = 1; i <= b.length; i++) {
|
||||
for (j = 1; j <= a.length; j++) {
|
||||
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
||||
matrix[i][j] = matrix[i - 1][j - 1];
|
||||
} else {
|
||||
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, // Substitution
|
||||
Math.min(matrix[i][j - 1] + 1, // Insertion
|
||||
matrix[i - 1][j] + 1)); // Deletion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matrix[b.length][a.length];
|
||||
}
|
||||
|
||||
Rematch.levenshtein = levenshtein;
|
||||
|
||||
Rematch.distance = function distance(x, y) {
|
||||
x = x.trim();
|
||||
y = y.trim();
|
||||
var lev = levenshtein(x, y);
|
||||
var score = lev / (x.length + y.length);
|
||||
|
||||
return score;
|
||||
};
|
||||
|
||||
Rematch.rematch = function rematch(distanceFunction) {
|
||||
function findBestMatch(a, b, cache) {
|
||||
var bestMatchDist = Infinity;
|
||||
var bestMatch;
|
||||
for (var i = 0; i < a.length; ++i) {
|
||||
for (var j = 0; j < b.length; ++j) {
|
||||
var cacheKey = JSON.stringify([a[i], b[j]]);
|
||||
var md;
|
||||
if (cache.hasOwnProperty(cacheKey)) {
|
||||
md = cache[cacheKey];
|
||||
} else {
|
||||
md = distanceFunction(a[i], b[j]);
|
||||
cache[cacheKey] = md;
|
||||
}
|
||||
if (md < bestMatchDist) {
|
||||
bestMatchDist = md;
|
||||
bestMatch = {indexA: i, indexB: j, score: bestMatchDist};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
function group(a, b, level, cache) {
|
||||
if (typeof (cache) === 'undefined') {
|
||||
cache = {};
|
||||
}
|
||||
|
||||
var bm = findBestMatch(a, b, cache);
|
||||
|
||||
if (!level) {
|
||||
level = 0;
|
||||
}
|
||||
|
||||
if (!bm || (a.length + b.length < 3)) {
|
||||
return [[a, b]];
|
||||
}
|
||||
|
||||
var a1 = a.slice(0, bm.indexA);
|
||||
var b1 = b.slice(0, bm.indexB);
|
||||
var aMatch = [a[bm.indexA]];
|
||||
var bMatch = [b[bm.indexB]];
|
||||
var tailA = bm.indexA + 1;
|
||||
var tailB = bm.indexB + 1;
|
||||
var a2 = a.slice(tailA);
|
||||
var b2 = b.slice(tailB);
|
||||
|
||||
var group1 = group(a1, b1, level + 1, cache);
|
||||
var groupMatch = group(aMatch, bMatch, level + 1, cache);
|
||||
var group2 = group(a2, b2, level + 1, cache);
|
||||
var result = groupMatch;
|
||||
|
||||
if (bm.indexA > 0 || bm.indexB > 0) {
|
||||
result = group1.concat(result);
|
||||
}
|
||||
|
||||
if (a.length > tailA || b.length > tailB) {
|
||||
result = result.concat(group2);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return group;
|
||||
};
|
||||
|
||||
module.exports.Rematch = Rematch;
|
||||
})();
|
||||
137
src/rematch.ts
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* Matching two sequences of objects by similarity
|
||||
* Author: W. Illmeyer, Nexxar GmbH
|
||||
*/
|
||||
|
||||
export type BestMatch = {
|
||||
indexA: number;
|
||||
indexB: number;
|
||||
score: number;
|
||||
};
|
||||
|
||||
/*
|
||||
Copyright (c) 2011 Andrei Mackenzie
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
||||
THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
export function levenshtein(a: string, b: string): number {
|
||||
if (a.length === 0) {
|
||||
return b.length;
|
||||
}
|
||||
if (b.length === 0) {
|
||||
return a.length;
|
||||
}
|
||||
|
||||
const matrix = [];
|
||||
|
||||
// Increment along the first column of each row
|
||||
let i;
|
||||
for (i = 0; i <= b.length; i++) {
|
||||
matrix[i] = [i];
|
||||
}
|
||||
|
||||
// Increment each column in the first row
|
||||
let j;
|
||||
for (j = 0; j <= a.length; j++) {
|
||||
matrix[0][j] = j;
|
||||
}
|
||||
|
||||
// Fill in the rest of the matrix
|
||||
for (i = 1; i <= b.length; i++) {
|
||||
for (j = 1; j <= a.length; j++) {
|
||||
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
||||
matrix[i][j] = matrix[i - 1][j - 1];
|
||||
} else {
|
||||
matrix[i][j] = Math.min(
|
||||
matrix[i - 1][j - 1] + 1, // Substitution
|
||||
Math.min(
|
||||
matrix[i][j - 1] + 1, // Insertion
|
||||
matrix[i - 1][j] + 1
|
||||
)
|
||||
); // Deletion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matrix[b.length][a.length];
|
||||
}
|
||||
|
||||
export type DistanceFn<T> = (x: T, y: T) => number;
|
||||
|
||||
export function newDistanceFn<T>(str: (value: T) => string): DistanceFn<T> {
|
||||
return (x: T, y: T): number => {
|
||||
const xValue = str(x).trim();
|
||||
const yValue = str(y).trim();
|
||||
const lev = levenshtein(xValue, yValue);
|
||||
const score = lev / (xValue.length + yValue.length);
|
||||
|
||||
return score;
|
||||
};
|
||||
}
|
||||
|
||||
export type MatcherFn<T> = (a: T[], b: T[], level?: number, cache?: Map<string, number>) => T[][][];
|
||||
|
||||
export function newMatcherFn<T>(distance: (x: T, y: T) => number): MatcherFn<T> {
|
||||
function findBestMatch(a: T[], b: T[], cache: Map<string, number> = new Map()): BestMatch | undefined {
|
||||
let bestMatchDist = Infinity;
|
||||
let bestMatch;
|
||||
|
||||
for (let i = 0; i < a.length; ++i) {
|
||||
for (let j = 0; j < b.length; ++j) {
|
||||
const cacheKey = JSON.stringify([a[i], b[j]]);
|
||||
let md;
|
||||
if (!(cache.has(cacheKey) && (md = cache.get(cacheKey)))) {
|
||||
md = distance(a[i], b[j]);
|
||||
cache.set(cacheKey, md);
|
||||
}
|
||||
if (md < bestMatchDist) {
|
||||
bestMatchDist = md;
|
||||
bestMatch = { indexA: i, indexB: j, score: bestMatchDist };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
function group(a: T[], b: T[], level = 0, cache: Map<string, number> = new Map()): T[][][] {
|
||||
const bm = findBestMatch(a, b, cache);
|
||||
|
||||
if (!bm || a.length + b.length < 3) {
|
||||
return [[a, b]];
|
||||
}
|
||||
|
||||
const a1 = a.slice(0, bm.indexA);
|
||||
const b1 = b.slice(0, bm.indexB);
|
||||
const aMatch = [a[bm.indexA]];
|
||||
const bMatch = [b[bm.indexB]];
|
||||
const tailA = bm.indexA + 1;
|
||||
const tailB = bm.indexB + 1;
|
||||
const a2 = a.slice(tailA);
|
||||
const b2 = b.slice(tailB);
|
||||
|
||||
const group1 = group(a1, b1, level + 1, cache);
|
||||
const groupMatch = group(aMatch, bMatch, level + 1, cache);
|
||||
const group2 = group(a2, b2, level + 1, cache);
|
||||
let result = groupMatch;
|
||||
|
||||
if (bm.indexA > 0 || bm.indexB > 0) {
|
||||
result = group1.concat(result);
|
||||
}
|
||||
|
||||
if (a.length > tailA || b.length > tailB) {
|
||||
result = result.concat(group2);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return group;
|
||||
}
|
||||
275
src/render-utils.ts
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
import * as jsDiff from "diff";
|
||||
|
||||
import { unifyPath, hashCode } from "./utils";
|
||||
import * as rematch from "./rematch";
|
||||
import { LineMatchingType, DiffStyleType, LineType, DiffLineParts, DiffFile, DiffFileName } from "./types";
|
||||
|
||||
export enum CSSLineClass {
|
||||
INSERTS = "d2h-ins",
|
||||
DELETES = "d2h-del",
|
||||
CONTEXT = "d2h-cntx",
|
||||
INFO = "d2h-info",
|
||||
INSERT_CHANGES = "d2h-ins d2h-change",
|
||||
DELETE_CHANGES = "d2h-del d2h-change"
|
||||
}
|
||||
|
||||
export type HighlightedLines = {
|
||||
oldLine: {
|
||||
prefix: string;
|
||||
content: string;
|
||||
};
|
||||
newLine: {
|
||||
prefix: string;
|
||||
content: string;
|
||||
};
|
||||
};
|
||||
|
||||
export interface RenderConfig {
|
||||
matching?: LineMatchingType;
|
||||
matchWordsThreshold?: number;
|
||||
maxLineLengthHighlight?: number;
|
||||
diffStyle?: DiffStyleType;
|
||||
}
|
||||
|
||||
export const defaultRenderConfig = {
|
||||
matching: LineMatchingType.NONE,
|
||||
matchWordsThreshold: 0.25,
|
||||
maxLineLengthHighlight: 10000,
|
||||
diffStyle: DiffStyleType.WORD
|
||||
};
|
||||
|
||||
const separator = "/";
|
||||
const distance = rematch.newDistanceFn((change: jsDiff.Change) => change.value);
|
||||
const matcher = rematch.newMatcherFn(distance);
|
||||
|
||||
function isDevNullName(name: string): boolean {
|
||||
return name.indexOf("dev/null") !== -1;
|
||||
}
|
||||
|
||||
function removeInsElements(line: string): string {
|
||||
return line.replace(/(<ins[^>]*>((.|\n)*?)<\/ins>)/g, "");
|
||||
}
|
||||
|
||||
function removeDelElements(line: string): string {
|
||||
return line.replace(/(<del[^>]*>((.|\n)*?)<\/del>)/g, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from LineType to CSSLineClass
|
||||
*/
|
||||
export function toCSSClass(lineType: LineType): CSSLineClass {
|
||||
switch (lineType) {
|
||||
case LineType.CONTEXT:
|
||||
return CSSLineClass.CONTEXT;
|
||||
case LineType.INSERT:
|
||||
return CSSLineClass.INSERTS;
|
||||
case LineType.DELETE:
|
||||
return CSSLineClass.DELETES;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefix length of the hunk lines in the diff
|
||||
*/
|
||||
function prefixLength(isCombined: boolean): number {
|
||||
return isCombined ? 2 : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes all required characters for safe HTML rendering
|
||||
*/
|
||||
// TODO: Test this method inside deconstructLine since it should not be used anywhere else
|
||||
export function escapeForHtml(str: string): string {
|
||||
return str
|
||||
.slice(0)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'")
|
||||
.replace(/\//g, "/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Deconstructs diff @line by separating the content from the prefix type
|
||||
*/
|
||||
export function deconstructLine(line: string, isCombined: boolean): DiffLineParts {
|
||||
const indexToSplit = prefixLength(isCombined);
|
||||
return {
|
||||
prefix: line.substring(0, indexToSplit),
|
||||
content: escapeForHtml(line.substring(indexToSplit))
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates pretty filename diffs
|
||||
*
|
||||
* e.g.:
|
||||
* 1. file = { oldName: "my/path/to/file.js", newName: "my/path/to/new-file.js" }
|
||||
* returns "my/path/to/{file.js → new-file.js}"
|
||||
* 2. file = { oldName: "my/path/to/file.js", newName: "very/new/path/to/new-file.js" }
|
||||
* returns "my/path/to/file.js → very/new/path/to/new-file.js"
|
||||
* 3. file = { oldName: "my/path/to/file.js", newName: "my/path/for/file.js" }
|
||||
* returns "my/path/{to → for}/file.js"
|
||||
*/
|
||||
export function filenameDiff(file: DiffFileName): string {
|
||||
// TODO: Move unify path to parsing
|
||||
const oldFilename = unifyPath(file.oldName);
|
||||
const newFilename = unifyPath(file.newName);
|
||||
|
||||
if (oldFilename !== newFilename && !isDevNullName(oldFilename) && !isDevNullName(newFilename)) {
|
||||
const prefixPaths = [];
|
||||
const suffixPaths = [];
|
||||
|
||||
const oldFilenameParts = oldFilename.split(separator);
|
||||
const newFilenameParts = newFilename.split(separator);
|
||||
|
||||
const oldFilenamePartsSize = oldFilenameParts.length;
|
||||
const newFilenamePartsSize = newFilenameParts.length;
|
||||
|
||||
let i = 0;
|
||||
let j = oldFilenamePartsSize - 1;
|
||||
let k = newFilenamePartsSize - 1;
|
||||
|
||||
while (i < j && i < k) {
|
||||
if (oldFilenameParts[i] === newFilenameParts[i]) {
|
||||
prefixPaths.push(newFilenameParts[i]);
|
||||
i += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (j > i && k > i) {
|
||||
if (oldFilenameParts[j] === newFilenameParts[k]) {
|
||||
suffixPaths.unshift(newFilenameParts[k]);
|
||||
j -= 1;
|
||||
k -= 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const finalPrefix = prefixPaths.join(separator);
|
||||
const finalSuffix = suffixPaths.join(separator);
|
||||
|
||||
const oldRemainingPath = oldFilenameParts.slice(i, j + 1).join(separator);
|
||||
const newRemainingPath = newFilenameParts.slice(i, k + 1).join(separator);
|
||||
|
||||
if (finalPrefix.length && finalSuffix.length) {
|
||||
return (
|
||||
finalPrefix + separator + "{" + oldRemainingPath + " → " + newRemainingPath + "}" + separator + finalSuffix
|
||||
);
|
||||
} else if (finalPrefix.length) {
|
||||
return finalPrefix + separator + "{" + oldRemainingPath + " → " + newRemainingPath + "}";
|
||||
} else if (finalSuffix.length) {
|
||||
return "{" + oldRemainingPath + " → " + newRemainingPath + "}" + separator + finalSuffix;
|
||||
}
|
||||
|
||||
return oldFilename + " → " + newFilename;
|
||||
} else if (!isDevNullName(newFilename)) {
|
||||
return newFilename;
|
||||
} else {
|
||||
return oldFilename;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a unique string numerical identifier based on the names of the file diff
|
||||
*/
|
||||
export function getHtmlId(file: DiffFileName): string {
|
||||
return `d2h-${hashCode(filenameDiff(file))
|
||||
.toString()
|
||||
.slice(-6)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the correct icon name for the file
|
||||
*/
|
||||
export function getFileIcon(file: DiffFile): string {
|
||||
let templateName = "file-changed";
|
||||
|
||||
if (file.isRename) {
|
||||
templateName = "file-renamed";
|
||||
} else if (file.isCopy) {
|
||||
templateName = "file-renamed";
|
||||
} else if (file.isNew) {
|
||||
templateName = "file-added";
|
||||
} else if (file.isDeleted) {
|
||||
templateName = "file-deleted";
|
||||
} else if (file.newName !== file.oldName) {
|
||||
// If file is not Added, not Deleted and the names changed it must be a rename :)
|
||||
templateName = "file-renamed";
|
||||
}
|
||||
|
||||
return templateName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight differences between @diffLine1 and @diffLine2 using <ins> and <del> tags
|
||||
*/
|
||||
export function diffHighlight(
|
||||
diffLine1: string,
|
||||
diffLine2: string,
|
||||
isCombined: boolean,
|
||||
config: RenderConfig = {}
|
||||
): HighlightedLines {
|
||||
const { matching, maxLineLengthHighlight, matchWordsThreshold, diffStyle } = { ...defaultRenderConfig, ...config };
|
||||
|
||||
const line1 = deconstructLine(diffLine1, isCombined);
|
||||
const line2 = deconstructLine(diffLine2, isCombined);
|
||||
|
||||
if (line1.content.length > maxLineLengthHighlight || line2.content.length > maxLineLengthHighlight) {
|
||||
return {
|
||||
oldLine: {
|
||||
prefix: line1.prefix,
|
||||
content: line1.content
|
||||
},
|
||||
newLine: {
|
||||
prefix: line2.prefix,
|
||||
content: line2.content
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const diff =
|
||||
diffStyle === "char"
|
||||
? jsDiff.diffChars(line1.content, line2.content)
|
||||
: jsDiff.diffWordsWithSpace(line1.content, line2.content);
|
||||
|
||||
const changedWords: jsDiff.Change[] = [];
|
||||
if (diffStyle === "word" && matching === "words") {
|
||||
const removed = diff.filter(element => element.removed);
|
||||
const added = diff.filter(element => element.added);
|
||||
const chunks = matcher(added, removed);
|
||||
chunks.forEach(chunk => {
|
||||
if (chunk[0].length === 1 && chunk[1].length === 1) {
|
||||
const dist = distance(chunk[0][0], chunk[1][0]);
|
||||
if (dist < matchWordsThreshold) {
|
||||
changedWords.push(chunk[0][0]);
|
||||
changedWords.push(chunk[1][0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const highlightedLine = diff.reduce((highlightedLine, part) => {
|
||||
const elemType = part.added ? "ins" : part.removed ? "del" : null;
|
||||
const addClass = changedWords.indexOf(part) > -1 ? ' class="d2h-change"' : "";
|
||||
|
||||
return elemType !== null
|
||||
? `${highlightedLine}<${elemType}${addClass}>${part.value}</${elemType}>`
|
||||
: `${highlightedLine}${part.value}`;
|
||||
}, "");
|
||||
|
||||
return {
|
||||
oldLine: {
|
||||
prefix: line1.prefix,
|
||||
content: removeInsElements(highlightedLine)
|
||||
},
|
||||
newLine: {
|
||||
prefix: line2.prefix,
|
||||
content: removeDelElements(highlightedLine)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -1,275 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* HtmlPrinter (html-printer.js)
|
||||
* Author: rtfpessoa
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var diffParser = require('./diff-parser.js').DiffParser;
|
||||
var printerUtils = require('./printer-utils.js').PrinterUtils;
|
||||
var utils = require('./utils.js').Utils;
|
||||
var Rematch = require('./rematch.js').Rematch;
|
||||
|
||||
var hoganUtils;
|
||||
|
||||
var genericTemplatesPath = 'generic';
|
||||
var baseTemplatesPath = 'side-by-side';
|
||||
var iconsBaseTemplatesPath = 'icon';
|
||||
var tagsBaseTemplatesPath = 'tag';
|
||||
|
||||
var matcher = Rematch.rematch(function(a, b) {
|
||||
var amod = a.content.substr(1);
|
||||
var bmod = b.content.substr(1);
|
||||
|
||||
return Rematch.distance(amod, bmod);
|
||||
});
|
||||
|
||||
function SideBySidePrinter(config) {
|
||||
this.config = config;
|
||||
|
||||
var HoganJsUtils = require('./hoganjs-utils.js').HoganJsUtils;
|
||||
hoganUtils = new HoganJsUtils(config);
|
||||
}
|
||||
|
||||
SideBySidePrinter.prototype.makeDiffHtml = function(file, diffs) {
|
||||
var fileDiffTemplate = hoganUtils.template(baseTemplatesPath, 'file-diff');
|
||||
var filePathTemplate = hoganUtils.template(genericTemplatesPath, 'file-path');
|
||||
var fileIconTemplate = hoganUtils.template(iconsBaseTemplatesPath, 'file');
|
||||
var fileTagTemplate = hoganUtils.template(tagsBaseTemplatesPath, printerUtils.getFileTypeIcon(file));
|
||||
|
||||
return fileDiffTemplate.render({
|
||||
file: file,
|
||||
fileHtmlId: printerUtils.getHtmlId(file),
|
||||
diffs: diffs,
|
||||
filePath: filePathTemplate.render({
|
||||
fileDiffName: printerUtils.getDiffName(file)
|
||||
}, {
|
||||
fileIcon: fileIconTemplate,
|
||||
fileTag: fileTagTemplate
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
SideBySidePrinter.prototype.generateSideBySideJsonHtml = function(diffFiles) {
|
||||
var that = this;
|
||||
|
||||
var content = diffFiles.map(function(file) {
|
||||
var diffs;
|
||||
if (file.blocks.length) {
|
||||
diffs = that.generateSideBySideFileHtml(file);
|
||||
} else {
|
||||
diffs = that.generateEmptyDiff();
|
||||
}
|
||||
|
||||
return that.makeDiffHtml(file, diffs);
|
||||
}).join('\n');
|
||||
|
||||
return hoganUtils.render(genericTemplatesPath, 'wrapper', {'content': content});
|
||||
};
|
||||
|
||||
SideBySidePrinter.prototype.makeSideHtml = function(blockHeader) {
|
||||
return hoganUtils.render(genericTemplatesPath, 'column-line-number', {
|
||||
diffParser: diffParser,
|
||||
blockHeader: utils.escape(blockHeader),
|
||||
lineClass: 'd2h-code-side-linenumber',
|
||||
contentClass: 'd2h-code-side-line'
|
||||
});
|
||||
};
|
||||
|
||||
SideBySidePrinter.prototype.generateSideBySideFileHtml = function(file) {
|
||||
var that = this;
|
||||
var fileHtml = {};
|
||||
fileHtml.left = '';
|
||||
fileHtml.right = '';
|
||||
|
||||
file.blocks.forEach(function(block) {
|
||||
fileHtml.left += that.makeSideHtml(block.header);
|
||||
fileHtml.right += that.makeSideHtml('');
|
||||
|
||||
var oldLines = [];
|
||||
var newLines = [];
|
||||
|
||||
function processChangeBlock() {
|
||||
var matches;
|
||||
var insertType;
|
||||
var deleteType;
|
||||
|
||||
var comparisons = oldLines.length * newLines.length;
|
||||
|
||||
var maxLineSizeInBlock = Math.max.apply(null, (oldLines.concat(newLines)).map(function(elem) {
|
||||
return elem.length;
|
||||
}));
|
||||
|
||||
var doMatching = comparisons < that.config.matchingMaxComparisons &&
|
||||
maxLineSizeInBlock < that.config.maxLineSizeInBlockForComparison &&
|
||||
(that.config.matching === 'lines' || that.config.matching === 'words');
|
||||
|
||||
if (doMatching) {
|
||||
matches = matcher(oldLines, newLines);
|
||||
insertType = diffParser.LINE_TYPE.INSERT_CHANGES;
|
||||
deleteType = diffParser.LINE_TYPE.DELETE_CHANGES;
|
||||
} else {
|
||||
matches = [[oldLines, newLines]];
|
||||
insertType = diffParser.LINE_TYPE.INSERTS;
|
||||
deleteType = diffParser.LINE_TYPE.DELETES;
|
||||
}
|
||||
|
||||
matches.forEach(function(match) {
|
||||
oldLines = match[0];
|
||||
newLines = match[1];
|
||||
|
||||
var common = Math.min(oldLines.length, newLines.length);
|
||||
var max = Math.max(oldLines.length, newLines.length);
|
||||
|
||||
for (var j = 0; j < common; j++) {
|
||||
var oldLine = oldLines[j];
|
||||
var newLine = newLines[j];
|
||||
|
||||
that.config.isCombined = file.isCombined;
|
||||
|
||||
var diff = printerUtils.diffHighlight(oldLine.content, newLine.content, that.config);
|
||||
|
||||
fileHtml.left +=
|
||||
that.generateSingleLineHtml(file.isCombined, deleteType, oldLine.oldNumber,
|
||||
diff.first.line, diff.first.prefix);
|
||||
fileHtml.right +=
|
||||
that.generateSingleLineHtml(file.isCombined, insertType, newLine.newNumber,
|
||||
diff.second.line, diff.second.prefix);
|
||||
}
|
||||
|
||||
if (max > common) {
|
||||
var oldSlice = oldLines.slice(common);
|
||||
var newSlice = newLines.slice(common);
|
||||
|
||||
var tmpHtml = that.processLines(file.isCombined, oldSlice, newSlice);
|
||||
fileHtml.left += tmpHtml.left;
|
||||
fileHtml.right += tmpHtml.right;
|
||||
}
|
||||
});
|
||||
|
||||
oldLines = [];
|
||||
newLines = [];
|
||||
}
|
||||
|
||||
for (var i = 0; i < block.lines.length; i++) {
|
||||
var line = block.lines[i];
|
||||
var prefix = line.content[0];
|
||||
var escapedLine = utils.escape(line.content.substr(1));
|
||||
|
||||
if (line.type !== diffParser.LINE_TYPE.INSERTS &&
|
||||
(newLines.length > 0 || (line.type !== diffParser.LINE_TYPE.DELETES && oldLines.length > 0))) {
|
||||
processChangeBlock();
|
||||
}
|
||||
|
||||
if (line.type === diffParser.LINE_TYPE.CONTEXT) {
|
||||
fileHtml.left += that.generateSingleLineHtml(file.isCombined, line.type, line.oldNumber, escapedLine, prefix);
|
||||
fileHtml.right += that.generateSingleLineHtml(file.isCombined, line.type, line.newNumber, escapedLine, prefix);
|
||||
} else if (line.type === diffParser.LINE_TYPE.INSERTS && !oldLines.length) {
|
||||
fileHtml.left += that.generateSingleLineHtml(file.isCombined, diffParser.LINE_TYPE.CONTEXT, '', '', '');
|
||||
fileHtml.right += that.generateSingleLineHtml(file.isCombined, line.type, line.newNumber, escapedLine, prefix);
|
||||
} else if (line.type === diffParser.LINE_TYPE.DELETES) {
|
||||
oldLines.push(line);
|
||||
} else if (line.type === diffParser.LINE_TYPE.INSERTS && Boolean(oldLines.length)) {
|
||||
newLines.push(line);
|
||||
} else {
|
||||
console.error('unknown state in html side-by-side generator');
|
||||
processChangeBlock();
|
||||
}
|
||||
}
|
||||
|
||||
processChangeBlock();
|
||||
});
|
||||
|
||||
return fileHtml;
|
||||
};
|
||||
|
||||
SideBySidePrinter.prototype.processLines = function(isCombined, oldLines, newLines) {
|
||||
var that = this;
|
||||
var fileHtml = {};
|
||||
fileHtml.left = '';
|
||||
fileHtml.right = '';
|
||||
|
||||
var maxLinesNumber = Math.max(oldLines.length, newLines.length);
|
||||
for (var i = 0; i < maxLinesNumber; i++) {
|
||||
var oldLine = oldLines[i];
|
||||
var newLine = newLines[i];
|
||||
var oldContent;
|
||||
var newContent;
|
||||
var oldPrefix;
|
||||
var newPrefix;
|
||||
|
||||
if (oldLine) {
|
||||
oldContent = utils.escape(oldLine.content.substr(1));
|
||||
oldPrefix = oldLine.content[0];
|
||||
}
|
||||
|
||||
if (newLine) {
|
||||
newContent = utils.escape(newLine.content.substr(1));
|
||||
newPrefix = newLine.content[0];
|
||||
}
|
||||
|
||||
if (oldLine && newLine) {
|
||||
fileHtml.left += that.generateSingleLineHtml(isCombined, oldLine.type, oldLine.oldNumber, oldContent, oldPrefix);
|
||||
fileHtml.right += that.generateSingleLineHtml(isCombined, newLine.type, newLine.newNumber, newContent, newPrefix);
|
||||
} else if (oldLine) {
|
||||
fileHtml.left += that.generateSingleLineHtml(isCombined, oldLine.type, oldLine.oldNumber, oldContent, oldPrefix);
|
||||
fileHtml.right += that.generateSingleLineHtml(isCombined, diffParser.LINE_TYPE.CONTEXT, '', '', '');
|
||||
} else if (newLine) {
|
||||
fileHtml.left += that.generateSingleLineHtml(isCombined, diffParser.LINE_TYPE.CONTEXT, '', '', '');
|
||||
fileHtml.right += that.generateSingleLineHtml(isCombined, newLine.type, newLine.newNumber, newContent, newPrefix);
|
||||
} else {
|
||||
console.error('How did it get here?');
|
||||
}
|
||||
}
|
||||
|
||||
return fileHtml;
|
||||
};
|
||||
|
||||
SideBySidePrinter.prototype.generateSingleLineHtml = function(isCombined, type, number, content, possiblePrefix) {
|
||||
var lineWithoutPrefix = content;
|
||||
var prefix = possiblePrefix;
|
||||
var lineClass = 'd2h-code-side-linenumber';
|
||||
var contentClass = 'd2h-code-side-line';
|
||||
|
||||
if (!number && !content) {
|
||||
lineClass += ' d2h-code-side-emptyplaceholder';
|
||||
contentClass += ' d2h-code-side-emptyplaceholder';
|
||||
type += ' d2h-emptyplaceholder';
|
||||
prefix = ' ';
|
||||
lineWithoutPrefix = ' ';
|
||||
} else if (!prefix) {
|
||||
var lineWithPrefix = printerUtils.separatePrefix(isCombined, content);
|
||||
prefix = lineWithPrefix.prefix;
|
||||
lineWithoutPrefix = lineWithPrefix.line;
|
||||
}
|
||||
|
||||
if (prefix === ' ') {
|
||||
prefix = ' ';
|
||||
}
|
||||
|
||||
return hoganUtils.render(genericTemplatesPath, 'line',
|
||||
{
|
||||
type: type,
|
||||
lineClass: lineClass,
|
||||
contentClass: contentClass,
|
||||
prefix: prefix,
|
||||
content: lineWithoutPrefix,
|
||||
lineNumber: number
|
||||
});
|
||||
};
|
||||
|
||||
SideBySidePrinter.prototype.generateEmptyDiff = function() {
|
||||
var fileHtml = {};
|
||||
fileHtml.right = '';
|
||||
|
||||
fileHtml.left = hoganUtils.render(genericTemplatesPath, 'empty-diff', {
|
||||
contentClass: 'd2h-code-side-line',
|
||||
diffParser: diffParser
|
||||
});
|
||||
|
||||
return fileHtml;
|
||||
};
|
||||
|
||||
module.exports.SideBySidePrinter = SideBySidePrinter;
|
||||
})();
|
||||
313
src/side-by-side-renderer.ts
Normal file
|
|
@ -0,0 +1,313 @@
|
|||
import HoganJsUtils from "./hoganjs-utils";
|
||||
import * as Rematch from "./rematch";
|
||||
import * as renderUtils from "./render-utils";
|
||||
import {
|
||||
DiffLine,
|
||||
LineType,
|
||||
DiffFile,
|
||||
DiffBlock,
|
||||
DiffLineContext,
|
||||
DiffLineDeleted,
|
||||
DiffLineInserted,
|
||||
DiffLineContent
|
||||
} from "./types";
|
||||
|
||||
export interface SideBySideRendererConfig extends renderUtils.RenderConfig {
|
||||
renderNothingWhenEmpty?: boolean;
|
||||
matchingMaxComparisons?: number;
|
||||
maxLineSizeInBlockForComparison?: number;
|
||||
}
|
||||
|
||||
export const defaultSideBySideRendererConfig = {
|
||||
...renderUtils.defaultRenderConfig,
|
||||
renderNothingWhenEmpty: false,
|
||||
matchingMaxComparisons: 2500,
|
||||
maxLineSizeInBlockForComparison: 200
|
||||
};
|
||||
|
||||
const genericTemplatesPath = "generic";
|
||||
const baseTemplatesPath = "side-by-side";
|
||||
const iconsBaseTemplatesPath = "icon";
|
||||
const tagsBaseTemplatesPath = "tag";
|
||||
|
||||
export default class SideBySideRenderer {
|
||||
private readonly hoganUtils: HoganJsUtils;
|
||||
private readonly config: typeof defaultSideBySideRendererConfig;
|
||||
|
||||
constructor(hoganUtils: HoganJsUtils, config: SideBySideRendererConfig = {}) {
|
||||
this.hoganUtils = hoganUtils;
|
||||
this.config = { ...defaultSideBySideRendererConfig, ...config };
|
||||
}
|
||||
|
||||
render(diffFiles: DiffFile[]): string {
|
||||
const diffsHtml = diffFiles
|
||||
.map(file => {
|
||||
let diffs;
|
||||
if (file.blocks.length) {
|
||||
diffs = this.generateFileHtml(file);
|
||||
} else {
|
||||
diffs = this.generateEmptyDiff();
|
||||
}
|
||||
return this.makeFileDiffHtml(file, diffs);
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
return this.hoganUtils.render(genericTemplatesPath, "wrapper", { content: diffsHtml });
|
||||
}
|
||||
|
||||
makeFileDiffHtml(file: DiffFile, diffs: FileHtml): string {
|
||||
if (this.config.renderNothingWhenEmpty && Array.isArray(file.blocks) && file.blocks.length === 0) return "";
|
||||
|
||||
const fileDiffTemplate = this.hoganUtils.template(baseTemplatesPath, "file-diff");
|
||||
const filePathTemplate = this.hoganUtils.template(genericTemplatesPath, "file-path");
|
||||
const fileIconTemplate = this.hoganUtils.template(iconsBaseTemplatesPath, "file");
|
||||
const fileTagTemplate = this.hoganUtils.template(tagsBaseTemplatesPath, renderUtils.getFileIcon(file));
|
||||
|
||||
return fileDiffTemplate.render({
|
||||
file: file,
|
||||
fileHtmlId: renderUtils.getHtmlId(file),
|
||||
diffs: diffs,
|
||||
filePath: filePathTemplate.render(
|
||||
{
|
||||
fileDiffName: renderUtils.filenameDiff(file)
|
||||
},
|
||||
{
|
||||
fileIcon: fileIconTemplate,
|
||||
fileTag: fileTagTemplate
|
||||
}
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
generateEmptyDiff(): FileHtml {
|
||||
return {
|
||||
right: "",
|
||||
left: this.hoganUtils.render(genericTemplatesPath, "empty-diff", {
|
||||
contentClass: "d2h-code-side-line",
|
||||
CSSLineClass: renderUtils.CSSLineClass
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
generateFileHtml(file: DiffFile): FileHtml {
|
||||
const matcher = Rematch.newMatcherFn(
|
||||
Rematch.newDistanceFn((e: DiffLine) => renderUtils.deconstructLine(e.content, file.isCombined).content)
|
||||
);
|
||||
|
||||
return file.blocks
|
||||
.map(block => {
|
||||
const fileHtml = {
|
||||
left: this.makeHeaderHtml(block.header),
|
||||
right: this.makeHeaderHtml("")
|
||||
};
|
||||
|
||||
this.applyLineGroupping(block).forEach(([contextLines, oldLines, newLines]) => {
|
||||
if (oldLines.length && newLines.length && !contextLines.length) {
|
||||
this.applyRematchMatching(oldLines, newLines, matcher).map(([oldLines, newLines]) => {
|
||||
const { left, right } = this.processChangedLines(file.isCombined, oldLines, newLines);
|
||||
fileHtml.left += left;
|
||||
fileHtml.right += right;
|
||||
});
|
||||
} else if (contextLines.length) {
|
||||
contextLines.forEach(line => {
|
||||
const { prefix, content } = renderUtils.deconstructLine(line.content, file.isCombined);
|
||||
const { left, right } = this.generateLineHtml(
|
||||
{
|
||||
type: renderUtils.CSSLineClass.CONTEXT,
|
||||
prefix: prefix,
|
||||
content: content,
|
||||
number: line.oldNumber
|
||||
},
|
||||
{
|
||||
type: renderUtils.CSSLineClass.CONTEXT,
|
||||
prefix: prefix,
|
||||
content: content,
|
||||
number: line.newNumber
|
||||
}
|
||||
);
|
||||
fileHtml.left += left;
|
||||
fileHtml.right += right;
|
||||
});
|
||||
} else if (oldLines.length || newLines.length) {
|
||||
const { left, right } = this.processChangedLines(file.isCombined, oldLines, newLines);
|
||||
fileHtml.left += left;
|
||||
fileHtml.right += right;
|
||||
} else {
|
||||
console.error("Unknown state reached while processing groups of lines", contextLines, oldLines, newLines);
|
||||
}
|
||||
});
|
||||
|
||||
return fileHtml;
|
||||
})
|
||||
.reduce(
|
||||
(accomulated, html) => {
|
||||
return { left: accomulated.left + html.left, right: accomulated.right + html.right };
|
||||
},
|
||||
{ left: "", right: "" }
|
||||
);
|
||||
}
|
||||
|
||||
applyLineGroupping(block: DiffBlock): DiffLineGroups {
|
||||
const blockLinesGroups: DiffLineGroups = [];
|
||||
|
||||
let oldLines: (DiffLineDeleted & DiffLineContent)[] = [];
|
||||
let newLines: (DiffLineInserted & DiffLineContent)[] = [];
|
||||
|
||||
for (let i = 0; i < block.lines.length; i++) {
|
||||
const diffLine = block.lines[i];
|
||||
|
||||
if (
|
||||
(diffLine.type !== LineType.INSERT && newLines.length) ||
|
||||
(diffLine.type === LineType.CONTEXT && oldLines.length > 0)
|
||||
) {
|
||||
blockLinesGroups.push([[], oldLines, newLines]);
|
||||
oldLines = [];
|
||||
newLines = [];
|
||||
}
|
||||
|
||||
if (diffLine.type === LineType.CONTEXT) {
|
||||
blockLinesGroups.push([[diffLine], [], []]);
|
||||
} else if (diffLine.type === LineType.INSERT && oldLines.length === 0) {
|
||||
blockLinesGroups.push([[], [], [diffLine]]);
|
||||
} else if (diffLine.type === LineType.INSERT && oldLines.length > 0) {
|
||||
newLines.push(diffLine);
|
||||
} else if (diffLine.type === LineType.DELETE) {
|
||||
oldLines.push(diffLine);
|
||||
}
|
||||
}
|
||||
|
||||
if (oldLines.length || newLines.length) {
|
||||
blockLinesGroups.push([[], oldLines, newLines]);
|
||||
oldLines = [];
|
||||
newLines = [];
|
||||
}
|
||||
|
||||
return blockLinesGroups;
|
||||
}
|
||||
|
||||
applyRematchMatching(
|
||||
oldLines: DiffLine[],
|
||||
newLines: DiffLine[],
|
||||
matcher: Rematch.MatcherFn<DiffLine>
|
||||
): DiffLine[][][] {
|
||||
const comparisons = oldLines.length * newLines.length;
|
||||
const maxLineSizeInBlock = Math.max.apply(
|
||||
null,
|
||||
[0].concat(oldLines.concat(newLines).map(elem => elem.content.length))
|
||||
);
|
||||
const doMatching =
|
||||
comparisons < this.config.matchingMaxComparisons &&
|
||||
maxLineSizeInBlock < this.config.maxLineSizeInBlockForComparison &&
|
||||
(this.config.matching === "lines" || this.config.matching === "words");
|
||||
|
||||
const matches = doMatching ? matcher(oldLines, newLines) : [[oldLines, newLines]];
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
makeHeaderHtml(blockHeader: string): string {
|
||||
return this.hoganUtils.render(genericTemplatesPath, "block-header", {
|
||||
CSSLineClass: renderUtils.CSSLineClass,
|
||||
blockHeader: blockHeader,
|
||||
lineClass: "d2h-code-side-linenumber",
|
||||
contentClass: "d2h-code-side-line"
|
||||
});
|
||||
}
|
||||
|
||||
processChangedLines(isCombined: boolean, oldLines: DiffLine[], newLines: DiffLine[]): FileHtml {
|
||||
const fileHtml = {
|
||||
right: "",
|
||||
left: ""
|
||||
};
|
||||
|
||||
const maxLinesNumber = Math.max(oldLines.length, newLines.length);
|
||||
for (let i = 0; i < maxLinesNumber; i++) {
|
||||
const oldLine = oldLines[i];
|
||||
const newLine = newLines[i];
|
||||
|
||||
const diff =
|
||||
oldLine !== undefined && newLine !== undefined
|
||||
? renderUtils.diffHighlight(oldLine.content, newLine.content, isCombined, this.config)
|
||||
: undefined;
|
||||
|
||||
const preparedOldLine =
|
||||
oldLine !== undefined && oldLine.oldNumber !== undefined
|
||||
? {
|
||||
...(diff !== undefined
|
||||
? {
|
||||
prefix: diff.oldLine.prefix,
|
||||
content: diff.oldLine.content,
|
||||
type: renderUtils.CSSLineClass.DELETE_CHANGES
|
||||
}
|
||||
: {
|
||||
...renderUtils.deconstructLine(oldLine.content, isCombined),
|
||||
type: renderUtils.toCSSClass(oldLine.type)
|
||||
}),
|
||||
number: oldLine.oldNumber
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const preparedNewLine =
|
||||
newLine !== undefined && newLine.newNumber !== undefined
|
||||
? {
|
||||
...(diff !== undefined
|
||||
? {
|
||||
prefix: diff.newLine.prefix,
|
||||
content: diff.newLine.content,
|
||||
type: renderUtils.CSSLineClass.INSERT_CHANGES
|
||||
}
|
||||
: {
|
||||
...renderUtils.deconstructLine(newLine.content, isCombined),
|
||||
type: renderUtils.toCSSClass(newLine.type)
|
||||
}),
|
||||
number: newLine.newNumber
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const { left, right } = this.generateLineHtml(preparedOldLine, preparedNewLine);
|
||||
fileHtml.left += left;
|
||||
fileHtml.right += right;
|
||||
}
|
||||
|
||||
return fileHtml;
|
||||
}
|
||||
|
||||
generateLineHtml(oldLine?: DiffPreparedLine, newLine?: DiffPreparedLine): FileHtml {
|
||||
return {
|
||||
left: this.generateSingleHtml(oldLine),
|
||||
right: this.generateSingleHtml(newLine)
|
||||
};
|
||||
}
|
||||
|
||||
generateSingleHtml(line?: DiffPreparedLine): string {
|
||||
const lineClass = "d2h-code-side-linenumber";
|
||||
const contentClass = "d2h-code-side-line";
|
||||
|
||||
return this.hoganUtils.render(genericTemplatesPath, "line", {
|
||||
type: line?.type || `${renderUtils.CSSLineClass.CONTEXT} d2h-emptyplaceholder`,
|
||||
lineClass: line !== undefined ? lineClass : `${lineClass} d2h-code-side-emptyplaceholder`,
|
||||
contentClass: line !== undefined ? contentClass : `${contentClass} d2h-code-side-emptyplaceholder`,
|
||||
prefix: line?.prefix === " " ? " " : line?.prefix || " ",
|
||||
content: line?.content || " ",
|
||||
lineNumber: line?.number
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
type DiffLineGroups = [
|
||||
(DiffLineContext & DiffLineContent)[],
|
||||
(DiffLineDeleted & DiffLineContent)[],
|
||||
(DiffLineInserted & DiffLineContent)[]
|
||||
][];
|
||||
|
||||
type DiffPreparedLine = {
|
||||
type: renderUtils.CSSLineClass;
|
||||
prefix: string;
|
||||
content: string;
|
||||
number: number;
|
||||
};
|
||||
|
||||
type FileHtml = {
|
||||
left: string;
|
||||
right: string;
|
||||
};
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
(function() {
|
||||
if (!!!global.browserTemplates) global.browserTemplates = {};
|
||||
var Hogan = require("hogan.js");global.browserTemplates["file-summary-line"] = new Hogan.Template({code: function (c,p,i) { var t=this;t.b(i=i||"");t.b("<li class=\"d2h-file-list-line\">");t.b("\n" + i);t.b(" <span class=\"d2h-file-name-wrapper\">");t.b("\n" + i);t.b(t.rp("<fileIcon0",c,p," "));t.b(" <a href=\"#");t.b(t.v(t.f("fileHtmlId",c,p,0)));t.b("\" class=\"d2h-file-name\">");t.b(t.v(t.f("fileName",c,p,0)));t.b("</a>");t.b("\n" + i);t.b(" <span class=\"d2h-file-stats\">");t.b("\n" + i);t.b(" <span class=\"d2h-lines-added\">");t.b(t.v(t.f("addedLines",c,p,0)));t.b("</span>");t.b("\n" + i);t.b(" <span class=\"d2h-lines-deleted\">");t.b(t.v(t.f("deletedLines",c,p,0)));t.b("</span>");t.b("\n" + i);t.b(" </span>");t.b("\n" + i);t.b(" </span>");t.b("\n" + i);t.b("</li>");return t.fl(); },partials: {"<fileIcon0":{name:"fileIcon", partials: {}, subs: { }}}, subs: { }});
|
||||
global.browserTemplates["file-summary-wrapper"] = new Hogan.Template({code: function (c,p,i) { var t=this;t.b(i=i||"");t.b("<div class=\"d2h-file-list-wrapper\">");t.b("\n" + i);t.b(" <div class=\"d2h-file-list-header\">");t.b("\n" + i);t.b(" <span class=\"d2h-file-list-title\">Files changed (");t.b(t.v(t.f("filesNumber",c,p,0)));t.b(")</span>");t.b("\n" + i);t.b(" <a class=\"d2h-file-switch d2h-hide\">hide</a>");t.b("\n" + i);t.b(" <a class=\"d2h-file-switch d2h-show\">show</a>");t.b("\n" + i);t.b(" </div>");t.b("\n" + i);t.b(" <ol class=\"d2h-file-list\">");t.b("\n" + i);t.b(" ");t.b(t.t(t.f("files",c,p,0)));t.b("\n" + i);t.b(" </ol>");t.b("\n" + i);t.b("</div>");return t.fl(); },partials: {}, subs: { }});
|
||||
global.browserTemplates["generic-column-line-number"] = new Hogan.Template({code: function (c,p,i) { var t=this;t.b(i=i||"");t.b("<tr>");t.b("\n" + i);t.b(" <td class=\"");t.b(t.v(t.f("lineClass",c,p,0)));t.b(" ");t.b(t.v(t.d("diffParser.LINE_TYPE.INFO",c,p,0)));t.b("\"></td>");t.b("\n" + i);t.b(" <td class=\"");t.b(t.v(t.d("diffParser.LINE_TYPE.INFO",c,p,0)));t.b("\">");t.b("\n" + i);t.b(" <div class=\"");t.b(t.v(t.f("contentClass",c,p,0)));t.b(" ");t.b(t.v(t.d("diffParser.LINE_TYPE.INFO",c,p,0)));t.b("\">");t.b(t.t(t.f("blockHeader",c,p,0)));t.b("</div>");t.b("\n" + i);t.b(" </td>");t.b("\n" + i);t.b("</tr>");return t.fl(); },partials: {}, subs: { }});
|
||||
global.browserTemplates["generic-empty-diff"] = new Hogan.Template({code: function (c,p,i) { var t=this;t.b(i=i||"");t.b("<tr>");t.b("\n" + i);t.b(" <td class=\"");t.b(t.v(t.d("diffParser.LINE_TYPE.INFO",c,p,0)));t.b("\">");t.b("\n" + i);t.b(" <div class=\"");t.b(t.v(t.f("contentClass",c,p,0)));t.b(" ");t.b(t.v(t.d("diffParser.LINE_TYPE.INFO",c,p,0)));t.b("\">");t.b("\n" + i);t.b(" File without changes");t.b("\n" + i);t.b(" </div>");t.b("\n" + i);t.b(" </td>");t.b("\n" + i);t.b("</tr>");return t.fl(); },partials: {}, subs: { }});
|
||||
global.browserTemplates["generic-file-path"] = new Hogan.Template({code: function (c,p,i) { var t=this;t.b(i=i||"");t.b("<span class=\"d2h-file-name-wrapper\">");t.b("\n" + i);t.b(t.rp("<fileIcon0",c,p," "));t.b(" <span class=\"d2h-file-name\">");t.b(t.v(t.f("fileDiffName",c,p,0)));t.b("</span>");t.b("\n" + i);t.b(t.rp("<fileTag1",c,p," "));t.b("</span>");return t.fl(); },partials: {"<fileIcon0":{name:"fileIcon", partials: {}, subs: { }},"<fileTag1":{name:"fileTag", partials: {}, subs: { }}}, subs: { }});
|
||||
global.browserTemplates["generic-line"] = new Hogan.Template({code: function (c,p,i) { var t=this;t.b(i=i||"");t.b("<tr>");t.b("\n" + i);t.b(" <td class=\"");t.b(t.v(t.f("lineClass",c,p,0)));t.b(" ");t.b(t.v(t.f("type",c,p,0)));t.b("\">");t.b("\n" + i);t.b(" ");t.b(t.t(t.f("lineNumber",c,p,0)));t.b("\n" + i);t.b(" </td>");t.b("\n" + i);t.b(" <td class=\"");t.b(t.v(t.f("type",c,p,0)));t.b("\">");t.b("\n" + i);t.b(" <div class=\"");t.b(t.v(t.f("contentClass",c,p,0)));t.b(" ");t.b(t.v(t.f("type",c,p,0)));t.b("\">");t.b("\n" + i);if(t.s(t.f("prefix",c,p,1),c,p,0,171,247,"{{ }}")){t.rs(c,p,function(c,p,t){t.b(" <span class=\"d2h-code-line-prefix\">");t.b(t.t(t.f("prefix",c,p,0)));t.b("</span>");t.b("\n" + i);});c.pop();}if(t.s(t.f("content",c,p,1),c,p,0,279,353,"{{ }}")){t.rs(c,p,function(c,p,t){t.b(" <span class=\"d2h-code-line-ctn\">");t.b(t.t(t.f("content",c,p,0)));t.b("</span>");t.b("\n" + i);});c.pop();}t.b(" </div>");t.b("\n" + i);t.b(" </td>");t.b("\n" + i);t.b("</tr>");return t.fl(); },partials: {}, subs: { }});
|
||||
global.browserTemplates["generic-wrapper"] = new Hogan.Template({code: function (c,p,i) { var t=this;t.b(i=i||"");t.b("<div class=\"d2h-wrapper\">");t.b("\n" + i);t.b(" ");t.b(t.t(t.f("content",c,p,0)));t.b("\n" + i);t.b("</div>");return t.fl(); },partials: {}, subs: { }});
|
||||
global.browserTemplates["icon-file-added"] = new Hogan.Template({code: function (c,p,i) { var t=this;t.b(i=i||"");t.b("<svg aria-hidden=\"true\" class=\"d2h-icon d2h-added\" height=\"16\" title=\"added\" version=\"1.1\" viewBox=\"0 0 14 16\"");t.b("\n" + i);t.b(" width=\"14\">");t.b("\n" + i);t.b(" <path d=\"M13 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V2c0-0.55-0.45-1-1-1z m0 13H1V2h12v12zM6 9H3V7h3V4h2v3h3v2H8v3H6V9z\"></path>");t.b("\n" + i);t.b("</svg>");return t.fl(); },partials: {}, subs: { }});
|
||||
global.browserTemplates["icon-file-changed"] = new Hogan.Template({code: function (c,p,i) { var t=this;t.b(i=i||"");t.b("<svg aria-hidden=\"true\" class=\"d2h-icon d2h-changed\" height=\"16\" title=\"modified\" version=\"1.1\"");t.b("\n" + i);t.b(" viewBox=\"0 0 14 16\" width=\"14\">");t.b("\n" + i);t.b(" <path d=\"M13 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V2c0-0.55-0.45-1-1-1z m0 13H1V2h12v12zM4 8c0-1.66 1.34-3 3-3s3 1.34 3 3-1.34 3-3 3-3-1.34-3-3z\"></path>");t.b("\n" + i);t.b("</svg>");return t.fl(); },partials: {}, subs: { }});
|
||||
global.browserTemplates["icon-file-deleted"] = new Hogan.Template({code: function (c,p,i) { var t=this;t.b(i=i||"");t.b("<svg aria-hidden=\"true\" class=\"d2h-icon d2h-deleted\" height=\"16\" title=\"removed\" version=\"1.1\"");t.b("\n" + i);t.b(" viewBox=\"0 0 14 16\" width=\"14\">");t.b("\n" + i);t.b(" <path d=\"M13 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V2c0-0.55-0.45-1-1-1z m0 13H1V2h12v12zM11 9H3V7h8v2z\"></path>");t.b("\n" + i);t.b("</svg>");return t.fl(); },partials: {}, subs: { }});
|
||||
global.browserTemplates["icon-file-renamed"] = new Hogan.Template({code: function (c,p,i) { var t=this;t.b(i=i||"");t.b("<svg aria-hidden=\"true\" class=\"d2h-icon d2h-moved\" height=\"16\" title=\"renamed\" version=\"1.1\"");t.b("\n" + i);t.b(" viewBox=\"0 0 14 16\" width=\"14\">");t.b("\n" + i);t.b(" <path d=\"M6 9H3V7h3V4l5 4-5 4V9z m8-7v12c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h12c0.55 0 1 0.45 1 1z m-1 0H1v12h12V2z\"></path>");t.b("\n" + i);t.b("</svg>");return t.fl(); },partials: {}, subs: { }});
|
||||
global.browserTemplates["icon-file"] = new Hogan.Template({code: function (c,p,i) { var t=this;t.b(i=i||"");t.b("<svg aria-hidden=\"true\" class=\"d2h-icon\" height=\"16\" version=\"1.1\" viewBox=\"0 0 12 16\" width=\"12\">");t.b("\n" + i);t.b(" <path d=\"M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z\"></path>");t.b("\n" + i);t.b("</svg>");return t.fl(); },partials: {}, subs: { }});
|
||||
global.browserTemplates["line-by-line-file-diff"] = new Hogan.Template({code: function (c,p,i) { var t=this;t.b(i=i||"");t.b("<div id=\"");t.b(t.v(t.f("fileHtmlId",c,p,0)));t.b("\" class=\"d2h-file-wrapper\" data-lang=\"");t.b(t.v(t.d("file.language",c,p,0)));t.b("\">");t.b("\n" + i);t.b(" <div class=\"d2h-file-header\">");t.b("\n" + i);t.b(" ");t.b(t.t(t.f("filePath",c,p,0)));t.b("\n" + i);t.b(" </div>");t.b("\n" + i);t.b(" <div class=\"d2h-file-diff\">");t.b("\n" + i);t.b(" <div class=\"d2h-code-wrapper\">");t.b("\n" + i);t.b(" <table class=\"d2h-diff-table\">");t.b("\n" + i);t.b(" <tbody class=\"d2h-diff-tbody\">");t.b("\n" + i);t.b(" ");t.b(t.t(t.f("diffs",c,p,0)));t.b("\n" + i);t.b(" </tbody>");t.b("\n" + i);t.b(" </table>");t.b("\n" + i);t.b(" </div>");t.b("\n" + i);t.b(" </div>");t.b("\n" + i);t.b("</div>");return t.fl(); },partials: {}, subs: { }});
|
||||
global.browserTemplates["line-by-line-numbers"] = new Hogan.Template({code: function (c,p,i) { var t=this;t.b(i=i||"");t.b("<div class=\"line-num1\">");t.b(t.v(t.f("oldNumber",c,p,0)));t.b("</div>");t.b("\n" + i);t.b("<div class=\"line-num2\">");t.b(t.v(t.f("newNumber",c,p,0)));t.b("</div>");return t.fl(); },partials: {}, subs: { }});
|
||||
global.browserTemplates["side-by-side-file-diff"] = new Hogan.Template({code: function (c,p,i) { var t=this;t.b(i=i||"");t.b("<div id=\"");t.b(t.v(t.f("fileHtmlId",c,p,0)));t.b("\" class=\"d2h-file-wrapper\" data-lang=\"");t.b(t.v(t.d("file.language",c,p,0)));t.b("\">");t.b("\n" + i);t.b(" <div class=\"d2h-file-header\">");t.b("\n" + i);t.b(" ");t.b(t.t(t.f("filePath",c,p,0)));t.b("\n" + i);t.b(" </div>");t.b("\n" + i);t.b(" <div class=\"d2h-files-diff\">");t.b("\n" + i);t.b(" <div class=\"d2h-file-side-diff\">");t.b("\n" + i);t.b(" <div class=\"d2h-code-wrapper\">");t.b("\n" + i);t.b(" <table class=\"d2h-diff-table\">");t.b("\n" + i);t.b(" <tbody class=\"d2h-diff-tbody\">");t.b("\n" + i);t.b(" ");t.b(t.t(t.d("diffs.left",c,p,0)));t.b("\n" + i);t.b(" </tbody>");t.b("\n" + i);t.b(" </table>");t.b("\n" + i);t.b(" </div>");t.b("\n" + i);t.b(" </div>");t.b("\n" + i);t.b(" <div class=\"d2h-file-side-diff\">");t.b("\n" + i);t.b(" <div class=\"d2h-code-wrapper\">");t.b("\n" + i);t.b(" <table class=\"d2h-diff-table\">");t.b("\n" + i);t.b(" <tbody class=\"d2h-diff-tbody\">");t.b("\n" + i);t.b(" ");t.b(t.t(t.d("diffs.right",c,p,0)));t.b("\n" + i);t.b(" </tbody>");t.b("\n" + i);t.b(" </table>");t.b("\n" + i);t.b(" </div>");t.b("\n" + i);t.b(" </div>");t.b("\n" + i);t.b(" </div>");t.b("\n" + i);t.b("</div>");return t.fl(); },partials: {}, subs: { }});
|
||||
global.browserTemplates["tag-file-added"] = new Hogan.Template({code: function (c,p,i) { var t=this;t.b(i=i||"");t.b("<span class=\"d2h-tag d2h-added d2h-added-tag\">ADDED</span>");return t.fl(); },partials: {}, subs: { }});
|
||||
global.browserTemplates["tag-file-changed"] = new Hogan.Template({code: function (c,p,i) { var t=this;t.b(i=i||"");t.b("<span class=\"d2h-tag d2h-changed d2h-changed-tag\">CHANGED</span>");return t.fl(); },partials: {}, subs: { }});
|
||||
global.browserTemplates["tag-file-deleted"] = new Hogan.Template({code: function (c,p,i) { var t=this;t.b(i=i||"");t.b("<span class=\"d2h-tag d2h-deleted d2h-deleted-tag\">DELETED</span>");return t.fl(); },partials: {}, subs: { }});
|
||||
global.browserTemplates["tag-file-renamed"] = new Hogan.Template({code: function (c,p,i) { var t=this;t.b(i=i||"");t.b("<span class=\"d2h-tag d2h-moved d2h-moved-tag\">RENAMED</span>");return t.fl(); },partials: {}, subs: { }});
|
||||
module.exports = global.browserTemplates;
|
||||
})();
|
||||
6
src/templates/generic-block-header.mustache
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<tr>
|
||||
<td class="{{lineClass}} {{CSSLineClass.INFO}}"></td>
|
||||
<td class="{{CSSLineClass.INFO}}">
|
||||
<div class="{{contentClass}} {{CSSLineClass.INFO}}">{{{blockHeader}}}</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
<tr>
|
||||
<td class="{{lineClass}} {{diffParser.LINE_TYPE.INFO}}"></td>
|
||||
<td class="{{diffParser.LINE_TYPE.INFO}}">
|
||||
<div class="{{contentClass}} {{diffParser.LINE_TYPE.INFO}}">{{{blockHeader}}}</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<tr>
|
||||
<td class="{{diffParser.LINE_TYPE.INFO}}">
|
||||
<div class="{{contentClass}} {{diffParser.LINE_TYPE.INFO}}">
|
||||
<td class="{{CSSLineClass.INFO}}">
|
||||
<div class="{{contentClass}} {{CSSLineClass.INFO}}">
|
||||
File without changes
|
||||
</div>
|
||||
</td>
|
||||
|
|
|
|||
86
src/types.ts
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
export type DiffLineParts = {
|
||||
prefix: string;
|
||||
content: string;
|
||||
};
|
||||
|
||||
export enum LineType {
|
||||
INSERT = "insert",
|
||||
DELETE = "delete",
|
||||
CONTEXT = "context"
|
||||
}
|
||||
|
||||
export interface DiffLineDeleted {
|
||||
type: LineType.DELETE;
|
||||
oldNumber: number;
|
||||
newNumber: undefined;
|
||||
}
|
||||
|
||||
export interface DiffLineInserted {
|
||||
type: LineType.INSERT;
|
||||
oldNumber: undefined;
|
||||
newNumber: number;
|
||||
}
|
||||
|
||||
export interface DiffLineContext {
|
||||
type: LineType.CONTEXT;
|
||||
oldNumber: number;
|
||||
newNumber: number;
|
||||
}
|
||||
|
||||
export type DiffLineContent = {
|
||||
content: string;
|
||||
};
|
||||
|
||||
export type DiffLine = (DiffLineDeleted | DiffLineInserted | DiffLineContext) & DiffLineContent;
|
||||
|
||||
export interface DiffBlock {
|
||||
oldStartLine: number;
|
||||
oldStartLine2?: number;
|
||||
newStartLine: number;
|
||||
header: string;
|
||||
lines: DiffLine[];
|
||||
}
|
||||
|
||||
export interface DiffFileName {
|
||||
oldName: string;
|
||||
newName: string;
|
||||
}
|
||||
|
||||
export interface DiffFile extends DiffFileName {
|
||||
addedLines: number;
|
||||
deletedLines: number;
|
||||
isCombined: boolean;
|
||||
isGitDiff: boolean;
|
||||
language: string;
|
||||
blocks: DiffBlock[];
|
||||
oldMode?: string | string[];
|
||||
newMode?: string;
|
||||
deletedFileMode?: string;
|
||||
newFileMode?: string;
|
||||
isDeleted?: boolean;
|
||||
isNew?: boolean;
|
||||
isCopy?: boolean;
|
||||
isRename?: boolean;
|
||||
isBinary?: boolean;
|
||||
unchangedPercentage?: number;
|
||||
changedPercentage?: number;
|
||||
checksumBefore?: string | string[];
|
||||
checksumAfter?: string;
|
||||
mode?: string;
|
||||
}
|
||||
|
||||
export enum OutputFormatType {
|
||||
LINE_BY_LINE = "line-by-line",
|
||||
SIDE_BY_SIDE = "side-by-side"
|
||||
}
|
||||
|
||||
export enum LineMatchingType {
|
||||
LINES = "lines",
|
||||
WORDS = "words",
|
||||
NONE = "none"
|
||||
}
|
||||
|
||||
export enum DiffStyleType {
|
||||
WORD = "word",
|
||||
CHAR = "char"
|
||||
}
|
||||
|
|
@ -1,219 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* Diff to HTML (diff2html-ui.js)
|
||||
* Author: rtfpessoa
|
||||
*
|
||||
* Depends on: [ jQuery ]
|
||||
* Optional dependencies on: [ highlight.js ]
|
||||
*
|
||||
*/
|
||||
|
||||
/*global $, hljs, Diff2Html*/
|
||||
|
||||
(function() {
|
||||
var highlightJS = require('./highlight.js-internals.js').HighlightJS;
|
||||
|
||||
var diffJson = null;
|
||||
var defaultTarget = 'body';
|
||||
var currentSelectionColumnId = -1;
|
||||
|
||||
function Diff2HtmlUI(config) {
|
||||
var cfg = config || {};
|
||||
|
||||
if (cfg.diff) {
|
||||
diffJson = Diff2Html.getJsonFromDiff(cfg.diff);
|
||||
} else if (cfg.json) {
|
||||
diffJson = cfg.json;
|
||||
}
|
||||
|
||||
this._initSelection();
|
||||
}
|
||||
|
||||
Diff2HtmlUI.prototype.draw = function(targetId, config) {
|
||||
var cfg = config || {};
|
||||
cfg.inputFormat = 'json';
|
||||
var $target = this._getTarget(targetId);
|
||||
$target.html(Diff2Html.getPrettyHtml(diffJson, cfg));
|
||||
|
||||
if (cfg.synchronisedScroll) {
|
||||
this.synchronisedScroll($target, cfg);
|
||||
}
|
||||
};
|
||||
|
||||
Diff2HtmlUI.prototype.synchronisedScroll = function(targetId) {
|
||||
var $target = this._getTarget(targetId);
|
||||
$target.find('.d2h-file-side-diff').scroll(function() {
|
||||
var $this = $(this);
|
||||
$this.closest('.d2h-file-wrapper').find('.d2h-file-side-diff')
|
||||
.scrollLeft($this.scrollLeft());
|
||||
});
|
||||
};
|
||||
|
||||
Diff2HtmlUI.prototype.fileListCloseable = function(targetId, startVisible) {
|
||||
var $target = this._getTarget(targetId);
|
||||
|
||||
var hashTag = this._getHashTag();
|
||||
|
||||
var $showBtn = $target.find('.d2h-show');
|
||||
var $hideBtn = $target.find('.d2h-hide');
|
||||
var $fileList = $target.find('.d2h-file-list');
|
||||
|
||||
if (hashTag === 'files-summary-show') show();
|
||||
else if (hashTag === 'files-summary-hide') hide();
|
||||
else if (startVisible) show();
|
||||
else hide();
|
||||
|
||||
$showBtn.click(show);
|
||||
$hideBtn.click(hide);
|
||||
|
||||
function show() {
|
||||
$showBtn.hide();
|
||||
$hideBtn.show();
|
||||
$fileList.show();
|
||||
}
|
||||
|
||||
function hide() {
|
||||
$hideBtn.hide();
|
||||
$showBtn.show();
|
||||
$fileList.hide();
|
||||
}
|
||||
};
|
||||
|
||||
Diff2HtmlUI.prototype.highlightCode = function(targetId) {
|
||||
var that = this;
|
||||
|
||||
var $target = that._getTarget(targetId);
|
||||
|
||||
// collect all the diff files and execute the highlight on their lines
|
||||
var $files = $target.find('.d2h-file-wrapper');
|
||||
$files.map(function(_i, file) {
|
||||
var oldLinesState;
|
||||
var newLinesState;
|
||||
var $file = $(file);
|
||||
var language = $file.data('lang');
|
||||
|
||||
// collect all the code lines and execute the highlight on them
|
||||
var $codeLines = $file.find('.d2h-code-line-ctn');
|
||||
$codeLines.map(function(_j, line) {
|
||||
var $line = $(line);
|
||||
var text = line.textContent;
|
||||
var lineParent = line.parentNode;
|
||||
|
||||
var lineState;
|
||||
if (lineParent.className.indexOf('d2h-del') !== -1) {
|
||||
lineState = oldLinesState;
|
||||
} else {
|
||||
lineState = newLinesState;
|
||||
}
|
||||
|
||||
var result = hljs.getLanguage(language) ? hljs.highlight(language, text, true, lineState) : hljs.highlightAuto(text);
|
||||
|
||||
if (lineParent.className.indexOf('d2h-del') !== -1) {
|
||||
oldLinesState = result.top;
|
||||
} else if (lineParent.className.indexOf('d2h-ins') !== -1) {
|
||||
newLinesState = result.top;
|
||||
} else {
|
||||
oldLinesState = result.top;
|
||||
newLinesState = result.top;
|
||||
}
|
||||
|
||||
var originalStream = highlightJS.nodeStream(line);
|
||||
if (originalStream.length) {
|
||||
var resultNode = document.createElementNS('http://www.w3.org/1999/xhtml', 'div');
|
||||
resultNode.innerHTML = result.value;
|
||||
result.value = highlightJS.mergeStreams(originalStream, highlightJS.nodeStream(resultNode), text);
|
||||
}
|
||||
|
||||
$line.addClass('hljs');
|
||||
$line.addClass(result.language);
|
||||
$line.html(result.value);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Diff2HtmlUI.prototype._getTarget = function(targetId) {
|
||||
var $target;
|
||||
|
||||
if (typeof targetId === 'object' && targetId instanceof jQuery) {
|
||||
$target = targetId;
|
||||
} else if (typeof targetId === 'string') {
|
||||
$target = $(targetId);
|
||||
} else {
|
||||
console.error("Wrong target provided! Falling back to default value 'body'.");
|
||||
console.log('Please provide a jQuery object or a valid DOM query string.');
|
||||
$target = $(defaultTarget);
|
||||
}
|
||||
|
||||
return $target;
|
||||
};
|
||||
|
||||
Diff2HtmlUI.prototype._getHashTag = function() {
|
||||
var docUrl = document.URL;
|
||||
var hashTagIndex = docUrl.indexOf('#');
|
||||
|
||||
var hashTag = null;
|
||||
if (hashTagIndex !== -1) {
|
||||
hashTag = docUrl.substr(hashTagIndex + 1);
|
||||
}
|
||||
|
||||
return hashTag;
|
||||
};
|
||||
|
||||
Diff2HtmlUI.prototype._distinct = function(collection) {
|
||||
return collection.filter(function(v, i) {
|
||||
return collection.indexOf(v) === i;
|
||||
});
|
||||
};
|
||||
|
||||
Diff2HtmlUI.prototype._initSelection = function() {
|
||||
var body = $('body');
|
||||
var that = this;
|
||||
|
||||
body.on('mousedown', '.d2h-diff-table', function(event) {
|
||||
var target = $(event.target);
|
||||
var table = target.closest('.d2h-diff-table');
|
||||
|
||||
if (target.closest('.d2h-code-line,.d2h-code-side-line').length) {
|
||||
table.removeClass('selecting-left');
|
||||
table.addClass('selecting-right');
|
||||
currentSelectionColumnId = 1;
|
||||
} else if (target.closest('.d2h-code-linenumber,.d2h-code-side-linenumber').length) {
|
||||
table.removeClass('selecting-right');
|
||||
table.addClass('selecting-left');
|
||||
currentSelectionColumnId = 0;
|
||||
}
|
||||
});
|
||||
|
||||
body.on('copy', '.d2h-diff-table', function(event) {
|
||||
var clipboardData = event.originalEvent.clipboardData;
|
||||
var text = that._getSelectedText();
|
||||
clipboardData.setData('text', text);
|
||||
event.preventDefault();
|
||||
});
|
||||
};
|
||||
|
||||
Diff2HtmlUI.prototype._getSelectedText = function() {
|
||||
var sel = window.getSelection();
|
||||
var range = sel.getRangeAt(0);
|
||||
var doc = range.cloneContents();
|
||||
var nodes = doc.querySelectorAll('tr');
|
||||
var text = '';
|
||||
var idx = currentSelectionColumnId;
|
||||
|
||||
if (nodes.length === 0) {
|
||||
text = doc.textContent;
|
||||
} else {
|
||||
[].forEach.call(nodes, function(tr, i) {
|
||||
var td = tr.cells[tr.cells.length === 1 ? 0 : idx];
|
||||
text += (i ? '\n' : '') + td.textContent.replace(/(?:\r\n|\r|\n)/g, '');
|
||||
});
|
||||
}
|
||||
|
||||
return text;
|
||||
};
|
||||
|
||||
module.exports.Diff2HtmlUI = Diff2HtmlUI;
|
||||
|
||||
// Expose diff2html in the browser
|
||||
global.Diff2HtmlUI = Diff2HtmlUI;
|
||||
})();
|
||||
223
src/ui/js/diff2html-ui.ts
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
import HighlightJS from "highlight.js";
|
||||
import * as HighlightJSInternals from "./highlight.js-internals";
|
||||
import { html, Diff2HtmlConfig, defaultDiff2HtmlConfig } from "../../diff2html";
|
||||
import { DiffFile } from "../../types";
|
||||
|
||||
export interface Diff2HtmlUIConfig extends Diff2HtmlConfig {
|
||||
synchronisedScroll?: boolean;
|
||||
highlight?: boolean;
|
||||
fileListToggle?: boolean;
|
||||
fileListStartVisible?: boolean;
|
||||
smartSelection?: boolean;
|
||||
}
|
||||
|
||||
export const defaultDiff2HtmlUIConfig = {
|
||||
...defaultDiff2HtmlConfig,
|
||||
synchronisedScroll: true,
|
||||
highlight: true,
|
||||
fileListToggle: true,
|
||||
fileListStartVisible: false,
|
||||
smartSelection: true
|
||||
};
|
||||
|
||||
export class Diff2HtmlUI {
|
||||
readonly config: typeof defaultDiff2HtmlUIConfig;
|
||||
readonly diffHtml: string;
|
||||
targetElement: HTMLElement;
|
||||
currentSelectionColumnId = -1;
|
||||
|
||||
constructor(diffInput: string | DiffFile[], target: HTMLElement, config: Diff2HtmlUIConfig = {}) {
|
||||
this.config = { ...defaultDiff2HtmlUIConfig, ...config };
|
||||
this.diffHtml = html(diffInput, this.config);
|
||||
this.targetElement = target;
|
||||
}
|
||||
|
||||
draw(): void {
|
||||
this.targetElement.innerHTML = this.diffHtml;
|
||||
if (this.config.smartSelection) this.initSelection();
|
||||
if (this.config.synchronisedScroll) this.synchronisedScroll();
|
||||
if (this.config.highlight) this.highlightCode();
|
||||
if (this.config.fileListToggle) this.fileListToggle(this.config.fileListStartVisible);
|
||||
}
|
||||
|
||||
synchronisedScroll(): void {
|
||||
this.targetElement.querySelectorAll(".d2h-file-wrapper").forEach(wrapper => {
|
||||
const [left, right] = [].slice.call(wrapper.querySelectorAll(".d2h-file-side-diff")) as HTMLElement[];
|
||||
if (left === undefined || right === undefined) return;
|
||||
const onScroll = (event: Event): void => {
|
||||
if (event === null || event.target === null) return;
|
||||
if (event.target === left) {
|
||||
right.scrollTop = left.scrollTop;
|
||||
right.scrollLeft = left.scrollLeft;
|
||||
} else {
|
||||
left.scrollTop = right.scrollTop;
|
||||
left.scrollLeft = right.scrollLeft;
|
||||
}
|
||||
};
|
||||
left.addEventListener("scroll", onScroll);
|
||||
right.addEventListener("scroll", onScroll);
|
||||
});
|
||||
}
|
||||
|
||||
fileListToggle(startVisible: boolean): void {
|
||||
const hashTag = this.getHashTag();
|
||||
|
||||
const showBtn = this.targetElement.querySelector(".d2h-show") as HTMLElement;
|
||||
const hideBtn = this.targetElement.querySelector(".d2h-hide") as HTMLElement;
|
||||
const fileList = this.targetElement.querySelector(".d2h-file-list") as HTMLElement;
|
||||
|
||||
if (showBtn === null || hideBtn === null || fileList === null) return;
|
||||
|
||||
function show(): void {
|
||||
showBtn.style.display = "none";
|
||||
hideBtn.style.display = "inline";
|
||||
fileList.style.display = "block";
|
||||
}
|
||||
|
||||
function hide(): void {
|
||||
showBtn.style.display = "inline";
|
||||
hideBtn.style.display = "none";
|
||||
fileList.style.display = "none";
|
||||
}
|
||||
|
||||
showBtn.addEventListener("click", () => show());
|
||||
hideBtn.addEventListener("click", () => hide());
|
||||
|
||||
if (hashTag === "files-summary-show") show();
|
||||
else if (hashTag === "files-summary-hide") hide();
|
||||
else if (startVisible) show();
|
||||
else hide();
|
||||
}
|
||||
|
||||
highlightCode(): void {
|
||||
// Collect all the diff files and execute the highlight on their lines
|
||||
const files = this.targetElement.querySelectorAll(".d2h-file-wrapper");
|
||||
files.forEach(file => {
|
||||
let oldLinesState: HighlightJS.ICompiledMode;
|
||||
let newLinesState: HighlightJS.ICompiledMode;
|
||||
|
||||
// Collect all the code lines and execute the highlight on them
|
||||
const codeLines = file.querySelectorAll(".d2h-code-line-ctn");
|
||||
codeLines.forEach(line => {
|
||||
const text = line.textContent;
|
||||
const lineParent = line.parentNode as HTMLElement;
|
||||
|
||||
if (lineParent === null || text === null) return;
|
||||
|
||||
const lineState = lineParent.className.indexOf("d2h-del") !== -1 ? oldLinesState : newLinesState;
|
||||
|
||||
const language = file.getAttribute("data-lang");
|
||||
const result =
|
||||
language && HighlightJS.getLanguage(language)
|
||||
? HighlightJS.highlight(language, text, true, lineState)
|
||||
: HighlightJS.highlightAuto(text);
|
||||
|
||||
if (this.instanceOfIHighlightResult(result)) {
|
||||
if (lineParent.className.indexOf("d2h-del") !== -1) {
|
||||
oldLinesState = result.top;
|
||||
} else if (lineParent.className.indexOf("d2h-ins") !== -1) {
|
||||
newLinesState = result.top;
|
||||
} else {
|
||||
oldLinesState = result.top;
|
||||
newLinesState = result.top;
|
||||
}
|
||||
}
|
||||
|
||||
const originalStream = HighlightJSInternals.nodeStream(line);
|
||||
if (originalStream.length) {
|
||||
const resultNode = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
|
||||
resultNode.innerHTML = result.value;
|
||||
result.value = HighlightJSInternals.mergeStreams(
|
||||
originalStream,
|
||||
HighlightJSInternals.nodeStream(resultNode),
|
||||
text
|
||||
);
|
||||
}
|
||||
|
||||
line.classList.add("hljs");
|
||||
line.classList.add("result.language");
|
||||
line.innerHTML = result.value;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private instanceOfIHighlightResult(
|
||||
object: HighlightJS.IHighlightResult | HighlightJS.IAutoHighlightResult
|
||||
): object is HighlightJS.IHighlightResult {
|
||||
return "top" in object;
|
||||
}
|
||||
|
||||
private getHashTag(): string | null {
|
||||
const docUrl = document.URL;
|
||||
const hashTagIndex = docUrl.indexOf("#");
|
||||
|
||||
let hashTag = null;
|
||||
if (hashTagIndex !== -1) {
|
||||
hashTag = docUrl.substr(hashTagIndex + 1);
|
||||
}
|
||||
|
||||
return hashTag;
|
||||
}
|
||||
|
||||
private initSelection(): void {
|
||||
const body = document.getElementsByTagName("body")[0];
|
||||
const diffTable = body.getElementsByClassName("d2h-diff-table")[0];
|
||||
|
||||
diffTable.addEventListener("mousedown", event => {
|
||||
if (event === null || event.target === null) return;
|
||||
|
||||
const mouseEvent = event as MouseEvent;
|
||||
const target = mouseEvent.target as HTMLElement;
|
||||
const table = target.closest(".d2h-diff-table");
|
||||
|
||||
if (table !== null) {
|
||||
if (target.closest(".d2h-code-line,.d2h-code-side-line") !== null) {
|
||||
table.classList.remove("selecting-left");
|
||||
table.classList.add("selecting-right");
|
||||
this.currentSelectionColumnId = 1;
|
||||
} else if (target.closest(".d2h-code-linenumber,.d2h-code-side-linenumber") !== null) {
|
||||
table.classList.remove("selecting-right");
|
||||
table.classList.add("selecting-left");
|
||||
this.currentSelectionColumnId = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
diffTable.addEventListener("copy", event => {
|
||||
const clipboardEvent = event as ClipboardEvent;
|
||||
const clipboardData = clipboardEvent.clipboardData;
|
||||
const text = this.getSelectedText();
|
||||
|
||||
if (clipboardData === null || text === undefined) return;
|
||||
|
||||
clipboardData.setData("text", text);
|
||||
event.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
private getSelectedText(): string | undefined {
|
||||
const sel = window.getSelection();
|
||||
|
||||
if (sel === null) return;
|
||||
|
||||
const range = sel.getRangeAt(0);
|
||||
const doc = range.cloneContents();
|
||||
const nodes = doc.querySelectorAll("tr");
|
||||
const idx = this.currentSelectionColumnId;
|
||||
|
||||
let text = "";
|
||||
if (nodes.length === 0) {
|
||||
text = doc.textContent || "";
|
||||
} else {
|
||||
nodes.forEach((tr, i) => {
|
||||
const td = tr.cells[tr.cells.length === 1 ? 0 : idx];
|
||||
|
||||
if (td === undefined || td.textContent === null) return;
|
||||
|
||||
text += (i ? "\n" : "") + td.textContent.replace(/(?:\r\n|\r|\n)/g, "");
|
||||
});
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* highlight.js
|
||||
* Author: isagalaev
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
function HighlightJS() {
|
||||
}
|
||||
|
||||
/*
|
||||
* Copied from Highlight.js Private API
|
||||
* Will be removed when this part of the API is exposed
|
||||
*/
|
||||
|
||||
/* Utility vars */
|
||||
|
||||
var ArrayProto = [];
|
||||
|
||||
/* Utility functions */
|
||||
|
||||
function escape(value) {
|
||||
return value.replace(/&/gm, '&').replace(/</gm, '<').replace(/>/gm, '>');
|
||||
}
|
||||
|
||||
function tag(node) {
|
||||
return node.nodeName.toLowerCase();
|
||||
}
|
||||
|
||||
/* Stream merging */
|
||||
|
||||
HighlightJS.prototype.nodeStream = function(node) {
|
||||
var result = [];
|
||||
(function _nodeStream(node, offset) {
|
||||
for (var child = node.firstChild; child; child = child.nextSibling) {
|
||||
if (child.nodeType === 3) {
|
||||
offset += child.nodeValue.length;
|
||||
} else if (child.nodeType === 1) {
|
||||
result.push({
|
||||
event: 'start',
|
||||
offset: offset,
|
||||
node: child
|
||||
});
|
||||
offset = _nodeStream(child, offset);
|
||||
// Prevent void elements from having an end tag that would actually
|
||||
// double them in the output. There are more void elements in HTML
|
||||
// but we list only those realistically expected in code display.
|
||||
if (!tag(child).match(/br|hr|img|input/)) {
|
||||
result.push({
|
||||
event: 'stop',
|
||||
offset: offset,
|
||||
node: child
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return offset;
|
||||
})(node, 0);
|
||||
return result;
|
||||
};
|
||||
|
||||
HighlightJS.prototype.mergeStreams = function(original, highlighted, value) {
|
||||
var processed = 0;
|
||||
var result = '';
|
||||
var nodeStack = [];
|
||||
|
||||
function selectStream() {
|
||||
if (!original.length || !highlighted.length) {
|
||||
return original.length ? original : highlighted;
|
||||
}
|
||||
if (original[0].offset !== highlighted[0].offset) {
|
||||
return (original[0].offset < highlighted[0].offset) ? original : highlighted;
|
||||
}
|
||||
|
||||
/*
|
||||
To avoid starting the stream just before it should stop the order is
|
||||
ensured that original always starts first and closes last:
|
||||
if (event1 == 'start' && event2 == 'start')
|
||||
return original;
|
||||
if (event1 == 'start' && event2 == 'stop')
|
||||
return highlighted;
|
||||
if (event1 == 'stop' && event2 == 'start')
|
||||
return original;
|
||||
if (event1 == 'stop' && event2 == 'stop')
|
||||
return highlighted;
|
||||
... which is collapsed to:
|
||||
*/
|
||||
return highlighted[0].event === 'start' ? original : highlighted;
|
||||
}
|
||||
|
||||
function open(node) {
|
||||
function attr_str(a) {
|
||||
return ' ' + a.nodeName + '="' + escape(a.value) + '"';
|
||||
}
|
||||
|
||||
result += '<' + tag(node) + ArrayProto.map.call(node.attributes, attr_str).join('') + '>';
|
||||
}
|
||||
|
||||
function close(node) {
|
||||
result += '</' + tag(node) + '>';
|
||||
}
|
||||
|
||||
function render(event) {
|
||||
(event.event === 'start' ? open : close)(event.node);
|
||||
}
|
||||
|
||||
while (original.length || highlighted.length) {
|
||||
var stream = selectStream();
|
||||
result += escape(value.substring(processed, stream[0].offset));
|
||||
processed = stream[0].offset;
|
||||
if (stream === original) {
|
||||
/*
|
||||
On any opening or closing tag of the original markup we first close
|
||||
the entire highlighted node stack, then render the original tag along
|
||||
with all the following original tags at the same offset and then
|
||||
reopen all the tags on the highlighted stack.
|
||||
*/
|
||||
nodeStack.reverse().forEach(close);
|
||||
do {
|
||||
render(stream.splice(0, 1)[0]);
|
||||
stream = selectStream();
|
||||
} while (stream === original && stream.length && stream[0].offset === processed);
|
||||
nodeStack.reverse().forEach(open);
|
||||
} else {
|
||||
if (stream[0].event === 'start') {
|
||||
nodeStack.push(stream[0].node);
|
||||
} else {
|
||||
nodeStack.pop();
|
||||
}
|
||||
render(stream.splice(0, 1)[0]);
|
||||
}
|
||||
}
|
||||
return result + escape(value.substr(processed));
|
||||
};
|
||||
|
||||
/* **** Highlight.js Private API **** */
|
||||
|
||||
module.exports.HighlightJS = new HighlightJS();
|
||||
})();
|
||||
134
src/ui/js/highlight.js-internals.ts
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* Copied from Highlight.js Private API
|
||||
* Will be removed when this part of the API is exposed
|
||||
*/
|
||||
|
||||
/* Utility functions */
|
||||
|
||||
function escape(value: string): string {
|
||||
return value
|
||||
.replace(/&/gm, "&")
|
||||
.replace(/</gm, "<")
|
||||
.replace(/>/gm, ">");
|
||||
}
|
||||
|
||||
function tag(node: Node): string {
|
||||
return node.nodeName.toLowerCase();
|
||||
}
|
||||
|
||||
/* Stream merging */
|
||||
|
||||
type NodeEvent = {
|
||||
event: "start" | "stop";
|
||||
offset: number;
|
||||
node: Node;
|
||||
};
|
||||
|
||||
export function nodeStream(node: Node): NodeEvent[] {
|
||||
const result: NodeEvent[] = [];
|
||||
|
||||
const nodeStream = (node: Node, offset: number): number => {
|
||||
for (let child = node.firstChild; child; child = child.nextSibling) {
|
||||
if (child.nodeType === 3 && child.nodeValue !== null) {
|
||||
offset += child.nodeValue.length;
|
||||
} else if (child.nodeType === 1) {
|
||||
result.push({
|
||||
event: "start",
|
||||
offset: offset,
|
||||
node: child
|
||||
});
|
||||
offset = nodeStream(child, offset);
|
||||
// Prevent void elements from having an end tag that would actually
|
||||
// double them in the output. There are more void elements in HTML
|
||||
// but we list only those realistically expected in code display.
|
||||
if (!tag(child).match(/br|hr|img|input/)) {
|
||||
result.push({
|
||||
event: "stop",
|
||||
offset: offset,
|
||||
node: child
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return offset;
|
||||
};
|
||||
|
||||
nodeStream(node, 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function mergeStreams(original: NodeEvent[], highlighted: NodeEvent[], value: string): string {
|
||||
let processed = 0;
|
||||
let result = "";
|
||||
const nodeStack = [];
|
||||
|
||||
function selectStream(): NodeEvent[] {
|
||||
if (!original.length || !highlighted.length) {
|
||||
return original.length ? original : highlighted;
|
||||
}
|
||||
if (original[0].offset !== highlighted[0].offset) {
|
||||
return original[0].offset < highlighted[0].offset ? original : highlighted;
|
||||
}
|
||||
|
||||
/*
|
||||
To avoid starting the stream just before it should stop the order is
|
||||
ensured that original always starts first and closes last:
|
||||
if (event1 == 'start' && event2 == 'start')
|
||||
return original;
|
||||
if (event1 == 'start' && event2 == 'stop')
|
||||
return highlighted;
|
||||
if (event1 == 'stop' && event2 == 'start')
|
||||
return original;
|
||||
if (event1 == 'stop' && event2 == 'stop')
|
||||
return highlighted;
|
||||
... which is collapsed to:
|
||||
*/
|
||||
return highlighted[0].event === "start" ? original : highlighted;
|
||||
}
|
||||
|
||||
function open(node: Node): void {
|
||||
const htmlNode = node as HTMLElement;
|
||||
result += `<${tag(node)} ${[].map
|
||||
.call(htmlNode.attributes, (attr: Attr) => `${attr.nodeName}="${escape(attr.value)}"`)
|
||||
.join(" ")}>`;
|
||||
}
|
||||
|
||||
function close(node: Node): void {
|
||||
result += "</" + tag(node) + ">";
|
||||
}
|
||||
|
||||
function render(event: NodeEvent): void {
|
||||
(event.event === "start" ? open : close)(event.node);
|
||||
}
|
||||
|
||||
while (original.length || highlighted.length) {
|
||||
let stream = selectStream();
|
||||
result += escape(value.substring(processed, stream[0].offset));
|
||||
processed = stream[0].offset;
|
||||
if (stream === original) {
|
||||
/*
|
||||
On any opening or closing tag of the original markup we first close
|
||||
the entire highlighted node stack, then render the original tag along
|
||||
with all the following original tags at the same offset and then
|
||||
reopen all the tags on the highlighted stack.
|
||||
*/
|
||||
nodeStack.reverse().forEach(close);
|
||||
do {
|
||||
render(stream.splice(0, 1)[0]);
|
||||
stream = selectStream();
|
||||
} while (stream === original && stream.length && stream[0].offset === processed);
|
||||
nodeStack.reverse().forEach(open);
|
||||
} else {
|
||||
if (stream[0].event === "start") {
|
||||
nodeStack.push(stream[0].node);
|
||||
} else {
|
||||
nodeStack.pop();
|
||||
}
|
||||
render(stream.splice(0, 1)[0]);
|
||||
}
|
||||
}
|
||||
return result + escape(value.substr(processed));
|
||||
}
|
||||
|
||||
/* **** Highlight.js Private API **** */
|
||||
48
src/utils.js
|
|
@ -1,48 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* Utils (utils.js)
|
||||
* Author: rtfpessoa
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var merge = require('merge');
|
||||
|
||||
function Utils() {
|
||||
}
|
||||
|
||||
Utils.prototype.escape = function(str) {
|
||||
return str.slice(0)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
.replace(/\//g, '/');
|
||||
};
|
||||
|
||||
Utils.prototype.startsWith = function(str, start) {
|
||||
if (typeof start === 'object') {
|
||||
var result = false;
|
||||
start.forEach(function(s) {
|
||||
if (str.indexOf(s) === 0) {
|
||||
result = true;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return str && str.indexOf(start) === 0;
|
||||
};
|
||||
|
||||
Utils.prototype.valueOrEmpty = function(value) {
|
||||
return value || '';
|
||||
};
|
||||
|
||||
Utils.prototype.safeConfig = function(cfg, defaultConfig) {
|
||||
return merge.recursive(true, defaultConfig, cfg);
|
||||
};
|
||||
|
||||
module.exports.Utils = new Utils();
|
||||
})();
|
||||
54
src/utils.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
const specials = [
|
||||
// Order matters for these
|
||||
"-",
|
||||
"[",
|
||||
"]",
|
||||
// Order doesn't matter for any of these
|
||||
"/",
|
||||
"{",
|
||||
"}",
|
||||
"(",
|
||||
")",
|
||||
"*",
|
||||
"+",
|
||||
"?",
|
||||
".",
|
||||
"\\",
|
||||
"^",
|
||||
"$",
|
||||
"|"
|
||||
];
|
||||
|
||||
// All characters will be escaped with '\'
|
||||
// even though only some strictly require it when inside of []
|
||||
const regex = RegExp("[" + specials.join("\\") + "]", "g");
|
||||
|
||||
/**
|
||||
* Escapes all required characters for safe usage inside a RegExp
|
||||
*/
|
||||
export function escapeForRegExp(str: string): string {
|
||||
return str.replace(regex, "\\$&");
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts all '\' in @path to unix style '/'
|
||||
*/
|
||||
export function unifyPath(path: string): string {
|
||||
return path ? path.replace(/\\/g, "/") : path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create unique number identifier for @text
|
||||
*/
|
||||
export function hashCode(text: string): number {
|
||||
let i, chr, len;
|
||||
let hash = 0;
|
||||
|
||||
for (i = 0, len = text.length; i < len; i++) {
|
||||
chr = text.charCodeAt(i);
|
||||
hash = (hash << 5) - hash + chr;
|
||||
hash |= 0; // Convert to 32bit integer
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
|
@ -1,743 +0,0 @@
|
|||
var assert = require('assert');
|
||||
|
||||
var DiffParser = require('../src/diff-parser.js').DiffParser;
|
||||
|
||||
describe('DiffParser', function() {
|
||||
describe('generateDiffJson', function() {
|
||||
it('should parse unix with \n diff', function() {
|
||||
var diff =
|
||||
'diff --git a/sample b/sample\n' +
|
||||
'index 0000001..0ddf2ba\n' +
|
||||
'--- a/sample\n' +
|
||||
'+++ b/sample\n' +
|
||||
'@@ -1 +1 @@\n' +
|
||||
'-test\n' +
|
||||
'+test1r\n';
|
||||
checkDiffSample(diff);
|
||||
});
|
||||
|
||||
it('should parse windows with \r\n diff', function() {
|
||||
var diff =
|
||||
'diff --git a/sample b/sample\r\n' +
|
||||
'index 0000001..0ddf2ba\r\n' +
|
||||
'--- a/sample\r\n' +
|
||||
'+++ b/sample\r\n' +
|
||||
'@@ -1 +1 @@\r\n' +
|
||||
'-test\r\n' +
|
||||
'+test1r\r\n';
|
||||
checkDiffSample(diff);
|
||||
});
|
||||
|
||||
it('should parse old os x with \r diff', function() {
|
||||
var diff =
|
||||
'diff --git a/sample b/sample\r' +
|
||||
'index 0000001..0ddf2ba\r' +
|
||||
'--- a/sample\r' +
|
||||
'+++ b/sample\r' +
|
||||
'@@ -1 +1 @@\r' +
|
||||
'-test\r' +
|
||||
'+test1r\r';
|
||||
checkDiffSample(diff);
|
||||
});
|
||||
|
||||
it('should parse mixed eols diff', function() {
|
||||
var diff =
|
||||
'diff --git a/sample b/sample\n' +
|
||||
'index 0000001..0ddf2ba\r\n' +
|
||||
'--- a/sample\r' +
|
||||
'+++ b/sample\r\n' +
|
||||
'@@ -1 +1 @@\n' +
|
||||
'-test\r' +
|
||||
'+test1r\n';
|
||||
checkDiffSample(diff);
|
||||
});
|
||||
|
||||
function checkDiffSample(diff) {
|
||||
var result = DiffParser.generateDiffJson(diff);
|
||||
var file1 = result[0];
|
||||
assert.equal(1, result.length);
|
||||
assert.equal(1, file1.addedLines);
|
||||
assert.equal(1, file1.deletedLines);
|
||||
assert.equal('sample', file1.oldName);
|
||||
assert.equal('sample', file1.newName);
|
||||
assert.equal(1, file1.blocks.length);
|
||||
}
|
||||
|
||||
it('should parse diff with special characters', function() {
|
||||
var diff =
|
||||
'diff --git "a/bla with \ttab.scala" "b/bla with \ttab.scala"\n' +
|
||||
'index 4c679d7..e9bd385 100644\n' +
|
||||
'--- "a/bla with \ttab.scala"\n' +
|
||||
'+++ "b/bla with \ttab.scala"\n' +
|
||||
'@@ -1 +1,2 @@\n' +
|
||||
'-cenas\n' +
|
||||
'+cenas com ananas\n' +
|
||||
'+bananas';
|
||||
|
||||
var result = DiffParser.generateDiffJson(diff);
|
||||
var file1 = result[0];
|
||||
assert.equal(1, result.length);
|
||||
assert.equal(2, file1.addedLines);
|
||||
assert.equal(1, file1.deletedLines);
|
||||
assert.equal('bla with \ttab.scala', file1.oldName);
|
||||
assert.equal('bla with \ttab.scala', file1.newName);
|
||||
assert.equal(1, file1.blocks.length);
|
||||
});
|
||||
|
||||
it('should parse diff with prefix', function() {
|
||||
var diff =
|
||||
'diff --git "\tbla with \ttab.scala" "\tbla with \ttab.scala"\n' +
|
||||
'index 4c679d7..e9bd385 100644\n' +
|
||||
'--- "\tbla with \ttab.scala"\n' +
|
||||
'+++ "\tbla with \ttab.scala"\n' +
|
||||
'@@ -1 +1,2 @@\n' +
|
||||
'-cenas\n' +
|
||||
'+cenas com ananas\n' +
|
||||
'+bananas';
|
||||
|
||||
var result = DiffParser.generateDiffJson(diff, {'srcPrefix': '\t', 'dstPrefix': '\t'});
|
||||
var file1 = result[0];
|
||||
assert.equal(1, result.length);
|
||||
assert.equal(2, file1.addedLines);
|
||||
assert.equal(1, file1.deletedLines);
|
||||
assert.equal('bla with \ttab.scala', file1.oldName);
|
||||
assert.equal('bla with \ttab.scala', file1.newName);
|
||||
assert.equal(1, file1.blocks.length);
|
||||
});
|
||||
|
||||
it('should parse diff with deleted file', function() {
|
||||
var diff =
|
||||
'diff --git a/src/var/strundefined.js b/src/var/strundefined.js\n' +
|
||||
'deleted file mode 100644\n' +
|
||||
'index 04e16b0..0000000\n' +
|
||||
'--- a/src/var/strundefined.js\n' +
|
||||
'+++ /dev/null\n' +
|
||||
'@@ -1,3 +0,0 @@\n' +
|
||||
'-define(function() {\n' +
|
||||
'- return typeof undefined;\n' +
|
||||
'-});\n';
|
||||
|
||||
var result = DiffParser.generateDiffJson(diff);
|
||||
assert.equal(1, result.length);
|
||||
|
||||
var file1 = result[0];
|
||||
assert.equal(false, file1.isCombined);
|
||||
assert.equal(0, file1.addedLines);
|
||||
assert.equal(3, file1.deletedLines);
|
||||
assert.equal('src/var/strundefined.js', file1.oldName);
|
||||
assert.equal('/dev/null', file1.newName);
|
||||
assert.equal(1, file1.blocks.length);
|
||||
assert.equal(true, file1.isDeleted);
|
||||
assert.equal('04e16b0', file1.checksumBefore);
|
||||
assert.equal('0000000', file1.checksumAfter);
|
||||
});
|
||||
|
||||
it('should parse diff with new file', function() {
|
||||
var diff =
|
||||
'diff --git a/test.js b/test.js\n' +
|
||||
'new file mode 100644\n' +
|
||||
'index 0000000..e1e22ec\n' +
|
||||
'--- /dev/null\n' +
|
||||
'+++ b/test.js\n' +
|
||||
'@@ -0,0 +1,5 @@\n' +
|
||||
"+var parser = require('./source/git-parser');\n" +
|
||||
'+\n' +
|
||||
'+var patchLineList = [ false, false, false, false ];\n' +
|
||||
'+\n' +
|
||||
'+console.log(parser.parsePatchDiffResult(text, patchLineList));\n';
|
||||
|
||||
var result = DiffParser.generateDiffJson(diff);
|
||||
assert.equal(1, result.length);
|
||||
|
||||
var file1 = result[0];
|
||||
assert.equal(false, file1.isCombined);
|
||||
assert.equal(5, file1.addedLines);
|
||||
assert.equal(0, file1.deletedLines);
|
||||
assert.equal('/dev/null', file1.oldName);
|
||||
assert.equal('test.js', file1.newName);
|
||||
assert.equal(1, file1.blocks.length);
|
||||
assert.equal(true, file1.isNew);
|
||||
assert.equal(100644, file1.newFileMode);
|
||||
assert.equal('0000000', file1.checksumBefore);
|
||||
assert.equal('e1e22ec', file1.checksumAfter);
|
||||
});
|
||||
|
||||
it('should parse diff with nested diff', function() {
|
||||
var diff =
|
||||
'diff --git a/src/offset.js b/src/offset.js\n' +
|
||||
'index cc6ffb4..fa51f18 100644\n' +
|
||||
'--- a/src/offset.js\n' +
|
||||
'+++ b/src/offset.js\n' +
|
||||
'@@ -1,6 +1,5 @@\n' +
|
||||
"+var parser = require('./source/git-parser');\n" +
|
||||
'+\n' +
|
||||
"+var text = 'diff --git a/components/app/app.html b/components/app/app.html\\nindex ecb7a95..027bd9b 100644\\n--- a/components/app/app.html\\n+++ b/components/app/app.html\\n@@ -52,0 +53,3 @@\\n+\\n+\\n+\\n@@ -56,0 +60,3 @@\\n+\\n+\\n+\\n'\n" +
|
||||
'+var patchLineList = [ false, false, false, false ];\n' +
|
||||
'+\n' +
|
||||
'+console.log(parser.parsePatchDiffResult(text, patchLineList));\n';
|
||||
|
||||
var result = DiffParser.generateDiffJson(diff);
|
||||
assert.equal(1, result.length);
|
||||
|
||||
var file1 = result[0];
|
||||
assert.equal(false, file1.isCombined);
|
||||
assert.equal(6, file1.addedLines);
|
||||
assert.equal(0, file1.deletedLines);
|
||||
assert.equal('src/offset.js', file1.oldName);
|
||||
assert.equal('src/offset.js', file1.newName);
|
||||
assert.equal(1, file1.blocks.length);
|
||||
assert.equal(6, file1.blocks[0].lines.length);
|
||||
assert.equal('cc6ffb4', file1.checksumBefore);
|
||||
assert.equal('fa51f18', file1.checksumAfter);
|
||||
});
|
||||
|
||||
it('should parse diff with multiple blocks', function() {
|
||||
var diff =
|
||||
'diff --git a/src/attributes/classes.js b/src/attributes/classes.js\n' +
|
||||
'index c617824..c8d1393 100644\n' +
|
||||
'--- a/src/attributes/classes.js\n' +
|
||||
'+++ b/src/attributes/classes.js\n' +
|
||||
'@@ -1,10 +1,9 @@\n' +
|
||||
' define([\n' +
|
||||
' "../core",\n' +
|
||||
' "../var/rnotwhite",\n' +
|
||||
'- "../var/strundefined",\n' +
|
||||
' "../data/var/dataPriv",\n' +
|
||||
' "../core/init"\n' +
|
||||
'-], function( jQuery, rnotwhite, strundefined, dataPriv ) {\n' +
|
||||
'+], function( jQuery, rnotwhite, dataPriv ) {\n' +
|
||||
' \n' +
|
||||
' var rclass = /[\\t\\r\\n\\f]/g;\n' +
|
||||
' \n' +
|
||||
'@@ -128,7 +127,7 @@ jQuery.fn.extend({\n' +
|
||||
' }\n' +
|
||||
' \n' +
|
||||
' // Toggle whole class name\n' +
|
||||
'- } else if ( type === strundefined || type === "boolean" ) {\n' +
|
||||
'+ } else if ( value === undefined || type === "boolean" ) {\n' +
|
||||
' if ( this.className ) {\n' +
|
||||
' // store className if set\n' +
|
||||
' dataPriv.set( this, "__className__", this.className );\n';
|
||||
|
||||
var result = DiffParser.generateDiffJson(diff);
|
||||
assert.equal(1, result.length);
|
||||
|
||||
var file1 = result[0];
|
||||
assert.equal(false, file1.isCombined);
|
||||
assert.equal(2, file1.addedLines);
|
||||
assert.equal(3, file1.deletedLines);
|
||||
assert.equal('src/attributes/classes.js', file1.oldName);
|
||||
assert.equal('src/attributes/classes.js', file1.newName);
|
||||
assert.equal(2, file1.blocks.length);
|
||||
assert.equal(11, file1.blocks[0].lines.length);
|
||||
assert.equal(8, file1.blocks[1].lines.length);
|
||||
assert.equal('c617824', file1.checksumBefore);
|
||||
assert.equal('c8d1393', file1.checksumAfter);
|
||||
});
|
||||
|
||||
it('should parse diff with multiple files', function() {
|
||||
var diff =
|
||||
'diff --git a/src/core/init.js b/src/core/init.js\n' +
|
||||
'index e49196a..50f310c 100644\n' +
|
||||
'--- a/src/core/init.js\n' +
|
||||
'+++ b/src/core/init.js\n' +
|
||||
'@@ -101,7 +101,7 @@ var rootjQuery,\n' +
|
||||
' // HANDLE: $(function)\n' +
|
||||
' // Shortcut for document ready\n' +
|
||||
' } else if ( jQuery.isFunction( selector ) ) {\n' +
|
||||
'- return typeof rootjQuery.ready !== "undefined" ?\n' +
|
||||
'+ return rootjQuery.ready !== undefined ?\n' +
|
||||
' rootjQuery.ready( selector ) :\n' +
|
||||
' // Execute immediately if ready is not present\n' +
|
||||
' selector( jQuery );\n' +
|
||||
'diff --git a/src/event.js b/src/event.js\n' +
|
||||
'index 7336f4d..6183f70 100644\n' +
|
||||
'--- a/src/event.js\n' +
|
||||
'+++ b/src/event.js\n' +
|
||||
'@@ -1,6 +1,5 @@\n' +
|
||||
' define([\n' +
|
||||
' "./core",\n' +
|
||||
'- "./var/strundefined",\n' +
|
||||
' "./var/rnotwhite",\n' +
|
||||
' "./var/hasOwn",\n' +
|
||||
' "./var/slice",\n';
|
||||
|
||||
var result = DiffParser.generateDiffJson(diff);
|
||||
assert.equal(2, result.length);
|
||||
|
||||
var file1 = result[0];
|
||||
assert.equal(false, file1.isCombined);
|
||||
assert.equal(1, file1.addedLines);
|
||||
assert.equal(1, file1.deletedLines);
|
||||
assert.equal('src/core/init.js', file1.oldName);
|
||||
assert.equal('src/core/init.js', file1.newName);
|
||||
assert.equal(1, file1.blocks.length);
|
||||
assert.equal(8, file1.blocks[0].lines.length);
|
||||
assert.equal('e49196a', file1.checksumBefore);
|
||||
assert.equal('50f310c', file1.checksumAfter);
|
||||
|
||||
var file2 = result[1];
|
||||
assert.equal(false, file2.isCombined);
|
||||
assert.equal(0, file2.addedLines);
|
||||
assert.equal(1, file2.deletedLines);
|
||||
assert.equal('src/event.js', file2.oldName);
|
||||
assert.equal('src/event.js', file2.newName);
|
||||
assert.equal(1, file2.blocks.length);
|
||||
assert.equal(6, file2.blocks[0].lines.length);
|
||||
assert.equal('7336f4d', file2.checksumBefore);
|
||||
assert.equal('6183f70', file2.checksumAfter);
|
||||
});
|
||||
|
||||
it('should parse combined diff', function() {
|
||||
var diff =
|
||||
'diff --combined describe.c\n' +
|
||||
'index fabadb8,cc95eb0..4866510\n' +
|
||||
'--- a/describe.c\n' +
|
||||
'+++ b/describe.c\n' +
|
||||
'@@@ -98,20 -98,12 +98,20 @@@\n' +
|
||||
' return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1;\n' +
|
||||
' }\n' +
|
||||
' \n' +
|
||||
'- static void describe(char *arg)\n' +
|
||||
' -static void describe(struct commit *cmit, int last_one)\n' +
|
||||
'++static void describe(char *arg, int last_one)\n' +
|
||||
' {\n' +
|
||||
' + unsigned char sha1[20];\n' +
|
||||
' + struct commit *cmit;\n' +
|
||||
' struct commit_list *list;\n' +
|
||||
' static int initialized = 0;\n' +
|
||||
' struct commit_name *n;\n' +
|
||||
' \n' +
|
||||
' + if (get_sha1(arg, sha1) < 0)\n' +
|
||||
' + usage(describe_usage);\n' +
|
||||
' + cmit = lookup_commit_reference(sha1);\n' +
|
||||
' + if (!cmit)\n' +
|
||||
' + usage(describe_usage);\n' +
|
||||
' +\n' +
|
||||
' if (!initialized) {\n' +
|
||||
' initialized = 1;\n' +
|
||||
' for_each_ref(get_name);\n';
|
||||
|
||||
var result = DiffParser.generateDiffJson(diff);
|
||||
assert.equal(1, result.length);
|
||||
|
||||
var file1 = result[0];
|
||||
assert.equal(true, file1.isCombined);
|
||||
assert.equal(9, file1.addedLines);
|
||||
assert.equal(2, file1.deletedLines);
|
||||
assert.equal('describe.c', file1.oldName);
|
||||
assert.equal('describe.c', file1.newName);
|
||||
assert.equal(1, file1.blocks.length);
|
||||
assert.equal(22, file1.blocks[0].lines.length);
|
||||
assert.deepEqual(['4866510', 'cc95eb0'].sort(), file1.checksumBefore.sort());
|
||||
assert.equal('fabadb8', file1.checksumAfter);
|
||||
});
|
||||
|
||||
it('should parse diffs with copied files', function() {
|
||||
var diff =
|
||||
'diff --git a/index.js b/more-index.js\n' +
|
||||
'dissimilarity index 5%\n' +
|
||||
'copy from index.js\n' +
|
||||
'copy to more-index.js\n';
|
||||
|
||||
var result = DiffParser.generateDiffJson(diff);
|
||||
assert.equal(1, result.length);
|
||||
|
||||
var file1 = result[0];
|
||||
assert.equal(0, file1.addedLines);
|
||||
assert.equal(0, file1.deletedLines);
|
||||
assert.equal('index.js', file1.oldName);
|
||||
assert.equal('more-index.js', file1.newName);
|
||||
assert.equal(0, file1.blocks.length);
|
||||
assert.equal(true, file1.isCopy);
|
||||
assert.equal(5, file1.changedPercentage);
|
||||
});
|
||||
|
||||
it('should parse diffs with moved files', function() {
|
||||
var diff =
|
||||
'diff --git a/more-index.js b/other-index.js\n' +
|
||||
'similarity index 86%\n' +
|
||||
'rename from more-index.js\n' +
|
||||
'rename to other-index.js\n';
|
||||
|
||||
var result = DiffParser.generateDiffJson(diff);
|
||||
assert.equal(1, result.length);
|
||||
|
||||
var file1 = result[0];
|
||||
assert.equal(0, file1.addedLines);
|
||||
assert.equal(0, file1.deletedLines);
|
||||
assert.equal('more-index.js', file1.oldName);
|
||||
assert.equal('other-index.js', file1.newName);
|
||||
assert.equal(0, file1.blocks.length);
|
||||
assert.equal(true, file1.isRename);
|
||||
assert.equal(86, file1.unchangedPercentage);
|
||||
});
|
||||
|
||||
it('should parse diffs correct line numbers', function() {
|
||||
var diff =
|
||||
'diff --git a/sample b/sample\n' +
|
||||
'index 0000001..0ddf2ba\n' +
|
||||
'--- a/sample\n' +
|
||||
'+++ b/sample\n' +
|
||||
'@@ -1 +1,2 @@\n' +
|
||||
'-test\n' +
|
||||
'+test1r\n';
|
||||
|
||||
var result = DiffParser.generateDiffJson(diff);
|
||||
assert.equal(1, result.length);
|
||||
|
||||
var file1 = result[0];
|
||||
assert.equal(1, file1.addedLines);
|
||||
assert.equal(1, file1.deletedLines);
|
||||
assert.equal('sample', file1.oldName);
|
||||
assert.equal('sample', file1.newName);
|
||||
assert.equal(1, file1.blocks.length);
|
||||
assert.equal(2, file1.blocks[0].lines.length);
|
||||
assert.equal(1, file1.blocks[0].lines[0].oldNumber);
|
||||
assert.equal(null, file1.blocks[0].lines[0].newNumber);
|
||||
assert.equal(null, file1.blocks[0].lines[1].oldNumber);
|
||||
assert.equal(1, file1.blocks[0].lines[1].newNumber);
|
||||
});
|
||||
|
||||
it('should parse unified non git diff and strip timestamps off the headers', function() {
|
||||
var diffs = [
|
||||
// 2 hours ahead of GMT
|
||||
'--- a/sample.js 2016-10-25 11:37:14.000000000 +0200\n' +
|
||||
'+++ b/sample.js 2016-10-25 11:37:14.000000000 +0200\n' +
|
||||
'@@ -1 +1,2 @@\n' +
|
||||
'-test\n' +
|
||||
'+test1r\n' +
|
||||
'+test2r\n',
|
||||
// 2 hours behind GMT
|
||||
'--- a/sample.js 2016-10-25 11:37:14.000000000 -0200\n' +
|
||||
'+++ b/sample.js 2016-10-25 11:37:14.000000000 -0200\n' +
|
||||
'@@ -1 +1,2 @@\n' +
|
||||
'-test\n' +
|
||||
'+test1r\n' +
|
||||
'+test2r\n'
|
||||
];
|
||||
|
||||
diffs.forEach(function(diff) {
|
||||
var result = DiffParser.generateDiffJson(diff);
|
||||
var file1 = result[0];
|
||||
assert.equal(1, result.length);
|
||||
assert.equal(2, file1.addedLines);
|
||||
assert.equal(1, file1.deletedLines);
|
||||
assert.equal('sample.js', file1.oldName);
|
||||
assert.equal('sample.js', file1.newName);
|
||||
assert.equal(1, file1.blocks.length);
|
||||
|
||||
var linesContent = file1.blocks[0].lines.map(function(line) {
|
||||
return line.content;
|
||||
});
|
||||
assert.deepEqual(linesContent, ['-test', '+test1r', '+test2r']);
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse unified non git diff', function() {
|
||||
var diff =
|
||||
'--- a/sample.js\n' +
|
||||
'+++ b/sample.js\n' +
|
||||
'@@ -1 +1,2 @@\n' +
|
||||
'-test\n' +
|
||||
'+test1r\n' +
|
||||
'+test2r\n';
|
||||
|
||||
var result = DiffParser.generateDiffJson(diff);
|
||||
var file1 = result[0];
|
||||
assert.equal(1, result.length);
|
||||
assert.equal(2, file1.addedLines);
|
||||
assert.equal(1, file1.deletedLines);
|
||||
assert.equal('sample.js', file1.oldName);
|
||||
assert.equal('sample.js', file1.newName);
|
||||
assert.equal(1, file1.blocks.length);
|
||||
|
||||
var linesContent = file1.blocks[0].lines.map(function(line) {
|
||||
return line.content;
|
||||
});
|
||||
assert.deepEqual(linesContent, ['-test', '+test1r', '+test2r']);
|
||||
});
|
||||
|
||||
it('should parse unified diff with multiple hunks and files', function() {
|
||||
var diff =
|
||||
'--- sample.js\n' +
|
||||
'+++ sample.js\n' +
|
||||
'@@ -1 +1,2 @@\n' +
|
||||
'-test\n' +
|
||||
'@@ -10 +20,2 @@\n' +
|
||||
'+test\n' +
|
||||
'--- sample1.js\n' +
|
||||
'+++ sample1.js\n' +
|
||||
'@@ -1 +1,2 @@\n' +
|
||||
'+test1';
|
||||
|
||||
var result = DiffParser.generateDiffJson(diff);
|
||||
assert.equal(2, result.length);
|
||||
|
||||
var file1 = result[0];
|
||||
assert.equal(1, file1.addedLines);
|
||||
assert.equal(1, file1.deletedLines);
|
||||
assert.equal('sample.js', file1.oldName);
|
||||
assert.equal('sample.js', file1.newName);
|
||||
assert.equal(2, file1.blocks.length);
|
||||
|
||||
var linesContent1 = file1.blocks[0].lines.map(function(line) {
|
||||
return line.content;
|
||||
});
|
||||
assert.deepEqual(linesContent1, ['-test']);
|
||||
|
||||
var linesContent2 = file1.blocks[1].lines.map(function(line) {
|
||||
return line.content;
|
||||
});
|
||||
assert.deepEqual(linesContent2, ['+test']);
|
||||
|
||||
var file2 = result[1];
|
||||
assert.equal(1, file2.addedLines);
|
||||
assert.equal(0, file2.deletedLines);
|
||||
assert.equal('sample1.js', file2.oldName);
|
||||
assert.equal('sample1.js', file2.newName);
|
||||
assert.equal(1, file2.blocks.length);
|
||||
|
||||
var linesContent = file2.blocks[0].lines.map(function(line) {
|
||||
return line.content;
|
||||
});
|
||||
assert.deepEqual(linesContent, ['+test1']);
|
||||
});
|
||||
|
||||
it('should parse diff with --- and +++ in the context lines', function() {
|
||||
var diff =
|
||||
'--- sample.js\n' +
|
||||
'+++ sample.js\n' +
|
||||
'@@ -1,8 +1,8 @@\n' +
|
||||
' test\n' +
|
||||
' \n' +
|
||||
'-- 1\n' +
|
||||
'--- 1\n' +
|
||||
'---- 1\n' +
|
||||
' \n' +
|
||||
'++ 2\n' +
|
||||
'+++ 2\n' +
|
||||
'++++ 2';
|
||||
|
||||
var result = DiffParser.generateDiffJson(diff);
|
||||
var file1 = result[0];
|
||||
assert.equal(1, result.length);
|
||||
assert.equal(3, file1.addedLines);
|
||||
assert.equal(3, file1.deletedLines);
|
||||
assert.equal('sample.js', file1.oldName);
|
||||
assert.equal('sample.js', file1.newName);
|
||||
assert.equal(1, file1.blocks.length);
|
||||
|
||||
var linesContent = file1.blocks[0].lines.map(function(line) {
|
||||
return line.content;
|
||||
});
|
||||
assert.deepEqual(linesContent,
|
||||
[' test', ' ', '-- 1', '--- 1', '---- 1', ' ', '++ 2', '+++ 2', '++++ 2']);
|
||||
});
|
||||
|
||||
it('should parse diff without proper hunk headers', function() {
|
||||
var diff =
|
||||
'--- sample.js\n' +
|
||||
'+++ sample.js\n' +
|
||||
'@@ @@\n' +
|
||||
' test';
|
||||
|
||||
var result = DiffParser.generateDiffJson(diff);
|
||||
var file1 = result[0];
|
||||
assert.equal(1, result.length);
|
||||
assert.equal(0, file1.addedLines);
|
||||
assert.equal(0, file1.deletedLines);
|
||||
assert.equal('sample.js', file1.oldName);
|
||||
assert.equal('sample.js', file1.newName);
|
||||
assert.equal(1, file1.blocks.length);
|
||||
|
||||
var linesContent = file1.blocks[0].lines.map(function(line) {
|
||||
return line.content;
|
||||
});
|
||||
assert.deepEqual(linesContent, [' test']);
|
||||
});
|
||||
|
||||
it('should parse binary file diff', function() {
|
||||
var diff =
|
||||
'diff --git a/last-changes-config.png b/last-changes-config.png\n' +
|
||||
'index 322248b..56fc1f2 100644\n' +
|
||||
'--- a/last-changes-config.png\n' +
|
||||
'+++ b/last-changes-config.png\n' +
|
||||
'Binary files differ';
|
||||
|
||||
var result = DiffParser.generateDiffJson(diff);
|
||||
var file1 = result[0];
|
||||
assert.equal(1, result.length);
|
||||
assert.equal(0, file1.addedLines);
|
||||
assert.equal(0, file1.deletedLines);
|
||||
assert.equal('last-changes-config.png', file1.oldName);
|
||||
assert.equal('last-changes-config.png', file1.newName);
|
||||
assert.equal(1, file1.blocks.length);
|
||||
assert.equal(0, file1.blocks[0].lines.length);
|
||||
assert.equal('Binary files differ', file1.blocks[0].header);
|
||||
});
|
||||
|
||||
it('should parse diff with --find-renames', function() {
|
||||
var diff =
|
||||
'diff --git a/src/test-bar.js b/src/test-baz.js\n' +
|
||||
'similarity index 98%\n' +
|
||||
'rename from src/test-bar.js\n' +
|
||||
'rename to src/test-baz.js\n' +
|
||||
'index e01513b..f14a870 100644\n' +
|
||||
'--- a/src/test-bar.js\n' +
|
||||
'+++ b/src/test-baz.js\n' +
|
||||
'@@ -1,4 +1,32 @@\n' +
|
||||
' function foo() {\n' +
|
||||
'-var bar = "Whoops!";\n' +
|
||||
'+var baz = "Whoops!";\n' +
|
||||
' }\n' +
|
||||
' ';
|
||||
|
||||
var result = DiffParser.generateDiffJson(diff);
|
||||
var file1 = result[0];
|
||||
assert.equal(1, result.length);
|
||||
assert.equal(1, file1.addedLines);
|
||||
assert.equal(1, file1.deletedLines);
|
||||
assert.equal('src/test-bar.js', file1.oldName);
|
||||
assert.equal('src/test-baz.js', file1.newName);
|
||||
assert.equal(1, file1.blocks.length);
|
||||
assert.equal(5, file1.blocks[0].lines.length);
|
||||
var linesContent = file1.blocks[0].lines.map(function(line) {
|
||||
return line.content;
|
||||
});
|
||||
assert.deepEqual(linesContent,
|
||||
[' function foo() {', '-var bar = "Whoops!";', '+var baz = "Whoops!";', ' }', ' ']);
|
||||
});
|
||||
|
||||
it('should parse diff with prefix', function() {
|
||||
var diff =
|
||||
'diff --git "\tTest.scala" "\tScalaTest.scala"\n' +
|
||||
'similarity index 88%\n' +
|
||||
'rename from Test.scala\n' +
|
||||
'rename to ScalaTest.scala\n' +
|
||||
'index 7d1f9bf..8b13271 100644\n' +
|
||||
'--- "\tTest.scala"\n' +
|
||||
'+++ "\tScalaTest.scala"\n' +
|
||||
'@@ -1,6 +1,8 @@\n' +
|
||||
' class Test {\n' +
|
||||
' \n' +
|
||||
' def method1 = ???\n' +
|
||||
'+\n' +
|
||||
'+ def method2 = ???\n' +
|
||||
' \n' +
|
||||
' def myMethod = ???\n' +
|
||||
' \n' +
|
||||
'@@ -10,7 +12,6 @@ class Test {\n' +
|
||||
' \n' +
|
||||
' def + = ???\n' +
|
||||
' \n' +
|
||||
'- def |> = ???\n' +
|
||||
' \n' +
|
||||
' }\n' +
|
||||
' \n' +
|
||||
'diff --git "\ttardis.png" "\ttardis.png"\n' +
|
||||
'new file mode 100644\n' +
|
||||
'index 0000000..d503a29\n' +
|
||||
'Binary files /dev/null and "\ttardis.png" differ\n' +
|
||||
'diff --git a/src/test-bar.js b/src/test-baz.js\n' +
|
||||
'similarity index 98%\n' +
|
||||
'rename from src/test-bar.js\n' +
|
||||
'rename to src/test-baz.js\n' +
|
||||
'index e01513b..f14a870 100644\n' +
|
||||
'--- a/src/test-bar.js\n' +
|
||||
'+++ b/src/test-baz.js\n' +
|
||||
'@@ -1,4 +1,32 @@\n' +
|
||||
' function foo() {\n' +
|
||||
'-var bar = "Whoops!";\n' +
|
||||
'+var baz = "Whoops!";\n' +
|
||||
' }\n' +
|
||||
' ';
|
||||
|
||||
var result = DiffParser.generateDiffJson(diff, {'srcPrefix': '\t', 'dstPrefix': '\t'});
|
||||
assert.equal(3, result.length);
|
||||
|
||||
var file1 = result[0];
|
||||
assert.equal(2, file1.addedLines);
|
||||
assert.equal(1, file1.deletedLines);
|
||||
assert.equal('Test.scala', file1.oldName);
|
||||
assert.equal('ScalaTest.scala', file1.newName);
|
||||
assert.equal(2, file1.blocks.length);
|
||||
assert.equal(8, file1.blocks[0].lines.length);
|
||||
assert.equal(7, file1.blocks[1].lines.length);
|
||||
|
||||
var file2 = result[1];
|
||||
assert.equal('/dev/null', file2.oldName);
|
||||
assert.equal('tardis.png', file2.newName);
|
||||
|
||||
var file3 = result[2];
|
||||
assert.equal(1, file3.addedLines);
|
||||
assert.equal(1, file3.deletedLines);
|
||||
assert.equal('src/test-bar.js', file3.oldName);
|
||||
assert.equal('src/test-baz.js', file3.newName);
|
||||
assert.equal(1, file3.blocks.length);
|
||||
assert.equal(5, file3.blocks[0].lines.length);
|
||||
var linesContent = file3.blocks[0].lines.map(function(line) {
|
||||
return line.content;
|
||||
});
|
||||
assert.deepEqual(linesContent,
|
||||
[' function foo() {', '-var bar = "Whoops!";', '+var baz = "Whoops!";', ' }', ' ']);
|
||||
});
|
||||
|
||||
it('should parse binary with content', function() {
|
||||
var diff =
|
||||
'diff --git a/favicon.png b/favicon.png\n' +
|
||||
'deleted file mode 100644\n' +
|
||||
'index 2a9d516a5647205d7be510dd0dff93a3663eff6f..0000000000000000000000000000000000000000\n' +
|
||||
'GIT binary patch\n' +
|
||||
'literal 0\n' +
|
||||
'HcmV?d00001\n' +
|
||||
'\n' +
|
||||
'literal 471\n' +
|
||||
'zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf<Z~8yL>4nJ\n' +
|
||||
'za0`Jj<E6WGe}IBwC9V-A&PAz-C7Jno3L%-fsSJk3`UaNzMkcGzh!g=;$beJ?=ckpF\n' +
|
||||
'zCl;kLIHu$$r7E~(7NwTw7iAYKI0u`(*t4mJfq_xq)5S5wqIc=!hrWj$cv|<b{x!c(\n' +
|
||||
'z;3r#y;31Y&=1q>qPVOAS4ANVKzqmCp=Cty@U^(7zk!jHsvT~YI{F^=Ex6g|gox78w\n' +
|
||||
'z+Sn2Du3GS9U7qU`1*NYYlJi3u-!<?H-eky}wyIIL;8VU@wCDrb0``&v(jQ*DWSR4K\n' +
|
||||
'zPq(3;isEyho{emNa=%%!jDPE`l3u;5d=q=<+v8kO-=C`*G#t-*AiE-D>-_B#8k9H0\n' +
|
||||
'zGl{FnZs<2$wz5^=Q2h-1XI^s{LQL1#T4epqNPC%Orl(tD_@!*EY++~^Lt2<2&!&%=\n' +
|
||||
'z`m>(TYj6uS7jDdt=eH>iOyQg(QMR<-Fw8)Dk^ZG)XQTuzEgl{`GpS?Cfq9818R9~=\n' +
|
||||
'z{&h9@9n8F^?|qusoPy{k#%tVHzu7H$t26CR`BJZk*Ixf&u36WuS=?6m2^ho-p00i_\n' +
|
||||
'I>zopr0Nz-&lmGw#\n' +
|
||||
'diff --git a/src/test-bar.js b/src/test-baz.js\n' +
|
||||
'similarity index 98%\n' +
|
||||
'rename from src/test-bar.js\n' +
|
||||
'rename to src/test-baz.js\n' +
|
||||
'index e01513b..f14a870 100644\n' +
|
||||
'--- a/src/test-bar.js\n' +
|
||||
'+++ b/src/test-baz.js\n' +
|
||||
'@@ -1,4 +1,32 @@\n' +
|
||||
' function foo() {\n' +
|
||||
'-var bar = "Whoops!";\n' +
|
||||
'+var baz = "Whoops!";\n' +
|
||||
' }\n' +
|
||||
' ';
|
||||
|
||||
var result = DiffParser.generateDiffJson(diff);
|
||||
assert.equal(2, result.length);
|
||||
|
||||
var file1 = result[0];
|
||||
assert.equal('favicon.png', file1.oldName);
|
||||
assert.equal('favicon.png', file1.newName);
|
||||
assert.equal(1, file1.blocks.length);
|
||||
assert.equal(0, file1.blocks[0].lines.length);
|
||||
|
||||
var file2 = result[1];
|
||||
assert.equal(1, file2.addedLines);
|
||||
assert.equal(1, file2.deletedLines);
|
||||
assert.equal('src/test-bar.js', file2.oldName);
|
||||
assert.equal('src/test-baz.js', file2.newName);
|
||||
assert.equal(1, file2.blocks.length);
|
||||
assert.equal(5, file2.blocks[0].lines.length);
|
||||
var linesContent = file2.blocks[0].lines.map(function(line) {
|
||||
return line.content;
|
||||
});
|
||||
assert.deepEqual(linesContent,
|
||||
[' function foo() {', '-var bar = "Whoops!";', '+var baz = "Whoops!";', ' }', ' ']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,145 +0,0 @@
|
|||
var assert = require('assert');
|
||||
var FileListPrinter = require('../src/file-list-printer.js').FileListPrinter;
|
||||
|
||||
describe('FileListPrinter', function() {
|
||||
describe('generateFileList', function() {
|
||||
it('should expose old and new files to templates', function() {
|
||||
var files = [{
|
||||
addedlines: 12,
|
||||
deletedlines: 41,
|
||||
language: 'js',
|
||||
oldName: 'my/file/name.js',
|
||||
newName: 'my/file/name.js'
|
||||
}, {
|
||||
addedLines: 12,
|
||||
deletedLines: 41,
|
||||
language: 'js',
|
||||
oldName: 'my/file/name1.js',
|
||||
newName: 'my/file/name2.js'
|
||||
}, {
|
||||
addedLines: 12,
|
||||
deletedLines: 0,
|
||||
language: 'js',
|
||||
oldName: 'dev/null',
|
||||
newName: 'my/file/name.js',
|
||||
isNew: true
|
||||
}, {
|
||||
addedLines: 0,
|
||||
deletedLines: 41,
|
||||
language: 'js',
|
||||
oldName: 'my/file/name.js',
|
||||
newName: 'dev/null',
|
||||
isDeleted: true
|
||||
}];
|
||||
|
||||
var fileListPrinter = new FileListPrinter({
|
||||
rawTemplates: {
|
||||
'file-summary-wrapper': '{{{files}}}',
|
||||
'file-summary-line': '{{oldName}}, {{newName}}, {{fileName}}'
|
||||
}
|
||||
});
|
||||
|
||||
var fileHtml = fileListPrinter.generateFileList(files);
|
||||
var expected = 'my/file/name.js, my/file/name.js, my/file/name.js\n' +
|
||||
'my/file/name1.js, my/file/name2.js, my/file/{name1.js → name2.js}\n' +
|
||||
'dev/null, my/file/name.js, my/file/name.js\n' +
|
||||
'my/file/name.js, dev/null, my/file/name.js';
|
||||
|
||||
assert.equal(expected, fileHtml);
|
||||
});
|
||||
|
||||
it('should work for all kinds of files', function() {
|
||||
var files = [{
|
||||
addedLines: 12,
|
||||
deletedLines: 41,
|
||||
language: 'js',
|
||||
oldName: 'my/file/name.js',
|
||||
newName: 'my/file/name.js'
|
||||
}, {
|
||||
addedLines: 12,
|
||||
deletedLines: 41,
|
||||
language: 'js',
|
||||
oldName: 'my/file/name1.js',
|
||||
newName: 'my/file/name2.js'
|
||||
}, {
|
||||
addedLines: 12,
|
||||
deletedLines: 0,
|
||||
language: 'js',
|
||||
oldName: 'dev/null',
|
||||
newName: 'my/file/name.js',
|
||||
isNew: true
|
||||
}, {
|
||||
addedLines: 0,
|
||||
deletedLines: 41,
|
||||
language: 'js',
|
||||
oldName: 'my/file/name.js',
|
||||
newName: 'dev/null',
|
||||
isDeleted: true
|
||||
}];
|
||||
|
||||
var fileListPrinter = new FileListPrinter();
|
||||
var fileHtml = fileListPrinter.generateFileList(files);
|
||||
|
||||
var expected =
|
||||
'<div class="d2h-file-list-wrapper">\n' +
|
||||
' <div class="d2h-file-list-header">\n' +
|
||||
' <span class="d2h-file-list-title">Files changed (4)</span>\n' +
|
||||
' <a class="d2h-file-switch d2h-hide">hide</a>\n' +
|
||||
' <a class="d2h-file-switch d2h-show">show</a>\n' +
|
||||
' </div>\n' +
|
||||
' <ol class="d2h-file-list">\n' +
|
||||
' <li class="d2h-file-list-line">\n' +
|
||||
' <span class="d2h-file-name-wrapper">\n' +
|
||||
' <svg aria-hidden="true" class="d2h-icon d2h-changed" height="16" title="modified" version="1.1"\n' +
|
||||
' viewBox="0 0 14 16" width="14">\n' +
|
||||
' <path d="M13 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V2c0-0.55-0.45-1-1-1z m0 13H1V2h12v12zM4 8c0-1.66 1.34-3 3-3s3 1.34 3 3-1.34 3-3 3-3-1.34-3-3z"></path>\n' +
|
||||
' </svg> <a href="#d2h-781444" class="d2h-file-name">my/file/name.js</a>\n' +
|
||||
' <span class="d2h-file-stats">\n' +
|
||||
' <span class="d2h-lines-added">+12</span>\n' +
|
||||
' <span class="d2h-lines-deleted">-41</span>\n' +
|
||||
' </span>\n' +
|
||||
' </span>\n' +
|
||||
'</li>\n' +
|
||||
'<li class="d2h-file-list-line">\n' +
|
||||
' <span class="d2h-file-name-wrapper">\n' +
|
||||
' <svg aria-hidden="true" class="d2h-icon d2h-moved" height="16" title="renamed" version="1.1"\n' +
|
||||
' viewBox="0 0 14 16" width="14">\n' +
|
||||
' <path d="M6 9H3V7h3V4l5 4-5 4V9z m8-7v12c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h12c0.55 0 1 0.45 1 1z m-1 0H1v12h12V2z"></path>\n' +
|
||||
' </svg> <a href="#d2h-662683" class="d2h-file-name">my/file/{name1.js → name2.js}</a>\n' +
|
||||
' <span class="d2h-file-stats">\n' +
|
||||
' <span class="d2h-lines-added">+12</span>\n' +
|
||||
' <span class="d2h-lines-deleted">-41</span>\n' +
|
||||
' </span>\n' +
|
||||
' </span>\n' +
|
||||
'</li>\n' +
|
||||
'<li class="d2h-file-list-line">\n' +
|
||||
' <span class="d2h-file-name-wrapper">\n' +
|
||||
' <svg aria-hidden="true" class="d2h-icon d2h-added" height="16" title="added" version="1.1" viewBox="0 0 14 16"\n' +
|
||||
' width="14">\n' +
|
||||
' <path d="M13 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V2c0-0.55-0.45-1-1-1z m0 13H1V2h12v12zM6 9H3V7h3V4h2v3h3v2H8v3H6V9z"></path>\n' +
|
||||
' </svg> <a href="#d2h-781444" class="d2h-file-name">my/file/name.js</a>\n' +
|
||||
' <span class="d2h-file-stats">\n' +
|
||||
' <span class="d2h-lines-added">+12</span>\n' +
|
||||
' <span class="d2h-lines-deleted">-0</span>\n' +
|
||||
' </span>\n' +
|
||||
' </span>\n' +
|
||||
'</li>\n' +
|
||||
'<li class="d2h-file-list-line">\n' +
|
||||
' <span class="d2h-file-name-wrapper">\n' +
|
||||
' <svg aria-hidden="true" class="d2h-icon d2h-deleted" height="16" title="removed" version="1.1"\n' +
|
||||
' viewBox="0 0 14 16" width="14">\n' +
|
||||
' <path d="M13 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V2c0-0.55-0.45-1-1-1z m0 13H1V2h12v12zM11 9H3V7h8v2z"></path>\n' +
|
||||
' </svg> <a href="#d2h-781444" class="d2h-file-name">my/file/name.js</a>\n' +
|
||||
' <span class="d2h-file-stats">\n' +
|
||||
' <span class="d2h-lines-added">+0</span>\n' +
|
||||
' <span class="d2h-lines-deleted">-41</span>\n' +
|
||||
' </span>\n' +
|
||||
' </span>\n' +
|
||||
'</li>\n' +
|
||||
' </ol>\n' +
|
||||
'</div>';
|
||||
|
||||
assert.equal(expected, fileHtml);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
var assert = require('assert');
|
||||
|
||||
var HoganJsUtils = new (require('../src/hoganjs-utils.js').HoganJsUtils)();
|
||||
var diffParser = require('../src/diff-parser.js').DiffParser;
|
||||
|
||||
describe('HoganJsUtils', function() {
|
||||
describe('render', function() {
|
||||
var emptyDiffHtml =
|
||||
'<tr>\n' +
|
||||
' <td class="d2h-info">\n' +
|
||||
' <div class="d2h-code-line d2h-info">\n' +
|
||||
' File without changes\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr>';
|
||||
|
||||
it('should render view', function() {
|
||||
var result = HoganJsUtils.render('generic', 'empty-diff', {
|
||||
contentClass: 'd2h-code-line',
|
||||
diffParser: diffParser
|
||||
});
|
||||
assert.equal(emptyDiffHtml, result);
|
||||
});
|
||||
|
||||
it('should render view without cache', function() {
|
||||
var result = HoganJsUtils.render('generic', 'empty-diff', {
|
||||
contentClass: 'd2h-code-line',
|
||||
diffParser: diffParser
|
||||
}, {noCache: true});
|
||||
assert.equal(emptyDiffHtml, result);
|
||||
});
|
||||
|
||||
it('should return null if template is missing', function() {
|
||||
var hoganUtils = new (require('../src/hoganjs-utils.js').HoganJsUtils)({noCache: true});
|
||||
var result = hoganUtils.render('generic', 'missing-template', {});
|
||||
assert.equal(null, result);
|
||||
});
|
||||
|
||||
it('should allow templates to be overridden with compiled templates', function() {
|
||||
var emptyDiffTemplate = HoganJsUtils.compile('<p>{{myName}}</p>');
|
||||
|
||||
var config = {templates: {'generic-empty-diff': emptyDiffTemplate}};
|
||||
var hoganUtils = new (require('../src/hoganjs-utils.js').HoganJsUtils)(config);
|
||||
var result = hoganUtils.render('generic', 'empty-diff', {myName: 'Rodrigo Fernandes'});
|
||||
assert.equal('<p>Rodrigo Fernandes</p>', result);
|
||||
});
|
||||
|
||||
it('should allow templates to be overridden with uncompiled templates', function() {
|
||||
var emptyDiffTemplate = '<p>{{myName}}</p>';
|
||||
|
||||
var config = {rawTemplates: {'generic-empty-diff': emptyDiffTemplate}};
|
||||
var hoganUtils = new (require('../src/hoganjs-utils.js').HoganJsUtils)(config);
|
||||
var result = hoganUtils.render('generic', 'empty-diff', {myName: 'Rodrigo Fernandes'});
|
||||
assert.equal('<p>Rodrigo Fernandes</p>', result);
|
||||
});
|
||||
|
||||
it('should allow templates to be overridden giving priority to compiled templates', function() {
|
||||
var emptyDiffTemplate = HoganJsUtils.compile('<p>{{myName}}</p>');
|
||||
var emptyDiffTemplateUncompiled = '<p>Not used!</p>';
|
||||
|
||||
var config = {
|
||||
templates: {'generic-empty-diff': emptyDiffTemplate},
|
||||
rawTemplates: {'generic-empty-diff': emptyDiffTemplateUncompiled}
|
||||
};
|
||||
var hoganUtils = new (require('../src/hoganjs-utils.js').HoganJsUtils)(config);
|
||||
var result = hoganUtils.render('generic', 'empty-diff', {myName: 'Rodrigo Fernandes'});
|
||||
assert.equal('<p>Rodrigo Fernandes</p>', result);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,604 +0,0 @@
|
|||
var assert = require('assert');
|
||||
|
||||
var LineByLinePrinter = require('../src/line-by-line-printer.js').LineByLinePrinter;
|
||||
|
||||
describe('LineByLinePrinter', function() {
|
||||
describe('_generateEmptyDiff', function() {
|
||||
it('should return an empty diff', function() {
|
||||
var lineByLinePrinter = new LineByLinePrinter({});
|
||||
var fileHtml = lineByLinePrinter._generateEmptyDiff();
|
||||
var expected = '<tr>\n' +
|
||||
' <td class="d2h-info">\n' +
|
||||
' <div class="d2h-code-line d2h-info">\n' +
|
||||
' File without changes\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr>';
|
||||
|
||||
assert.equal(expected, fileHtml);
|
||||
});
|
||||
});
|
||||
|
||||
describe('makeLineHtml', function() {
|
||||
it('should work for insertions', function() {
|
||||
var diffParser = require('../src/diff-parser.js').DiffParser;
|
||||
var lineByLinePrinter = new LineByLinePrinter({});
|
||||
var fileHtml = lineByLinePrinter.makeLineHtml(false,
|
||||
diffParser.LINE_TYPE.INSERTS, '', 30, 'test', '+');
|
||||
fileHtml = fileHtml.replace(/\n\n+/g, '\n');
|
||||
var expected = '<tr>\n' +
|
||||
' <td class="d2h-code-linenumber d2h-ins">\n' +
|
||||
' <div class="line-num1"></div>\n' +
|
||||
'<div class="line-num2">30</div>\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-ins">\n' +
|
||||
' <div class="d2h-code-line d2h-ins">\n' +
|
||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||
' <span class="d2h-code-line-ctn">test</span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr>';
|
||||
|
||||
assert.equal(expected, fileHtml);
|
||||
});
|
||||
|
||||
it('should work for deletions', function() {
|
||||
var diffParser = require('../src/diff-parser.js').DiffParser;
|
||||
var lineByLinePrinter = new LineByLinePrinter({});
|
||||
var fileHtml = lineByLinePrinter.makeLineHtml(false,
|
||||
diffParser.LINE_TYPE.DELETES, 30, '', 'test', '-');
|
||||
fileHtml = fileHtml.replace(/\n\n+/g, '\n');
|
||||
var expected = '<tr>\n' +
|
||||
' <td class="d2h-code-linenumber d2h-del">\n' +
|
||||
' <div class="line-num1">30</div>\n' +
|
||||
'<div class="line-num2"></div>\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-del">\n' +
|
||||
' <div class="d2h-code-line d2h-del">\n' +
|
||||
' <span class="d2h-code-line-prefix">-</span>\n' +
|
||||
' <span class="d2h-code-line-ctn">test</span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr>';
|
||||
|
||||
assert.equal(expected, fileHtml);
|
||||
});
|
||||
|
||||
it('should convert indents into non breakin spaces (2 white spaces)', function() {
|
||||
var diffParser = require('../src/diff-parser.js').DiffParser;
|
||||
var lineByLinePrinter = new LineByLinePrinter({});
|
||||
var fileHtml = lineByLinePrinter.makeLineHtml(false,
|
||||
diffParser.LINE_TYPE.INSERTS, '', 30, ' test', '+');
|
||||
fileHtml = fileHtml.replace(/\n\n+/g, '\n');
|
||||
var expected = '<tr>\n' +
|
||||
' <td class="d2h-code-linenumber d2h-ins">\n' +
|
||||
' <div class="line-num1"></div>\n' +
|
||||
'<div class="line-num2">30</div>\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-ins">\n' +
|
||||
' <div class="d2h-code-line d2h-ins">\n' +
|
||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"> test</span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr>';
|
||||
|
||||
assert.equal(expected, fileHtml);
|
||||
});
|
||||
|
||||
it('should convert indents into non breakin spaces (4 white spaces)', function() {
|
||||
var diffParser = require('../src/diff-parser.js').DiffParser;
|
||||
var lineByLinePrinter = new LineByLinePrinter({});
|
||||
var fileHtml = lineByLinePrinter.makeLineHtml(false,
|
||||
diffParser.LINE_TYPE.INSERTS, '', 30, ' test', '+');
|
||||
fileHtml = fileHtml.replace(/\n\n+/g, '\n');
|
||||
var expected = '<tr>\n' +
|
||||
' <td class="d2h-code-linenumber d2h-ins">\n' +
|
||||
' <div class="line-num1"></div>\n' +
|
||||
'<div class="line-num2">30</div>\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-ins">\n' +
|
||||
' <div class="d2h-code-line d2h-ins">\n' +
|
||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"> test</span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr>';
|
||||
|
||||
assert.equal(expected, fileHtml);
|
||||
});
|
||||
|
||||
it('should preserve tabs', function() {
|
||||
var diffParser = require('../src/diff-parser.js').DiffParser;
|
||||
var lineByLinePrinter = new LineByLinePrinter({});
|
||||
var fileHtml = lineByLinePrinter.makeLineHtml(false,
|
||||
diffParser.LINE_TYPE.INSERTS, '', 30, '\ttest', '+');
|
||||
fileHtml = fileHtml.replace(/\n\n+/g, '\n');
|
||||
var expected = '<tr>\n' +
|
||||
' <td class="d2h-code-linenumber d2h-ins">\n' +
|
||||
' <div class="line-num1"></div>\n' +
|
||||
'' +
|
||||
'<div class="line-num2">30</div>\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-ins">\n' +
|
||||
' <div class="d2h-code-line d2h-ins">\n' +
|
||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||
' <span class="d2h-code-line-ctn">\ttest</span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr>';
|
||||
|
||||
assert.equal(expected, fileHtml);
|
||||
});
|
||||
});
|
||||
|
||||
describe('makeFileDiffHtml', function() {
|
||||
it('should work for simple file', function() {
|
||||
var lineByLinePrinter = new LineByLinePrinter({});
|
||||
|
||||
var file = {
|
||||
addedLines: 12,
|
||||
deletedLines: 41,
|
||||
language: 'js',
|
||||
oldName: 'my/file/name.js',
|
||||
newName: 'my/file/name.js'
|
||||
};
|
||||
var diffs = '<span>Random Html</span>';
|
||||
|
||||
var fileHtml = lineByLinePrinter.makeFileDiffHtml(file, diffs);
|
||||
|
||||
var expected =
|
||||
'<div id="d2h-781444" class="d2h-file-wrapper" data-lang="js">\n' +
|
||||
' <div class="d2h-file-header">\n' +
|
||||
' <span class="d2h-file-name-wrapper">\n' +
|
||||
' <svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">\n' +
|
||||
' <path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>\n' +
|
||||
' </svg> <span class="d2h-file-name">my/file/name.js</span>\n' +
|
||||
' <span class="d2h-tag d2h-changed d2h-changed-tag">CHANGED</span></span>\n' +
|
||||
' </div>\n' +
|
||||
' <div class="d2h-file-diff">\n' +
|
||||
' <div class="d2h-code-wrapper">\n' +
|
||||
' <table class="d2h-diff-table">\n' +
|
||||
' <tbody class="d2h-diff-tbody">\n' +
|
||||
' <span>Random Html</span>\n' +
|
||||
' </tbody>\n' +
|
||||
' </table>\n' +
|
||||
' </div>\n' +
|
||||
' </div>\n' +
|
||||
'</div>';
|
||||
|
||||
assert.equal(expected, fileHtml);
|
||||
});
|
||||
it('should work for simple added file', function() {
|
||||
var lineByLinePrinter = new LineByLinePrinter({});
|
||||
|
||||
var file = {
|
||||
addedLines: 12,
|
||||
deletedLines: 0,
|
||||
language: 'js',
|
||||
oldName: 'dev/null',
|
||||
newName: 'my/file/name.js',
|
||||
isNew: true
|
||||
};
|
||||
var diffs = '<span>Random Html</span>';
|
||||
|
||||
var fileHtml = lineByLinePrinter.makeFileDiffHtml(file, diffs);
|
||||
|
||||
var expected =
|
||||
'<div id="d2h-781444" class="d2h-file-wrapper" data-lang="js">\n' +
|
||||
' <div class="d2h-file-header">\n' +
|
||||
' <span class="d2h-file-name-wrapper">\n' +
|
||||
' <svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">\n' +
|
||||
' <path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>\n' +
|
||||
' </svg> <span class="d2h-file-name">my/file/name.js</span>\n' +
|
||||
' <span class="d2h-tag d2h-added d2h-added-tag">ADDED</span></span>\n' +
|
||||
' </div>\n' +
|
||||
' <div class="d2h-file-diff">\n' +
|
||||
' <div class="d2h-code-wrapper">\n' +
|
||||
' <table class="d2h-diff-table">\n' +
|
||||
' <tbody class="d2h-diff-tbody">\n' +
|
||||
' <span>Random Html</span>\n' +
|
||||
' </tbody>\n' +
|
||||
' </table>\n' +
|
||||
' </div>\n' +
|
||||
' </div>\n' +
|
||||
'</div>';
|
||||
|
||||
assert.equal(expected, fileHtml);
|
||||
});
|
||||
it('should work for simple deleted file', function() {
|
||||
var lineByLinePrinter = new LineByLinePrinter({});
|
||||
|
||||
var file = {
|
||||
addedLines: 0,
|
||||
deletedLines: 41,
|
||||
language: 'js',
|
||||
oldName: 'my/file/name.js',
|
||||
newName: 'dev/null',
|
||||
isDeleted: true
|
||||
};
|
||||
var diffs = '<span>Random Html</span>';
|
||||
|
||||
var fileHtml = lineByLinePrinter.makeFileDiffHtml(file, diffs);
|
||||
|
||||
var expected =
|
||||
'<div id="d2h-781444" class="d2h-file-wrapper" data-lang="js">\n' +
|
||||
' <div class="d2h-file-header">\n' +
|
||||
' <span class="d2h-file-name-wrapper">\n' +
|
||||
' <svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">\n' +
|
||||
' <path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>\n' +
|
||||
' </svg> <span class="d2h-file-name">my/file/name.js</span>\n' +
|
||||
' <span class="d2h-tag d2h-deleted d2h-deleted-tag">DELETED</span></span>\n' +
|
||||
' </div>\n' +
|
||||
' <div class="d2h-file-diff">\n' +
|
||||
' <div class="d2h-code-wrapper">\n' +
|
||||
' <table class="d2h-diff-table">\n' +
|
||||
' <tbody class="d2h-diff-tbody">\n' +
|
||||
' <span>Random Html</span>\n' +
|
||||
' </tbody>\n' +
|
||||
' </table>\n' +
|
||||
' </div>\n' +
|
||||
' </div>\n' +
|
||||
'</div>';
|
||||
|
||||
assert.equal(expected, fileHtml);
|
||||
});
|
||||
it('should work for simple renamed file', function() {
|
||||
var lineByLinePrinter = new LineByLinePrinter({});
|
||||
|
||||
var file = {
|
||||
addedLines: 12,
|
||||
deletedLines: 41,
|
||||
language: 'js',
|
||||
oldName: 'my/file/name1.js',
|
||||
newName: 'my/file/name2.js',
|
||||
isRename: true
|
||||
};
|
||||
var diffs = '<span>Random Html</span>';
|
||||
|
||||
var fileHtml = lineByLinePrinter.makeFileDiffHtml(file, diffs);
|
||||
|
||||
var expected =
|
||||
'<div id="d2h-662683" class="d2h-file-wrapper" data-lang="js">\n' +
|
||||
' <div class="d2h-file-header">\n' +
|
||||
' <span class="d2h-file-name-wrapper">\n' +
|
||||
' <svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">\n' +
|
||||
' <path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>\n' +
|
||||
' </svg> <span class="d2h-file-name">my/file/{name1.js → name2.js}</span>\n' +
|
||||
' <span class="d2h-tag d2h-moved d2h-moved-tag">RENAMED</span></span>\n' +
|
||||
' </div>\n' +
|
||||
' <div class="d2h-file-diff">\n' +
|
||||
' <div class="d2h-code-wrapper">\n' +
|
||||
' <table class="d2h-diff-table">\n' +
|
||||
' <tbody class="d2h-diff-tbody">\n' +
|
||||
' <span>Random Html</span>\n' +
|
||||
' </tbody>\n' +
|
||||
' </table>\n' +
|
||||
' </div>\n' +
|
||||
' </div>\n' +
|
||||
'</div>';
|
||||
|
||||
assert.equal(expected, fileHtml);
|
||||
});
|
||||
it('should return empty when option renderNothingWhenEmpty is true and file blocks not present', function() {
|
||||
var lineByLinePrinter = new LineByLinePrinter({
|
||||
renderNothingWhenEmpty: true
|
||||
});
|
||||
|
||||
var file = {
|
||||
blocks: []
|
||||
};
|
||||
|
||||
var diffs = '<span>Random Html</span>';
|
||||
|
||||
var fileHtml = lineByLinePrinter.makeFileDiffHtml(file, diffs);
|
||||
|
||||
var expected = '';
|
||||
|
||||
assert.equal(expected, fileHtml);
|
||||
});
|
||||
});
|
||||
|
||||
describe('makeLineByLineHtmlWrapper', function() {
|
||||
it('should work for simple content', function() {
|
||||
var lineByLinePrinter = new LineByLinePrinter({});
|
||||
var fileHtml = lineByLinePrinter.makeLineByLineHtmlWrapper('<span>Random Html</span>');
|
||||
|
||||
var expected =
|
||||
'<div class="d2h-wrapper">\n' +
|
||||
' <span>Random Html</span>\n' +
|
||||
'</div>';
|
||||
|
||||
assert.equal(expected, fileHtml);
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateLineByLineJsonHtml', function() {
|
||||
it('should work for list of files', function() {
|
||||
var exampleJson = [
|
||||
{
|
||||
blocks: [
|
||||
{
|
||||
lines: [
|
||||
{
|
||||
content: '-test',
|
||||
type: 'd2h-del',
|
||||
oldNumber: 1,
|
||||
newNumber: null
|
||||
},
|
||||
{
|
||||
content: '+test1r',
|
||||
type: 'd2h-ins',
|
||||
oldNumber: null,
|
||||
newNumber: 1
|
||||
}
|
||||
],
|
||||
oldStartLine: '1',
|
||||
oldStartLine2: null,
|
||||
newStartLine: '1',
|
||||
header: '@@ -1 +1 @@'
|
||||
}
|
||||
],
|
||||
deletedLines: 1,
|
||||
addedLines: 1,
|
||||
checksumBefore: '0000001',
|
||||
checksumAfter: '0ddf2ba',
|
||||
oldName: 'sample',
|
||||
language: undefined,
|
||||
newName: 'sample',
|
||||
isCombined: false
|
||||
}
|
||||
];
|
||||
|
||||
var lineByLinePrinter = new LineByLinePrinter({matching: 'lines'});
|
||||
var html = lineByLinePrinter.generateLineByLineJsonHtml(exampleJson);
|
||||
var expected =
|
||||
'<div class="d2h-wrapper">\n' +
|
||||
' <div id="d2h-675094" class="d2h-file-wrapper" data-lang="">\n' +
|
||||
' <div class="d2h-file-header">\n' +
|
||||
' <span class="d2h-file-name-wrapper">\n' +
|
||||
' <svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">\n' +
|
||||
' <path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>\n' +
|
||||
' </svg> <span class="d2h-file-name">sample</span>\n' +
|
||||
' <span class="d2h-tag d2h-changed d2h-changed-tag">CHANGED</span></span>\n' +
|
||||
' </div>\n' +
|
||||
' <div class="d2h-file-diff">\n' +
|
||||
' <div class="d2h-code-wrapper">\n' +
|
||||
' <table class="d2h-diff-table">\n' +
|
||||
' <tbody class="d2h-diff-tbody">\n' +
|
||||
' <tr>\n' +
|
||||
' <td class="d2h-code-linenumber d2h-info"></td>\n' +
|
||||
' <td class="d2h-info">\n' +
|
||||
' <div class="d2h-code-line d2h-info">@@ -1 +1 @@</div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
' <td class="d2h-code-linenumber d2h-del">\n' +
|
||||
' <div class="line-num1">1</div>\n' +
|
||||
'<div class="line-num2"></div>\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-del">\n' +
|
||||
' <div class="d2h-code-line d2h-del">\n' +
|
||||
' <span class="d2h-code-line-prefix">-</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"><del>test</del></span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
' <td class="d2h-code-linenumber d2h-ins">\n' +
|
||||
' <div class="line-num1"></div>\n' +
|
||||
'<div class="line-num2">1</div>\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-ins">\n' +
|
||||
' <div class="d2h-code-line d2h-ins">\n' +
|
||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"><ins>test1r</ins></span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr>\n' +
|
||||
' </tbody>\n' +
|
||||
' </table>\n' +
|
||||
' </div>\n' +
|
||||
' </div>\n' +
|
||||
'</div>\n' +
|
||||
'</div>';
|
||||
|
||||
assert.equal(expected, html);
|
||||
});
|
||||
|
||||
it('should work for empty blocks', function() {
|
||||
var exampleJson = [{
|
||||
blocks: [],
|
||||
deletedLines: 0,
|
||||
addedLines: 0,
|
||||
oldName: 'sample',
|
||||
language: 'js',
|
||||
newName: 'sample',
|
||||
isCombined: false
|
||||
}];
|
||||
|
||||
var lineByLinePrinter = new LineByLinePrinter({ renderNothingWhenEmpty: false });
|
||||
var html = lineByLinePrinter.generateLineByLineJsonHtml(exampleJson);
|
||||
var expected =
|
||||
'<div class="d2h-wrapper">\n' +
|
||||
' <div id="d2h-675094" class="d2h-file-wrapper" data-lang="js">\n' +
|
||||
' <div class="d2h-file-header">\n' +
|
||||
' <span class="d2h-file-name-wrapper">\n' +
|
||||
' <svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">\n' +
|
||||
' <path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>\n' +
|
||||
' </svg> <span class="d2h-file-name">sample</span>\n' +
|
||||
' <span class="d2h-tag d2h-changed d2h-changed-tag">CHANGED</span></span>\n' +
|
||||
' </div>\n' +
|
||||
' <div class="d2h-file-diff">\n' +
|
||||
' <div class="d2h-code-wrapper">\n' +
|
||||
' <table class="d2h-diff-table">\n' +
|
||||
' <tbody class="d2h-diff-tbody">\n' +
|
||||
' <tr>\n' +
|
||||
' <td class="d2h-info">\n' +
|
||||
' <div class="d2h-code-line d2h-info">\n' +
|
||||
' File without changes\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr>\n' +
|
||||
' </tbody>\n' +
|
||||
' </table>\n' +
|
||||
' </div>\n' +
|
||||
' </div>\n' +
|
||||
'</div>\n' +
|
||||
'</div>';
|
||||
|
||||
assert.equal(expected, html);
|
||||
});
|
||||
});
|
||||
|
||||
describe('_processLines', function() {
|
||||
it('should work for simple block header', function() {
|
||||
var lineByLinePrinter = new LineByLinePrinter({});
|
||||
var oldLines = [{
|
||||
content: '-test',
|
||||
type: 'd2h-del',
|
||||
oldNumber: 1,
|
||||
newNumber: null
|
||||
}];
|
||||
var newLines = [{
|
||||
content: '+test1r',
|
||||
type: 'd2h-ins',
|
||||
oldNumber: null,
|
||||
newNumber: 1
|
||||
}];
|
||||
|
||||
var html = lineByLinePrinter._processLines(false, oldLines, newLines);
|
||||
|
||||
var expected =
|
||||
'<tr>\n' +
|
||||
' <td class="d2h-code-linenumber d2h-del">\n' +
|
||||
' <div class="line-num1">1</div>\n' +
|
||||
'<div class="line-num2"></div>\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-del">\n' +
|
||||
' <div class="d2h-code-line d2h-del">\n' +
|
||||
' <span class="d2h-code-line-prefix">-</span>\n' +
|
||||
' <span class="d2h-code-line-ctn">test</span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
' <td class="d2h-code-linenumber d2h-ins">\n' +
|
||||
' <div class="line-num1"></div>\n' +
|
||||
'<div class="line-num2">1</div>\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-ins">\n' +
|
||||
' <div class="d2h-code-line d2h-ins">\n' +
|
||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||
' <span class="d2h-code-line-ctn">test1r</span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr>';
|
||||
|
||||
assert.equal(expected, html);
|
||||
});
|
||||
});
|
||||
|
||||
describe('_generateFileHtml', function() {
|
||||
it('should work for simple file', function() {
|
||||
var lineByLinePrinter = new LineByLinePrinter({});
|
||||
var file = {
|
||||
blocks: [
|
||||
{
|
||||
lines: [
|
||||
{
|
||||
content: ' one context line',
|
||||
type: 'd2h-cntx',
|
||||
oldNumber: 1,
|
||||
newNumber: 1
|
||||
},
|
||||
{
|
||||
content: '-test',
|
||||
type: 'd2h-del',
|
||||
oldNumber: 2,
|
||||
newNumber: null
|
||||
},
|
||||
{
|
||||
content: '+test1r',
|
||||
type: 'd2h-ins',
|
||||
oldNumber: null,
|
||||
newNumber: 2
|
||||
},
|
||||
{
|
||||
content: '+test2r',
|
||||
type: 'd2h-ins',
|
||||
oldNumber: null,
|
||||
newNumber: 3
|
||||
}
|
||||
],
|
||||
oldStartLine: '1',
|
||||
oldStartLine2: null,
|
||||
newStartLine: '1',
|
||||
header: '@@ -1 +1 @@'
|
||||
}
|
||||
],
|
||||
deletedLines: 1,
|
||||
addedLines: 1,
|
||||
checksumBefore: '0000001',
|
||||
checksumAfter: '0ddf2ba',
|
||||
oldName: 'sample',
|
||||
language: undefined,
|
||||
newName: 'sample',
|
||||
isCombined: false
|
||||
};
|
||||
|
||||
var html = lineByLinePrinter._generateFileHtml(file);
|
||||
|
||||
var expected =
|
||||
'<tr>\n' +
|
||||
' <td class="d2h-code-linenumber d2h-info"></td>\n' +
|
||||
' <td class="d2h-info">\n' +
|
||||
' <div class="d2h-code-line d2h-info">@@ -1 +1 @@</div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
' <td class="d2h-code-linenumber d2h-cntx">\n' +
|
||||
' <div class="line-num1">1</div>\n' +
|
||||
'<div class="line-num2">1</div>\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-cntx">\n' +
|
||||
' <div class="d2h-code-line d2h-cntx">\n' +
|
||||
' <span class="d2h-code-line-prefix"> </span>\n' +
|
||||
' <span class="d2h-code-line-ctn">one context line</span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
' <td class="d2h-code-linenumber d2h-del">\n' +
|
||||
' <div class="line-num1">2</div>\n' +
|
||||
'<div class="line-num2"></div>\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-del">\n' +
|
||||
' <div class="d2h-code-line d2h-del">\n' +
|
||||
' <span class="d2h-code-line-prefix">-</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"><del>test</del></span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
' <td class="d2h-code-linenumber d2h-ins">\n' +
|
||||
' <div class="line-num1"></div>\n' +
|
||||
'<div class="line-num2">2</div>\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-ins">\n' +
|
||||
' <div class="d2h-code-line d2h-ins">\n' +
|
||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"><ins>test1r</ins></span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
' <td class="d2h-code-linenumber d2h-ins">\n' +
|
||||
' <div class="line-num1"></div>\n' +
|
||||
'<div class="line-num2">3</div>\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-ins">\n' +
|
||||
' <div class="d2h-code-line d2h-ins">\n' +
|
||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||
' <span class="d2h-code-line-ctn">test2r</span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr>';
|
||||
|
||||
assert.equal(expected, html);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
var assert = require('assert');
|
||||
|
||||
var PrinterUtils = require('../src/printer-utils.js').PrinterUtils;
|
||||
|
||||
describe('Utils', function() {
|
||||
describe('getHtmlId', function() {
|
||||
it('should generate file unique id', function() {
|
||||
var result = PrinterUtils.getHtmlId({
|
||||
oldName: 'sample.js',
|
||||
newName: 'sample.js'
|
||||
});
|
||||
assert.equal('d2h-960013', result);
|
||||
});
|
||||
it('should generate file unique id for empty hashes', function() {
|
||||
var result = PrinterUtils.getHtmlId({
|
||||
oldName: 'sample.js',
|
||||
newName: 'sample.js'
|
||||
});
|
||||
assert.equal('d2h-960013', result);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDiffName', function() {
|
||||
it('should generate the file name for a changed file', function() {
|
||||
var result = PrinterUtils.getDiffName({
|
||||
oldName: 'sample.js',
|
||||
newName: 'sample.js'
|
||||
});
|
||||
assert.equal('sample.js', result);
|
||||
});
|
||||
it('should generate the file name for a changed file and full rename', function() {
|
||||
var result = PrinterUtils.getDiffName({
|
||||
oldName: 'sample1.js',
|
||||
newName: 'sample2.js'
|
||||
});
|
||||
assert.equal('sample1.js → sample2.js', result);
|
||||
});
|
||||
it('should generate the file name for a changed file and prefix rename', function() {
|
||||
var result = PrinterUtils.getDiffName({
|
||||
oldName: 'src/path/sample.js',
|
||||
newName: 'source/path/sample.js'
|
||||
});
|
||||
assert.equal('{src → source}/path/sample.js', result);
|
||||
});
|
||||
it('should generate the file name for a changed file and suffix rename', function() {
|
||||
var result = PrinterUtils.getDiffName({
|
||||
oldName: 'src/path/sample1.js',
|
||||
newName: 'src/path/sample2.js'
|
||||
});
|
||||
assert.equal('src/path/{sample1.js → sample2.js}', result);
|
||||
});
|
||||
it('should generate the file name for a changed file and middle rename', function() {
|
||||
var result = PrinterUtils.getDiffName({
|
||||
oldName: 'src/really/big/path/sample.js',
|
||||
newName: 'src/small/path/sample.js'
|
||||
});
|
||||
assert.equal('src/{really/big → small}/path/sample.js', result);
|
||||
});
|
||||
it('should generate the file name for a deleted file', function() {
|
||||
var result = PrinterUtils.getDiffName({
|
||||
oldName: 'src/my/file.js',
|
||||
newName: '/dev/null'
|
||||
});
|
||||
assert.equal('src/my/file.js', result);
|
||||
});
|
||||
it('should generate the file name for a new file', function() {
|
||||
var result = PrinterUtils.getDiffName({
|
||||
oldName: '/dev/null',
|
||||
newName: 'src/my/file.js'
|
||||
});
|
||||
assert.equal('src/my/file.js', result);
|
||||
});
|
||||
it('should generate handle undefined filename', function() {
|
||||
var result = PrinterUtils.getDiffName({});
|
||||
assert.equal('unknown/file/path', result);
|
||||
});
|
||||
});
|
||||
|
||||
describe('diffHighlight', function() {
|
||||
it('should highlight two lines', function() {
|
||||
var result = PrinterUtils.diffHighlight(
|
||||
'-var myVar = 2;',
|
||||
'+var myVariable = 3;',
|
||||
{matching: 'words'}
|
||||
);
|
||||
|
||||
assert.deepEqual({
|
||||
first: {
|
||||
prefix: '-',
|
||||
line: 'var <del>myVar</del> = <del>2</del>;'
|
||||
},
|
||||
second: {
|
||||
prefix: '+',
|
||||
line: 'var <ins>myVariable</ins> = <ins>3</ins>;'
|
||||
}
|
||||
}, result);
|
||||
});
|
||||
it('should highlight two lines char by char', function() {
|
||||
var result = PrinterUtils.diffHighlight(
|
||||
'-var myVar = 2;',
|
||||
'+var myVariable = 3;',
|
||||
{ diffStyle: 'char' }
|
||||
);
|
||||
|
||||
assert.deepEqual({
|
||||
first: {
|
||||
prefix: '-',
|
||||
line: 'var myVar = <del>2</del>;'
|
||||
},
|
||||
second: {
|
||||
prefix: '+',
|
||||
line: 'var myVar<ins>iable</ins> = <ins>3</ins>;'
|
||||
}
|
||||
}, result);
|
||||
});
|
||||
it('should highlight combined diff lines', function() {
|
||||
var result = PrinterUtils.diffHighlight(
|
||||
' -var myVar = 2;',
|
||||
' +var myVariable = 3;',
|
||||
{
|
||||
diffStyle: 'word',
|
||||
isCombined: true,
|
||||
matching: 'words',
|
||||
matchWordsThreshold: 1.00
|
||||
}
|
||||
);
|
||||
|
||||
assert.deepEqual({
|
||||
first: {
|
||||
prefix: ' -',
|
||||
line: 'var <del class="d2h-change">myVar</del> = <del class="d2h-change">2</del>;'
|
||||
},
|
||||
second: {
|
||||
prefix: ' +',
|
||||
line: 'var <ins class="d2h-change">myVariable</ins> = <ins class="d2h-change">3</ins>;'
|
||||
}
|
||||
}, result);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,404 +0,0 @@
|
|||
var assert = require('assert');
|
||||
|
||||
var SideBySidePrinter = require('../src/side-by-side-printer.js').SideBySidePrinter;
|
||||
|
||||
describe('SideBySidePrinter', function() {
|
||||
describe('generateEmptyDiff', function() {
|
||||
it('should return an empty diff', function() {
|
||||
var sideBySidePrinter = new SideBySidePrinter({});
|
||||
var fileHtml = sideBySidePrinter.generateEmptyDiff();
|
||||
var expectedRight = '';
|
||||
var expectedLeft = '<tr>\n' +
|
||||
' <td class="d2h-info">\n' +
|
||||
' <div class="d2h-code-side-line d2h-info">\n' +
|
||||
' File without changes\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr>';
|
||||
|
||||
assert.equal(expectedRight, fileHtml.right);
|
||||
assert.equal(expectedLeft, fileHtml.left);
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateSideBySideFileHtml', function() {
|
||||
it('should generate lines with the right prefixes', function() {
|
||||
var sideBySidePrinter = new SideBySidePrinter({});
|
||||
|
||||
var file = {
|
||||
'blocks': [
|
||||
{
|
||||
'lines': [
|
||||
{
|
||||
'content': ' context',
|
||||
'type': 'd2h-cntx',
|
||||
'oldNumber': 19,
|
||||
'newNumber': 19
|
||||
},
|
||||
{
|
||||
'content': '-removed',
|
||||
'type': 'd2h-del',
|
||||
'oldNumber': 20,
|
||||
'newNumber': null
|
||||
},
|
||||
{
|
||||
'content': '+added',
|
||||
'type': 'd2h-ins',
|
||||
'oldNumber': null,
|
||||
'newNumber': 20
|
||||
},
|
||||
{
|
||||
'content': '+another added',
|
||||
'type': 'd2h-ins',
|
||||
'oldNumber': null,
|
||||
'newNumber': 21
|
||||
}
|
||||
],
|
||||
'oldStartLine': '19',
|
||||
'newStartLine': '19',
|
||||
'header': '@@ -19,7 +19,7 @@'
|
||||
}
|
||||
],
|
||||
'deletedLines': 1,
|
||||
'addedLines': 1,
|
||||
'checksumBefore': 'fc56817',
|
||||
'checksumAfter': 'e8e7e49',
|
||||
'mode': '100644',
|
||||
'oldName': 'coverage.init',
|
||||
'language': 'init',
|
||||
'newName': 'coverage.init',
|
||||
'isCombined': false
|
||||
};
|
||||
|
||||
var fileHtml = sideBySidePrinter.generateSideBySideFileHtml(file);
|
||||
|
||||
var expectedLeft =
|
||||
'<tr>\n' +
|
||||
' <td class="d2h-code-side-linenumber d2h-info"></td>\n' +
|
||||
' <td class="d2h-info">\n' +
|
||||
' <div class="d2h-code-side-line d2h-info">@@ -19,7 +19,7 @@</div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
' <td class="d2h-code-side-linenumber d2h-cntx">\n' +
|
||||
' 19\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-cntx">\n' +
|
||||
' <div class="d2h-code-side-line d2h-cntx">\n' +
|
||||
' <span class="d2h-code-line-prefix"> </span>\n' +
|
||||
' <span class="d2h-code-line-ctn">context</span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
' <td class="d2h-code-side-linenumber d2h-del">\n' +
|
||||
' 20\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-del">\n' +
|
||||
' <div class="d2h-code-side-line d2h-del">\n' +
|
||||
' <span class="d2h-code-line-prefix">-</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"><del>removed</del></span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
' <td class="d2h-code-side-linenumber d2h-code-side-emptyplaceholder d2h-cntx d2h-emptyplaceholder">\n' +
|
||||
' ' +
|
||||
'\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-cntx d2h-emptyplaceholder">\n' +
|
||||
' <div class="d2h-code-side-line d2h-code-side-emptyplaceholder d2h-cntx d2h-emptyplaceholder">\n' +
|
||||
' <span class="d2h-code-line-prefix"> </span>\n' +
|
||||
' <span class="d2h-code-line-ctn"> </span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr>';
|
||||
|
||||
var expectedRight =
|
||||
'<tr>\n' +
|
||||
' <td class="d2h-code-side-linenumber d2h-info"></td>\n' +
|
||||
' <td class="d2h-info">\n' +
|
||||
' <div class="d2h-code-side-line d2h-info"></div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
' <td class="d2h-code-side-linenumber d2h-cntx">\n' +
|
||||
' 19\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-cntx">\n' +
|
||||
' <div class="d2h-code-side-line d2h-cntx">\n' +
|
||||
' <span class="d2h-code-line-prefix"> </span>\n' +
|
||||
' <span class="d2h-code-line-ctn">context</span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
' <td class="d2h-code-side-linenumber d2h-ins">\n' +
|
||||
' 20\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-ins">\n' +
|
||||
' <div class="d2h-code-side-line d2h-ins">\n' +
|
||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"><ins>added</ins></span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
' <td class="d2h-code-side-linenumber d2h-ins">\n' +
|
||||
' 21\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-ins">\n' +
|
||||
' <div class="d2h-code-side-line d2h-ins">\n' +
|
||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||
' <span class="d2h-code-line-ctn">another added</span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr>';
|
||||
|
||||
assert.equal(expectedLeft, fileHtml.left);
|
||||
assert.equal(expectedRight, fileHtml.right);
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateSingleLineHtml', function() {
|
||||
it('should work for insertions', function() {
|
||||
var diffParser = require('../src/diff-parser.js').DiffParser;
|
||||
var sideBySidePrinter = new SideBySidePrinter({});
|
||||
var fileHtml = sideBySidePrinter.generateSingleLineHtml(false,
|
||||
diffParser.LINE_TYPE.INSERTS, 30, 'test', '+');
|
||||
var expected = '<tr>\n' +
|
||||
' <td class="d2h-code-side-linenumber d2h-ins">\n' +
|
||||
' 30\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-ins">\n' +
|
||||
' <div class="d2h-code-side-line d2h-ins">\n' +
|
||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||
' <span class="d2h-code-line-ctn">test</span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr>';
|
||||
|
||||
assert.equal(expected, fileHtml);
|
||||
});
|
||||
it('should work for deletions', function() {
|
||||
var diffParser = require('../src/diff-parser.js').DiffParser;
|
||||
var sideBySidePrinter = new SideBySidePrinter({});
|
||||
var fileHtml = sideBySidePrinter.generateSingleLineHtml(false,
|
||||
diffParser.LINE_TYPE.DELETES, 30, 'test', '-');
|
||||
var expected = '<tr>\n' +
|
||||
' <td class="d2h-code-side-linenumber d2h-del">\n' +
|
||||
' 30\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-del">\n' +
|
||||
' <div class="d2h-code-side-line d2h-del">\n' +
|
||||
' <span class="d2h-code-line-prefix">-</span>\n' +
|
||||
' <span class="d2h-code-line-ctn">test</span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr>';
|
||||
|
||||
assert.equal(expected, fileHtml);
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateSideBySideJsonHtml', function() {
|
||||
it('should work for list of files', function() {
|
||||
var exampleJson = [
|
||||
{
|
||||
blocks: [
|
||||
{
|
||||
lines: [
|
||||
{
|
||||
content: '-test',
|
||||
type: 'd2h-del',
|
||||
oldNumber: 1,
|
||||
newNumber: null
|
||||
},
|
||||
{
|
||||
content: '+test1r',
|
||||
type: 'd2h-ins',
|
||||
oldNumber: null,
|
||||
newNumber: 1
|
||||
}
|
||||
],
|
||||
oldStartLine: '1',
|
||||
oldStartLine2: null,
|
||||
newStartLine: '1',
|
||||
header: '@@ -1 +1 @@'
|
||||
}
|
||||
],
|
||||
deletedLines: 1,
|
||||
addedLines: 1,
|
||||
checksumBefore: '0000001',
|
||||
checksumAfter: '0ddf2ba',
|
||||
oldName: 'sample',
|
||||
language: undefined,
|
||||
newName: 'sample',
|
||||
isCombined: false
|
||||
}
|
||||
];
|
||||
|
||||
var sideBySidePrinter = new SideBySidePrinter({matching: 'lines'});
|
||||
var html = sideBySidePrinter.generateSideBySideJsonHtml(exampleJson);
|
||||
var expected =
|
||||
'<div class="d2h-wrapper">\n' +
|
||||
' <div id="d2h-675094" class="d2h-file-wrapper" data-lang="">\n' +
|
||||
' <div class="d2h-file-header">\n' +
|
||||
' <span class="d2h-file-name-wrapper">\n' +
|
||||
' <svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">\n' +
|
||||
' <path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>\n' +
|
||||
' </svg> <span class="d2h-file-name">sample</span>\n' +
|
||||
' <span class="d2h-tag d2h-changed d2h-changed-tag">CHANGED</span></span>\n' +
|
||||
' </div>\n' +
|
||||
' <div class="d2h-files-diff">\n' +
|
||||
' <div class="d2h-file-side-diff">\n' +
|
||||
' <div class="d2h-code-wrapper">\n' +
|
||||
' <table class="d2h-diff-table">\n' +
|
||||
' <tbody class="d2h-diff-tbody">\n' +
|
||||
' <tr>\n' +
|
||||
' <td class="d2h-code-side-linenumber d2h-info"></td>\n' +
|
||||
' <td class="d2h-info">\n' +
|
||||
' <div class="d2h-code-side-line d2h-info">@@ -1 +1 @@</div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
' <td class="d2h-code-side-linenumber d2h-del">\n' +
|
||||
' 1\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-del">\n' +
|
||||
' <div class="d2h-code-side-line d2h-del">\n' +
|
||||
' <span class="d2h-code-line-prefix">-</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"><del>test</del></span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr>\n' +
|
||||
' </tbody>\n' +
|
||||
' </table>\n' +
|
||||
' </div>\n' +
|
||||
' </div>\n' +
|
||||
' <div class="d2h-file-side-diff">\n' +
|
||||
' <div class="d2h-code-wrapper">\n' +
|
||||
' <table class="d2h-diff-table">\n' +
|
||||
' <tbody class="d2h-diff-tbody">\n' +
|
||||
' <tr>\n' +
|
||||
' <td class="d2h-code-side-linenumber d2h-info"></td>\n' +
|
||||
' <td class="d2h-info">\n' +
|
||||
' <div class="d2h-code-side-line d2h-info"></div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr><tr>\n' +
|
||||
' <td class="d2h-code-side-linenumber d2h-ins">\n' +
|
||||
' 1\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-ins">\n' +
|
||||
' <div class="d2h-code-side-line d2h-ins">\n' +
|
||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"><ins>test1r</ins></span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr>\n' +
|
||||
' </tbody>\n' +
|
||||
' </table>\n' +
|
||||
' </div>\n' +
|
||||
' </div>\n' +
|
||||
' </div>\n' +
|
||||
'</div>\n' +
|
||||
'</div>';
|
||||
|
||||
assert.equal(expected, html);
|
||||
});
|
||||
it('should work for files without blocks', function() {
|
||||
var exampleJson = [{
|
||||
blocks: [],
|
||||
oldName: 'sample',
|
||||
language: 'js',
|
||||
newName: 'sample',
|
||||
isCombined: false
|
||||
}];
|
||||
|
||||
var sideBySidePrinter = new SideBySidePrinter();
|
||||
var html = sideBySidePrinter.generateSideBySideJsonHtml(exampleJson);
|
||||
var expected =
|
||||
'<div class="d2h-wrapper">\n' +
|
||||
' <div id="d2h-675094" class="d2h-file-wrapper" data-lang="js">\n' +
|
||||
' <div class="d2h-file-header">\n' +
|
||||
' <span class="d2h-file-name-wrapper">\n' +
|
||||
' <svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">\n' +
|
||||
' <path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>\n' +
|
||||
' </svg> <span class="d2h-file-name">sample</span>\n' +
|
||||
' <span class="d2h-tag d2h-changed d2h-changed-tag">CHANGED</span></span>\n' +
|
||||
' </div>\n' +
|
||||
' <div class="d2h-files-diff">\n' +
|
||||
' <div class="d2h-file-side-diff">\n' +
|
||||
' <div class="d2h-code-wrapper">\n' +
|
||||
' <table class="d2h-diff-table">\n' +
|
||||
' <tbody class="d2h-diff-tbody">\n' +
|
||||
' <tr>\n' +
|
||||
' <td class="d2h-info">\n' +
|
||||
' <div class="d2h-code-side-line d2h-info">\n' +
|
||||
' File without changes\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr>\n' +
|
||||
' </tbody>\n' +
|
||||
' </table>\n' +
|
||||
' </div>\n' +
|
||||
' </div>\n' +
|
||||
' <div class="d2h-file-side-diff">\n' +
|
||||
' <div class="d2h-code-wrapper">\n' +
|
||||
' <table class="d2h-diff-table">\n' +
|
||||
' <tbody class="d2h-diff-tbody">\n' +
|
||||
' \n' +
|
||||
' </tbody>\n' +
|
||||
' </table>\n' +
|
||||
' </div>\n' +
|
||||
' </div>\n' +
|
||||
' </div>\n' +
|
||||
'</div>\n' +
|
||||
'</div>';
|
||||
|
||||
assert.equal(expected, html);
|
||||
});
|
||||
});
|
||||
|
||||
describe('processLines', function() {
|
||||
it('should process file lines', function() {
|
||||
var oldLines = [{
|
||||
content: '-test',
|
||||
type: 'd2h-del',
|
||||
oldNumber: 1,
|
||||
newNumber: null
|
||||
}];
|
||||
|
||||
var newLines = [{
|
||||
content: '+test1r',
|
||||
type: 'd2h-ins',
|
||||
oldNumber: null,
|
||||
newNumber: 1
|
||||
}];
|
||||
|
||||
var sideBySidePrinter = new SideBySidePrinter({matching: 'lines'});
|
||||
var html = sideBySidePrinter.processLines(false, oldLines, newLines);
|
||||
var expectedLeft =
|
||||
'<tr>\n' +
|
||||
' <td class="d2h-code-side-linenumber d2h-del">\n' +
|
||||
' 1\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-del">\n' +
|
||||
' <div class="d2h-code-side-line d2h-del">\n' +
|
||||
' <span class="d2h-code-line-prefix">-</span>\n' +
|
||||
' <span class="d2h-code-line-ctn">test</span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr>';
|
||||
|
||||
var expectedRight =
|
||||
'<tr>\n' +
|
||||
' <td class="d2h-code-side-linenumber d2h-ins">\n' +
|
||||
' 1\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-ins">\n' +
|
||||
' <div class="d2h-code-side-line d2h-ins">\n' +
|
||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||
' <span class="d2h-code-line-ctn">test1r</span>\n' +
|
||||
' </div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr>';
|
||||
|
||||
assert.equal(expectedLeft, html.left);
|
||||
assert.equal(expectedRight, html.right);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
var assert = require('assert');
|
||||
|
||||
var Utils = require('../src/utils.js').Utils;
|
||||
|
||||
describe('Utils', function() {
|
||||
describe('escape', function() {
|
||||
it('should escape & with &', function() {
|
||||
var result = Utils.escape('&');
|
||||
assert.equal('&', result);
|
||||
});
|
||||
it('should escape < with <', function() {
|
||||
var result = Utils.escape('<');
|
||||
assert.equal('<', result);
|
||||
});
|
||||
it('should escape > with >', function() {
|
||||
var result = Utils.escape('>');
|
||||
assert.equal('>', result);
|
||||
});
|
||||
it('should escape a string with multiple problematic characters', function() {
|
||||
var result = Utils.escape('<a href="#">\tlink text</a>');
|
||||
var expected = '<a href="#">\tlink text</a>';
|
||||
assert.equal(expected, result);
|
||||
});
|
||||
});
|
||||
});
|
||||
28
tsconfig.json
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "./_target",
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"lib": ["es2015", "dom"],
|
||||
"allowJs": false,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"strictNullChecks": true,
|
||||
"removeComments": true,
|
||||
"preserveConstEnums": true,
|
||||
"sourceMap": true,
|
||||
"alwaysStrict": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"noUnusedParameters": true,
|
||||
"noUnusedLocals": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"include": ["./src/**/*", "./typings/**/*"],
|
||||
"exclude": ["node_modules", "./src/__tests__/*"]
|
||||
}
|
||||
3
typings/merge.d.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
declare module "merge" {
|
||||
export function recursive(clone: boolean, ...items: object[]): object;
|
||||
}
|
||||
60
webpack.bundles.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import path from "path";
|
||||
|
||||
import webpack from "webpack";
|
||||
|
||||
const minimize = process.env.WEBPACK_MINIFY === "true";
|
||||
|
||||
const diff2htmlBrowserConfig: webpack.Configuration = {
|
||||
mode: "production",
|
||||
optimization: { minimize: minimize },
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: "ts-loader",
|
||||
exclude: /node_modules/
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".tsx", ".ts", ".jsx", ".js"]
|
||||
},
|
||||
entry: "./src/diff2html.ts",
|
||||
output: {
|
||||
path: path.resolve(__dirname, "bundles/js"),
|
||||
libraryTarget: "umd",
|
||||
globalObject: "this",
|
||||
library: "Diff2Html",
|
||||
filename: `diff2html${minimize ? ".min" : ""}.js`,
|
||||
umdNamedDefine: true
|
||||
}
|
||||
};
|
||||
|
||||
const diff2htmlUIBrowserConfig: webpack.Configuration = {
|
||||
mode: "production",
|
||||
optimization: { minimize: minimize },
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: "ts-loader",
|
||||
exclude: /node_modules/
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".tsx", ".ts", ".jsx", ".js"]
|
||||
},
|
||||
entry: "./src/ui/js/diff2html-ui.ts",
|
||||
output: {
|
||||
path: path.resolve(__dirname, "bundles/js"),
|
||||
libraryTarget: "umd",
|
||||
globalObject: "this",
|
||||
filename: `diff2html-ui${minimize ? ".min" : ""}.js`,
|
||||
umdNamedDefine: true
|
||||
}
|
||||
};
|
||||
|
||||
const config: webpack.Configuration[] = [diff2htmlBrowserConfig, diff2htmlUIBrowserConfig];
|
||||
|
||||
export default config;
|
||||
170
webpack.website.ts
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
import path from "path";
|
||||
|
||||
import webpack from "webpack";
|
||||
import { Plugin } from "postcss";
|
||||
import HtmlWebpackPlugin from "html-webpack-plugin";
|
||||
import MiniCssExtractPlugin from "mini-css-extract-plugin";
|
||||
import autoprefixer from "autoprefixer";
|
||||
import CopyWebpackPlugin from "copy-webpack-plugin";
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV !== "production";
|
||||
const minimize = process.env.WEBPACK_MINIFY === "true";
|
||||
|
||||
const pages = ["index", "demo"];
|
||||
|
||||
const config: webpack.Configuration[] = pages.map(page => {
|
||||
return {
|
||||
devServer: {
|
||||
port: 3000,
|
||||
open: true,
|
||||
contentBase: path.join(__dirname, "./website")
|
||||
},
|
||||
entry: {
|
||||
[page]: `./website/templates/pages/${page}/${page}.ts`
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, "./docs")
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".tsx", ".ts", ".jsx", ".js"]
|
||||
},
|
||||
optimization: { minimize },
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: "ts-loader",
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
test: /\.handlebars$/,
|
||||
loader: "handlebars-loader",
|
||||
options: {
|
||||
precompileOptions: {
|
||||
knownHelpersOnly: false
|
||||
},
|
||||
helperDirs: [path.join(__dirname, "website/templates/helpers")],
|
||||
partialDirs: [path.join(__dirname, "website/templates")]
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.(css)$/,
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
{
|
||||
loader: "css-loader",
|
||||
options: {
|
||||
sourceMap: isDevelopment
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: "postcss-loader",
|
||||
options: {
|
||||
autoprefixer: {
|
||||
browsers: ["last 2 versions"]
|
||||
},
|
||||
sourceMap: isDevelopment,
|
||||
plugins: (): Plugin<object>[] => [autoprefixer]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.(html)$/,
|
||||
use: {
|
||||
loader: "html-loader",
|
||||
options: {
|
||||
attrs: ["img:src"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||
use: [
|
||||
{
|
||||
loader: "url-loader",
|
||||
options: {
|
||||
limit: 1000,
|
||||
mimetype: "application/font-woff"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||
loader: "file-loader"
|
||||
},
|
||||
{
|
||||
test: /\.(jpeg|jpg|png|gif)$/,
|
||||
use: [
|
||||
{
|
||||
loader: "file-loader",
|
||||
options: {
|
||||
name: "[name].[ext]",
|
||||
outputPath: "images/",
|
||||
useRelativePath: true
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: "image-webpack-loader",
|
||||
options: {
|
||||
mozjpeg: {
|
||||
progressive: true,
|
||||
quality: 65
|
||||
},
|
||||
optipng: {
|
||||
enabled: true
|
||||
},
|
||||
pngquant: {
|
||||
quality: [0.65, 0.9],
|
||||
speed: 4
|
||||
},
|
||||
gifsicle: {
|
||||
interlaced: false
|
||||
},
|
||||
webp: {
|
||||
quality: 75
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new MiniCssExtractPlugin({
|
||||
filename: "[name].css",
|
||||
chunkFilename: "[id].css"
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
hash: true,
|
||||
inject: true,
|
||||
title: `${page} page`,
|
||||
filename: `${page}.html`,
|
||||
template: `./website/templates/pages/${page}/${page}.handlebars`,
|
||||
minify: !isDevelopment && {
|
||||
html5: true,
|
||||
collapseWhitespace: true,
|
||||
caseSensitive: true,
|
||||
removeEmptyElements: false,
|
||||
removeComments: true,
|
||||
removeRedundantAttributes: true,
|
||||
useShortDoctype: true,
|
||||
removeEmptyAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
keepClosingSlash: true,
|
||||
minifyJS: true,
|
||||
minifyCSS: true,
|
||||
minifyURLs: true
|
||||
}
|
||||
}),
|
||||
new CopyWebpackPlugin([
|
||||
{ from: "website/favicon.ico", to: "favicon.ico" },
|
||||
{ from: "website/robots.txt", to: "robots.txt" },
|
||||
{ from: "website/sitemap.xml", to: "sitemap.xml" }
|
||||
])
|
||||
]
|
||||
};
|
||||
});
|
||||
|
||||
export default config;
|
||||
|
|
@ -1 +0,0 @@
|
|||
diff2html.xyz
|
||||
|
Before Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 121 KiB |
|
|
@ -255,94 +255,6 @@
|
|||
|
||||
}
|
||||
|
||||
.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:last-child, .screenshots-fan .screenshot:first-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;
|
||||
|
|
|
|||
1
website/main.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
import "bootstrap/dist/css/bootstrap.css";
|
||||
15
website/templates/helpers/block.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import handlebars, { HelperOptions } from "handlebars";
|
||||
|
||||
const loadPartial = <T>(name: string): handlebars.Template<T> => {
|
||||
let partial = handlebars.partials[name];
|
||||
if (typeof partial === "string") {
|
||||
partial = handlebars.compile(partial);
|
||||
handlebars.partials[name] = partial;
|
||||
}
|
||||
return partial;
|
||||
};
|
||||
|
||||
export default (name: string, options: HelperOptions): string => {
|
||||
const partial = loadPartial(name) || options.fn;
|
||||
return partial(this, { data: options.hash });
|
||||
};
|
||||
5
website/templates/helpers/partial.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import handlebars, { HelperOptions } from "handlebars";
|
||||
|
||||
export default (name: string, options: HelperOptions): void => {
|
||||
handlebars.registerPartial(name, options.fn);
|
||||
};
|
||||
95
website/templates/pages/demo/content.handlebars
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
<h1>Diff Prettifier <a href="#help">
|
||||
<svg height="32" class="octicon octicon-unverified" viewBox="0 0 16 16" version="1.1" width="64" aria-hidden="true">
|
||||
<path
|
||||
d="M15.67 7.06l-1.08-1.34c-.17-.22-.28-.48-.31-.77l-.19-1.7a1.51 1.51 0 0 0-1.33-1.33l-1.7-.19c-.3-.03-.56-.16-.78-.33L8.94.32c-.55-.44-1.33-.44-1.88 0L5.72 1.4c-.22.17-.48.28-.77.31l-1.7.19c-.7.08-1.25.63-1.33 1.33l-.19 1.7c-.03.3-.16.56-.33.78L.32 7.05c-.44.55-.44 1.33 0 1.88l1.08 1.34c.17.22.28.48.31.77l.19 1.7c.08.7.63 1.25 1.33 1.33l1.7.19c.3.03.56.16.78.33l1.34 1.08c.55.44 1.33.44 1.88 0l1.34-1.08c.22-.17.48-.28.77-.31l1.7-.19c.7-.08 1.25-.63 1.33-1.33l.19-1.7c.03-.3.16-.56.33-.78l1.08-1.34c.44-.55.44-1.33 0-1.88zM9 11.5c0 .28-.22.5-.5.5h-1c-.27 0-.5-.22-.5-.5v-1c0-.28.23-.5.5-.5h1c.28 0 .5.22.5.5v1zm1.56-4.89c-.06.17-.17.33-.3.47-.13.16-.14.19-.33.38-.16.17-.31.3-.52.45-.11.09-.2.19-.28.27-.08.08-.14.17-.19.27-.05.1-.08.19-.11.3-.03.11-.03.13-.03.25H7.13c0-.22 0-.31.03-.48.03-.19.08-.36.14-.52.06-.14.14-.28.25-.42.11-.13.23-.25.41-.38.27-.19.36-.3.48-.52.12-.22.2-.38.2-.59 0-.27-.06-.45-.2-.58-.13-.13-.31-.19-.58-.19-.09 0-.19.02-.3.05-.11.03-.17.09-.25.16-.08.07-.14.11-.2.2a.41.41 0 0 0-.09.28h-2c0-.38.13-.56.27-.83.16-.27.36-.5.61-.67.25-.17.55-.3.88-.38.33-.08.7-.13 1.09-.13.44 0 .83.05 1.17.13.34.09.63.22.88.39.23.17.41.38.55.63.13.25.19.55.19.88 0 .22 0 .42-.08.59l-.02-.01z">
|
||||
</path>
|
||||
</svg>
|
||||
</a>
|
||||
</h1>
|
||||
<p>GitHub, Bitbucket and GitLab commit and pull request compatible</p>
|
||||
<p>Just paste the GitHub, Bitbucket or GitLab commit, pull request or merge request url
|
||||
or any other git or unified compatible diff and we will render a pretty html representation of it
|
||||
with code syntax highlight and line similarity matching for better code reviews.
|
||||
</p>
|
||||
<h2>Options:</h2>
|
||||
<div class="row">
|
||||
<div class="col-md-2 col-xs-12 col-15">
|
||||
<label title="Output format of the HTML, either line by line or side by side">Output Format
|
||||
<select class="options-label-value" id="diff-url-options-output-format" name="outputFormat">
|
||||
<option value="line-by-line" selected>Line by Line</option>
|
||||
<option value="side-by-side">Side by Side</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div class=" col-md-2 col-xs-12 col-15">
|
||||
<label title="Show the file list summary before the diff">File Summary
|
||||
<input class="options-label-value" id="diff-url-options-show-files" type="checkbox" name="drawFileList" checked />
|
||||
</label>
|
||||
</div>
|
||||
<div class=" col-md-2 col-xs-12 col-15">
|
||||
<label title="Level of matching for the comparison algorithm">Matching Type
|
||||
<select class="options-label-value" id="diff-url-options-matching" name="matching">
|
||||
<option value="lines">Lines</option>
|
||||
<option value="words" selected>Words</option>
|
||||
<option value="none">None</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div class=" col-md-2 col-xs-12 col-15">
|
||||
<label title="Similarity threshold for the matching algorithm">Words Threshold
|
||||
<input class="options-label-value" id="diff-url-options-match-words-threshold" type="number"
|
||||
name="matchWordsThreshold" value="0.25" step="0.05" min="0" max="1" />
|
||||
</label>
|
||||
</div>
|
||||
<div class=" col-md-2 col-xs-12 col-15">
|
||||
<label title="Maximum number of comparison performed by the matching algorithm in a block of changes">Max
|
||||
Comparisons
|
||||
<input class="options-label-value" id="diff-url-options-matching-max-comparisons" type="number"
|
||||
name="matchingMaxComparisons" value="2500" step="100" min="0" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<div class="diff-url-wrapper">
|
||||
<input id="url" class="diff-url-input" type="text" name="url" placeholder="URL" />
|
||||
<a id="url-btn" class="diff-url-btn btn btn-sm" href="#">Load</a>
|
||||
</div>
|
||||
<br>
|
||||
<div id="url-diff-container" style="margin: 0 auto;">
|
||||
</div>
|
||||
<br>
|
||||
<h3 id="help">Help:</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<b>Why should I use this instead of GitHub, Bitbucket or GitLab?</b>
|
||||
<p>Code Syntax Highlight</p>
|
||||
<p>Line similarity match (similar lines are together)</p>
|
||||
<p>Line by Line and Side by Side diffs</p>
|
||||
<p>Supports any git and unified compatible diffs</p>
|
||||
<p>Easy code selection</p>
|
||||
</li>
|
||||
<li>
|
||||
<b>What urls are supported?</b>
|
||||
<p>Any GitHub, Bitbucket or GitLab Commit, Pull Request or Merge Request urls.</p>
|
||||
<p>Any Git or Unified Raw Diff or Patch urls.</p>
|
||||
</li>
|
||||
<li>
|
||||
<b>Can I send a custom url for a friend, colleague or co-worker?</b>
|
||||
<p>Just add a url parameter called diff to current url using as value your Commit, Pull Request, Merge Request, Diff
|
||||
or Patch url.</p>
|
||||
<p>ex: <a href="demo.html">https://diff2html.xyz/demo.html</a>
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<b>Why can't I paste a diff?</b>
|
||||
<p><a href="https://diffy.org/">diffy.org</a> is an amazing tool created by <a
|
||||
href="https://github.com/pbu88">pbu88</a>
|
||||
to share your diffs and uses diff2html under the hood.</p>
|
||||
<p>Also, diff2html cli can directly publish diffs to <a href="https://diffy.org/">diffy.org</a></p>
|
||||
</li>
|
||||
</ul>
|
||||
<br>
|
||||
<h3>Thank you</h3>
|
||||
<p>I want to thank <a href="https://github.com/kevinsimper">kevinsimper</a> for this great idea,
|
||||
providing better diff support for existing online services.
|
||||
</p>
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/styles/github.min.css">
|
||||
|
||||
<!-- diff2html -->
|
||||
<link rel="stylesheet" type="text/css" href="assets/diff2html.min.css">
|
||||
<!-- -->
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/highlight.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/languages/scala.min.js"></script>
|
||||
|
||||
<!-- diff2html -->
|
||||
<script type="text/javascript" src="assets/diff2html.min.js"></script>
|
||||
<script type="text/javascript" src="assets/diff2html-ui.min.js"></script>
|
||||
<!-- -->
|
||||
|
||||
<script type="text/javascript" src="demo.min.js"></script>
|
||||
1
website/templates/pages/demo/demo.css
Normal file
|
|
@ -0,0 +1 @@
|
|||
@import url("https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/styles/github.min.css");
|
||||
4
website/templates/pages/demo/demo.handlebars
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{{#partial "content"}}
|
||||
{{> content}}
|
||||
{{/partial}}
|
||||
{{> ../../template}}
|
||||
|
|
@ -1,233 +0,0 @@
|
|||
/* global Diff2HtmlUI */
|
||||
|
||||
/*
|
||||
* Example URLs:
|
||||
*
|
||||
* https://github.com/rtfpessoa/diff2html/commit/7d02e67f3b3386ac5d804f974d025cd7a1165839
|
||||
* https://github.com/rtfpessoa/diff2html/pull/106
|
||||
*
|
||||
* https://gitlab.com/gitlab-org/gitlab-ce/commit/4e963fed42ad518caa7353d361a38a1250c99c41
|
||||
* https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6763
|
||||
*
|
||||
* https://bitbucket.org/atlassian/amps/commits/52c38116f12475f75af4a147b7a7685478b83eca
|
||||
* https://bitbucket.org/atlassian/amps/pull-requests/236
|
||||
*/
|
||||
|
||||
$(document).ready(function() {
|
||||
// Improves browser compatibility
|
||||
require('whatwg-fetch');
|
||||
|
||||
var searchParam = 'diff';
|
||||
|
||||
var $container = $('.container');
|
||||
var $url = $('#url');
|
||||
var $outputFormat = $('#diff-url-options-output-format');
|
||||
var $showFiles = $('#diff-url-options-show-files');
|
||||
var $matching = $('#diff-url-options-matching');
|
||||
var $wordsThreshold = $('#diff-url-options-match-words-threshold');
|
||||
var $matchingMaxComparisons = $('#diff-url-options-matching-max-comparisons');
|
||||
|
||||
if (window.location.search) {
|
||||
var url = getUrlFromSearch(window.location.search);
|
||||
$url.val(url);
|
||||
smartDraw(url);
|
||||
}
|
||||
|
||||
bind();
|
||||
|
||||
$outputFormat
|
||||
.add($showFiles)
|
||||
.add($matching)
|
||||
.add($wordsThreshold)
|
||||
.add($matchingMaxComparisons)
|
||||
.change(function(e) {
|
||||
console.log('');
|
||||
console.log(e);
|
||||
console.log('');
|
||||
smartDraw(null, true);
|
||||
});
|
||||
|
||||
function getUrlFromSearch(search) {
|
||||
try {
|
||||
return search
|
||||
.split('?')[1]
|
||||
.split(searchParam + '=')[1]
|
||||
.split('&')[0];
|
||||
} catch (_ignore) {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getParamsFromSearch(search) {
|
||||
var map = {};
|
||||
try {
|
||||
search
|
||||
.split('?')[1]
|
||||
.split('&')
|
||||
.map(function(e) {
|
||||
var values = e.split('=');
|
||||
map[values[0]] = values[1];
|
||||
});
|
||||
} catch (_ignore) {
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
function bind() {
|
||||
$('#url-btn').click(function(e) {
|
||||
e.preventDefault();
|
||||
var url = $url.val();
|
||||
smartDraw(url);
|
||||
});
|
||||
|
||||
$url.on('paste', function(e) {
|
||||
var url = e.originalEvent.clipboardData.getData('Text');
|
||||
smartDraw(url);
|
||||
});
|
||||
}
|
||||
|
||||
function prepareUrl(url) {
|
||||
var fetchUrl;
|
||||
var headers = new Headers();
|
||||
|
||||
var githubCommitUrl = /^https?:\/\/(?:www\.)?github\.com\/(.*?)\/(.*?)\/commit\/(.*?)(?:\.diff)?(?:\.patch)?(?:\/.*)?$/;
|
||||
var githubPrUrl = /^https?:\/\/(?:www\.)?github\.com\/(.*?)\/(.*?)\/pull\/(.*?)(?:\.diff)?(?:\.patch)?(?:\/.*)?$/;
|
||||
|
||||
var gitlabCommitUrl = /^https?:\/\/(?:www\.)?gitlab\.com\/(.*?)\/(.*?)\/commit\/(.*?)(?:\.diff)?(?:\.patch)?(?:\/.*)?$/;
|
||||
var gitlabPrUrl = /^https?:\/\/(?:www\.)?gitlab\.com\/(.*?)\/(.*?)\/merge_requests\/(.*?)(?:\.diff)?(?:\.patch)?(?:\/.*)?$/;
|
||||
|
||||
var bitbucketCommitUrl = /^https?:\/\/(?:www\.)?bitbucket\.org\/(.*?)\/(.*?)\/commits\/(.*?)(?:\/raw)?(?:\/.*)?$/;
|
||||
var bitbucketPrUrl = /^https?:\/\/(?:www\.)?bitbucket\.org\/(.*?)\/(.*?)\/pull-requests\/(.*?)(?:\/.*)?$/;
|
||||
|
||||
function gitLabUrlGen(userName, projectName, type, value) {
|
||||
return 'https://crossorigin.me/https://gitlab.com/' + userName + '/' + projectName + '/' + type + '/' + value + '.diff';
|
||||
}
|
||||
|
||||
function gitHubUrlGen(userName, projectName, type, value) {
|
||||
headers.append('Accept', 'application/vnd.github.v3.diff');
|
||||
return 'https://api.github.com/repos/' + userName + '/' + projectName + '/' + type + '/' + value;
|
||||
}
|
||||
|
||||
function bitbucketUrlGen(userName, projectName, type, value) {
|
||||
var baseUrl = 'https://bitbucket.org/api/2.0/repositories/';
|
||||
if (type === 'pullrequests') {
|
||||
return baseUrl + userName + '/' + projectName + '/pullrequests/' + value + '/diff';
|
||||
}
|
||||
return baseUrl + userName + '/' + projectName + '/diff/' + value;
|
||||
}
|
||||
|
||||
var values;
|
||||
if ((values = githubCommitUrl.exec(url))) {
|
||||
fetchUrl = gitHubUrlGen(values[1], values[2], 'commits', values[3]);
|
||||
} else if ((values = githubPrUrl.exec(url))) {
|
||||
fetchUrl = gitHubUrlGen(values[1], values[2], 'pulls', values[3]);
|
||||
} else if ((values = gitlabCommitUrl.exec(url))) {
|
||||
fetchUrl = gitLabUrlGen(values[1], values[2], 'commit', values[3]);
|
||||
} else if ((values = gitlabPrUrl.exec(url))) {
|
||||
fetchUrl = gitLabUrlGen(values[1], values[2], 'merge_requests', values[3]);
|
||||
} else if ((values = bitbucketCommitUrl.exec(url))) {
|
||||
fetchUrl = bitbucketUrlGen(values[1], values[2], 'commit', values[3]);
|
||||
} else if ((values = bitbucketPrUrl.exec(url))) {
|
||||
fetchUrl = bitbucketUrlGen(values[1], values[2], 'pullrequests', values[3]);
|
||||
} else {
|
||||
console.info('Could not parse url, using the provided url.');
|
||||
fetchUrl = 'https://crossorigin.me/' + url;
|
||||
}
|
||||
|
||||
return {
|
||||
originalUrl: url,
|
||||
url: fetchUrl,
|
||||
headers: headers
|
||||
};
|
||||
}
|
||||
|
||||
function smartDraw(urlOpt, forced) {
|
||||
var url = urlOpt || $url.val();
|
||||
var req = prepareUrl(url);
|
||||
draw(req, forced);
|
||||
}
|
||||
|
||||
function draw(req, forced) {
|
||||
if (!validateUrl(req.url)) {
|
||||
console.error('Invalid url provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (validateUrl(req.originalUrl)) updateUrl(req.originalUrl);
|
||||
|
||||
var outputFormat = $outputFormat.val();
|
||||
var showFiles = $showFiles.is(':checked');
|
||||
var matching = $matching.val();
|
||||
var wordsThreshold = $wordsThreshold.val();
|
||||
var matchingMaxComparisons = $matchingMaxComparisons.val();
|
||||
|
||||
fetch(req.url, {
|
||||
method: 'GET',
|
||||
headers: req.headers,
|
||||
mode: 'cors',
|
||||
cache: 'default'
|
||||
})
|
||||
.then(function(res) {
|
||||
return res.text();
|
||||
})
|
||||
.then(function(data) {
|
||||
var container = '#url-diff-container';
|
||||
var diff2htmlUi = new Diff2HtmlUI({diff: data});
|
||||
|
||||
if (outputFormat === 'side-by-side') {
|
||||
$container.css({'width': '100%'});
|
||||
} else {
|
||||
$container.css({'width': ''});
|
||||
}
|
||||
|
||||
var params = getParamsFromSearch(window.location.search);
|
||||
delete params[searchParam];
|
||||
|
||||
if (forced) {
|
||||
params['outputFormat'] = outputFormat;
|
||||
params['showFiles'] = showFiles;
|
||||
params['matching'] = matching;
|
||||
params['wordsThreshold'] = wordsThreshold;
|
||||
params['matchingMaxComparisons'] = matchingMaxComparisons;
|
||||
} else {
|
||||
params['outputFormat'] = params['outputFormat'] || outputFormat;
|
||||
params['showFiles'] = String(params['showFiles']) !== 'false' || (params['showFiles'] === null && showFiles);
|
||||
params['matching'] = params['matching'] || matching;
|
||||
params['wordsThreshold'] = params['wordsThreshold'] || wordsThreshold;
|
||||
params['matchingMaxComparisons'] = params['matchingMaxComparisons'] || matchingMaxComparisons;
|
||||
|
||||
$outputFormat.val(params['outputFormat']);
|
||||
$showFiles.prop('checked', params['showFiles']);
|
||||
$matching.val(params['matching']);
|
||||
$wordsThreshold.val(params['wordsThreshold']);
|
||||
$matchingMaxComparisons.val(params['matchingMaxComparisons']);
|
||||
}
|
||||
|
||||
params['synchronisedScroll'] = params['synchronisedScroll'] || true;
|
||||
|
||||
diff2htmlUi.draw(container, params);
|
||||
diff2htmlUi.fileListCloseable(container, params['fileListCloseable'] || false);
|
||||
if (params['highlight'] === undefined || params['highlight']) {
|
||||
diff2htmlUi.highlightCode(container);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function validateUrl(url) {
|
||||
return /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(url);
|
||||
}
|
||||
|
||||
function updateUrl(url) {
|
||||
var params = getParamsFromSearch(window.location.search);
|
||||
|
||||
if (params[searchParam] === url) return;
|
||||
|
||||
params[searchParam] = url;
|
||||
|
||||
var paramString = Object.keys(params).map(function(k) { return k + '=' + params[k]; }).join('&');
|
||||
|
||||
window.location = 'demo.html?' + paramString;
|
||||
}
|
||||
});
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
<h1>Diff Prettifier <a href="#help">
|
||||
<svg height="32" class="octicon octicon-unverified" viewBox="0 0 16 16" version="1.1" width="64" aria-hidden="true">
|
||||
<path
|
||||
d="M15.67 7.06l-1.08-1.34c-.17-.22-.28-.48-.31-.77l-.19-1.7a1.51 1.51 0 0 0-1.33-1.33l-1.7-.19c-.3-.03-.56-.16-.78-.33L8.94.32c-.55-.44-1.33-.44-1.88 0L5.72 1.4c-.22.17-.48.28-.77.31l-1.7.19c-.7.08-1.25.63-1.33 1.33l-.19 1.7c-.03.3-.16.56-.33.78L.32 7.05c-.44.55-.44 1.33 0 1.88l1.08 1.34c.17.22.28.48.31.77l.19 1.7c.08.7.63 1.25 1.33 1.33l1.7.19c.3.03.56.16.78.33l1.34 1.08c.55.44 1.33.44 1.88 0l1.34-1.08c.22-.17.48-.28.77-.31l1.7-.19c.7-.08 1.25-.63 1.33-1.33l.19-1.7c.03-.3.16-.56.33-.78l1.08-1.34c.44-.55.44-1.33 0-1.88zM9 11.5c0 .28-.22.5-.5.5h-1c-.27 0-.5-.22-.5-.5v-1c0-.28.23-.5.5-.5h1c.28 0 .5.22.5.5v1zm1.56-4.89c-.06.17-.17.33-.3.47-.13.16-.14.19-.33.38-.16.17-.31.3-.52.45-.11.09-.2.19-.28.27-.08.08-.14.17-.19.27-.05.1-.08.19-.11.3-.03.11-.03.13-.03.25H7.13c0-.22 0-.31.03-.48.03-.19.08-.36.14-.52.06-.14.14-.28.25-.42.11-.13.23-.25.41-.38.27-.19.36-.3.48-.52.12-.22.2-.38.2-.59 0-.27-.06-.45-.2-.58-.13-.13-.31-.19-.58-.19-.09 0-.19.02-.3.05-.11.03-.17.09-.25.16-.08.07-.14.11-.2.2a.41.41 0 0 0-.09.28h-2c0-.38.13-.56.27-.83.16-.27.36-.5.61-.67.25-.17.55-.3.88-.38.33-.08.7-.13 1.09-.13.44 0 .83.05 1.17.13.34.09.63.22.88.39.23.17.41.38.55.63.13.25.19.55.19.88 0 .22 0 .42-.08.59l-.02-.01z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</h1>
|
||||
<p>GitHub, Bitbucket and GitLab commit and pull request compatible</p>
|
||||
<p>Just paste the GitHub, Bitbucket or GitLab commit, pull request or merge request url
|
||||
or any other git or unified compatible diff and we will render a pretty html representation of it
|
||||
with code syntax highlight and line similarity matching for better code reviews.
|
||||
</p>
|
||||
<h2>Options:</h2>
|
||||
<div class="row">
|
||||
<div class="col-md-2 col-xs-12 col-15">
|
||||
<label title="Output format of the HTML, either line by line or side by side">Output Format
|
||||
<select class="options-label-value" id="diff-url-options-output-format" name="outputFormat">
|
||||
<option value="line-by-line" selected>Line by Line</option>
|
||||
<option value="side-by-side">Side by Side</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div class=" col-md-2 col-xs-12 col-15">
|
||||
<label title="Show the file list summary before the diff">File Summary
|
||||
<input class="options-label-value" id="diff-url-options-show-files" type="checkbox" name="showFiles" checked/>
|
||||
</label>
|
||||
</div>
|
||||
<div class=" col-md-2 col-xs-12 col-15">
|
||||
<label title="Level of matching for the comparison algorithm">Matching Type
|
||||
<select class="options-label-value" id="diff-url-options-matching" name="matching">
|
||||
<option value="lines">Lines</option>
|
||||
<option value="words" selected>Words</option>
|
||||
<option value="none">None</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div class=" col-md-2 col-xs-12 col-15">
|
||||
<label title="Similarity threshold for the matching algorithm">Words Threshold
|
||||
<input class="options-label-value" id="diff-url-options-match-words-threshold" type="number"
|
||||
name="matchWordsThreshold" value="0.25" step="0.05"
|
||||
min="0" max="1"/>
|
||||
</label>
|
||||
</div>
|
||||
<div class=" col-md-2 col-xs-12 col-15">
|
||||
<label title="Maximum number of comparison performed by the matching algorithm in a block of changes">Max
|
||||
Comparisons
|
||||
<input class="options-label-value" id="diff-url-options-matching-max-comparisons" type="number"
|
||||
name="matchingMaxComparisons" value="2500"
|
||||
step="100" min="0"/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<div class="diff-url-wrapper">
|
||||
<input id="url" class="diff-url-input" type="text" name="url" placeholder="URL"/>
|
||||
<a id="url-btn" class="diff-url-btn btn btn-sm" href="#">Load</a>
|
||||
</div>
|
||||
<br>
|
||||
<div id="url-diff-container" style="margin: 0 auto;">
|
||||
</div>
|
||||
<br>
|
||||
<h3 id="help">Help:</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<b>Why should I use this instead of GitHub, Bitbucket or GitLab?</b>
|
||||
<p>Code Syntax Highlight</p>
|
||||
<p>Line similarity match (similar lines are together)</p>
|
||||
<p>Line by Line and Side by Side diffs</p>
|
||||
<p>Supports any git and unified compatible diffs</p>
|
||||
<p>Easy code selection</p>
|
||||
</li>
|
||||
<li>
|
||||
<b>What urls are supported?</b>
|
||||
<p>Any GitHub, Bitbucket or GitLab Commit, Pull Request or Merge Request urls.</p>
|
||||
<p>Any Git or Unified Raw Diff or Patch urls.</p>
|
||||
</li>
|
||||
<li>
|
||||
<b>Can I send a custom url for a friend, colleague or co-worker?</b>
|
||||
<p>Just add a url parameter called diff to current url using as value your Commit, Pull Request, Merge Request, Diff
|
||||
or Patch url.</p>
|
||||
<p>ex: <a href="{{ demoUrl }}">https://diff2html.xyz/{{ demoUrl }}</a>
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<b>Why can't I paste a diff?</b>
|
||||
<p><a href="https://diffy.org/">diffy.org</a> is an amazing tool created by <a
|
||||
href="https://github.com/pbu88">pbu88</a>
|
||||
to share your diffs and uses diff2html under the hood.</p>
|
||||
<p>Also, diff2html cli can directly publish diffs to <a href="https://diffy.org/">diffy.org</a></p>
|
||||
</li>
|
||||
</ul>
|
||||
<br>
|
||||
<h3>Thank you</h3>
|
||||
<p>I want to thank <a href="https://github.com/kevinsimper">kevinsimper</a> for this great idea,
|
||||
providing better diff support for existing online services.
|
||||
</p>
|
||||
287
website/templates/pages/demo/demo.ts
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
import { Diff2HtmlUI, defaultDiff2HtmlUIConfig, Diff2HtmlUIConfig } from "../../../../src/ui/js/diff2html-ui";
|
||||
|
||||
import "../../../main.ts";
|
||||
import "../../../main.css";
|
||||
import "./demo.css";
|
||||
import "../../../../src/ui/css/diff2html.css";
|
||||
|
||||
/*
|
||||
* Example URLs:
|
||||
*
|
||||
* https://github.com/rtfpessoa/diff2html/commit/7d02e67f3b3386ac5d804f974d025cd7a1165839
|
||||
* https://github.com/rtfpessoa/diff2html/pull/106
|
||||
*
|
||||
* https://gitlab.com/gitlab-org/gitlab-ce/commit/4e963fed42ad518caa7353d361a38a1250c99c41
|
||||
* https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6763
|
||||
*
|
||||
* https://bitbucket.org/atlassian/amps/commits/52c38116f12475f75af4a147b7a7685478b83eca
|
||||
* https://bitbucket.org/atlassian/amps/pull-requests/236
|
||||
*/
|
||||
|
||||
type URLParams = {
|
||||
diff?: string;
|
||||
[key: string]: string | boolean | number | undefined;
|
||||
};
|
||||
|
||||
const searchParam = "diff";
|
||||
|
||||
function getParamsFromSearch(search: string): URLParams {
|
||||
try {
|
||||
return search
|
||||
.split("?")[1]
|
||||
.split("&")
|
||||
.reduce((urlParams, e) => {
|
||||
const values = e.split("=");
|
||||
return {
|
||||
...urlParams,
|
||||
[values[0]]: values[1]
|
||||
};
|
||||
}, {});
|
||||
} catch (_ignore) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function validateUrl(url: string): boolean {
|
||||
return /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(
|
||||
url
|
||||
);
|
||||
}
|
||||
|
||||
type Request = {
|
||||
url: string;
|
||||
headers: Headers;
|
||||
};
|
||||
|
||||
function prepareRequest(url: string): Request {
|
||||
if (!validateUrl(url)) {
|
||||
const errorMsg = "Invalid url provided!";
|
||||
console.error(errorMsg);
|
||||
throw new Error(errorMsg);
|
||||
}
|
||||
|
||||
let fetchUrl;
|
||||
const headers = new Headers();
|
||||
|
||||
const githubCommitUrl = /^https?:\/\/(?:www\.)?github\.com\/(.*?)\/(.*?)\/commit\/(.*?)(?:\.diff)?(?:\.patch)?(?:\/.*)?$/;
|
||||
const githubPrUrl = /^https?:\/\/(?:www\.)?github\.com\/(.*?)\/(.*?)\/pull\/(.*?)(?:\.diff)?(?:\.patch)?(?:\/.*)?$/;
|
||||
|
||||
const gitlabCommitUrl = /^https?:\/\/(?:www\.)?gitlab\.com\/(.*?)\/(.*?)\/commit\/(.*?)(?:\.diff)?(?:\.patch)?(?:\/.*)?$/;
|
||||
const gitlabPrUrl = /^https?:\/\/(?:www\.)?gitlab\.com\/(.*?)\/(.*?)\/merge_requests\/(.*?)(?:\.diff)?(?:\.patch)?(?:\/.*)?$/;
|
||||
|
||||
const bitbucketCommitUrl = /^https?:\/\/(?:www\.)?bitbucket\.org\/(.*?)\/(.*?)\/commits\/(.*?)(?:\/raw)?(?:\/.*)?$/;
|
||||
const bitbucketPrUrl = /^https?:\/\/(?:www\.)?bitbucket\.org\/(.*?)\/(.*?)\/pull-requests\/(.*?)(?:\/.*)?$/;
|
||||
|
||||
function gitLabUrlGen(userName: string, projectName: string, type: string, value: string): string {
|
||||
return (
|
||||
"https://crossorigin.me/https://gitlab.com/" + userName + "/" + projectName + "/" + type + "/" + value + ".diff"
|
||||
);
|
||||
}
|
||||
|
||||
function gitHubUrlGen(userName: string, projectName: string, type: string, value: string): string {
|
||||
headers.append("Accept", "application/vnd.github.v3.diff");
|
||||
return "https://api.github.com/repos/" + userName + "/" + projectName + "/" + type + "/" + value;
|
||||
}
|
||||
|
||||
function bitbucketUrlGen(userName: string, projectName: string, type: string, value: string): string {
|
||||
const baseUrl = "https://bitbucket.org/api/2.0/repositories/";
|
||||
if (type === "pullrequests") {
|
||||
return baseUrl + userName + "/" + projectName + "/pullrequests/" + value + "/diff";
|
||||
}
|
||||
return baseUrl + userName + "/" + projectName + "/diff/" + value;
|
||||
}
|
||||
|
||||
let values;
|
||||
if ((values = githubCommitUrl.exec(url))) {
|
||||
fetchUrl = gitHubUrlGen(values[1], values[2], "commits", values[3]);
|
||||
} else if ((values = githubPrUrl.exec(url))) {
|
||||
fetchUrl = gitHubUrlGen(values[1], values[2], "pulls", values[3]);
|
||||
} else if ((values = gitlabCommitUrl.exec(url))) {
|
||||
fetchUrl = gitLabUrlGen(values[1], values[2], "commit", values[3]);
|
||||
} else if ((values = gitlabPrUrl.exec(url))) {
|
||||
fetchUrl = gitLabUrlGen(values[1], values[2], "merge_requests", values[3]);
|
||||
} else if ((values = bitbucketCommitUrl.exec(url))) {
|
||||
fetchUrl = bitbucketUrlGen(values[1], values[2], "commit", values[3]);
|
||||
} else if ((values = bitbucketPrUrl.exec(url))) {
|
||||
fetchUrl = bitbucketUrlGen(values[1], values[2], "pullrequests", values[3]);
|
||||
} else {
|
||||
console.info("Could not parse url, using the provided url.");
|
||||
fetchUrl = "https://crossorigin.me/" + url;
|
||||
}
|
||||
|
||||
return {
|
||||
url: fetchUrl,
|
||||
headers: headers
|
||||
};
|
||||
}
|
||||
|
||||
function getConfiguration(urlParams: URLParams): Diff2HtmlUIConfig {
|
||||
// Removing `diff` form `urlParams` to avoid being inserted
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { diff, ...urlParamsRest } = urlParams;
|
||||
const config: URLParams = {
|
||||
...defaultDiff2HtmlUIConfig,
|
||||
...urlParamsRest
|
||||
};
|
||||
|
||||
return Object.entries(config).reduce((object, [k, v]) => {
|
||||
const newObject = !Number.isNaN(Number(v))
|
||||
? { [k]: Number(v) }
|
||||
: v === "true" || v === "false"
|
||||
? { [k]: Boolean(v) }
|
||||
: { [k]: v };
|
||||
return { ...object, ...newObject };
|
||||
}, {});
|
||||
}
|
||||
|
||||
async function getDiff(request: Request): Promise<string> {
|
||||
try {
|
||||
const result = await fetch(request.url, {
|
||||
method: "GET",
|
||||
headers: request.headers,
|
||||
mode: "cors",
|
||||
cache: "default"
|
||||
});
|
||||
return result.text();
|
||||
} catch (error) {
|
||||
console.error("Failed to retrieve diff", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function draw(diffString: string, config: Diff2HtmlUIConfig, elements: Elements): void {
|
||||
const diff2htmlUi = new Diff2HtmlUI(diffString, elements.structure.diffTarget, config);
|
||||
|
||||
if (config.outputFormat === "side-by-side") {
|
||||
elements.structure.container.style.width = "100%";
|
||||
} else {
|
||||
elements.structure.container.style.width = "";
|
||||
}
|
||||
|
||||
diff2htmlUi.draw();
|
||||
}
|
||||
|
||||
async function prepareInitialState(elements: Elements): Promise<[Diff2HtmlUIConfig, string]> {
|
||||
const urlParams = getParamsFromSearch(window.location.search);
|
||||
const currentUrl = (urlParams && urlParams[searchParam]) || "https://github.com/rtfpessoa/diff2html/pull/106";
|
||||
|
||||
if (currentUrl !== elements.url.input.value) elements.url.input.value = currentUrl;
|
||||
|
||||
const request = prepareRequest(currentUrl);
|
||||
|
||||
const initialConfiguration = getConfiguration(urlParams);
|
||||
const initialDiff = await getDiff(request);
|
||||
|
||||
return [initialConfiguration, initialDiff];
|
||||
}
|
||||
|
||||
function updateBrowserUrl(config: Diff2HtmlUIConfig, newDiffUrl: string): void {
|
||||
if (history.pushState) {
|
||||
const paramString = Object.entries(config)
|
||||
.map(([k, v]) => k + "=" + v)
|
||||
.join("&");
|
||||
const newPageUrl =
|
||||
window.location.protocol +
|
||||
"//" +
|
||||
window.location.host +
|
||||
window.location.pathname +
|
||||
"?" +
|
||||
paramString +
|
||||
"&" +
|
||||
searchParam +
|
||||
"=" +
|
||||
newDiffUrl;
|
||||
window.history.pushState({ path: newPageUrl }, "", newPageUrl);
|
||||
}
|
||||
}
|
||||
|
||||
type Elements = {
|
||||
structure: {
|
||||
container: HTMLElement;
|
||||
diffTarget: HTMLElement;
|
||||
};
|
||||
url: {
|
||||
input: HTMLInputElement;
|
||||
button: HTMLElement;
|
||||
};
|
||||
options: {
|
||||
outputFormat: HTMLInputElement;
|
||||
matching: HTMLInputElement;
|
||||
wordsThreshold: HTMLInputElement;
|
||||
matchingMaxComparisons: HTMLInputElement;
|
||||
};
|
||||
checkboxes: {
|
||||
drawFileList: HTMLInputElement;
|
||||
};
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
// Improves browser compatibility
|
||||
require("whatwg-fetch");
|
||||
|
||||
const drawAndUpdateUrl = async (
|
||||
diffUrl: string,
|
||||
diffString: string,
|
||||
config: Diff2HtmlUIConfig,
|
||||
elements: Elements
|
||||
): Promise<void> => {
|
||||
updateBrowserUrl(config, diffUrl);
|
||||
const newRequest = prepareRequest(diffUrl);
|
||||
diffString = await getDiff(newRequest);
|
||||
draw(diffString, config, elements);
|
||||
};
|
||||
|
||||
const elements: Elements = {
|
||||
structure: {
|
||||
container: document.getElementsByClassName("container")[0] as HTMLElement,
|
||||
diffTarget: document.getElementById("url-diff-container") as HTMLElement
|
||||
},
|
||||
url: {
|
||||
input: document.getElementById("url") as HTMLInputElement,
|
||||
button: document.getElementById("url-btn") as HTMLElement
|
||||
},
|
||||
options: {
|
||||
outputFormat: document.getElementById("diff-url-options-output-format") as HTMLInputElement,
|
||||
matching: document.getElementById("diff-url-options-matching") as HTMLInputElement,
|
||||
wordsThreshold: document.getElementById("diff-url-options-match-words-threshold") as HTMLInputElement,
|
||||
matchingMaxComparisons: document.getElementById("diff-url-options-matching-max-comparisons") as HTMLInputElement
|
||||
},
|
||||
checkboxes: {
|
||||
drawFileList: document.getElementById("diff-url-options-show-files") as HTMLInputElement
|
||||
}
|
||||
};
|
||||
|
||||
let [config, diffString] = await prepareInitialState(elements);
|
||||
|
||||
// Update HTML inputs from any changes in URL
|
||||
config.outputFormat && (elements.options.outputFormat.value = config.outputFormat);
|
||||
config.drawFileList && (elements.checkboxes.drawFileList.checked = config.drawFileList);
|
||||
config.matching && (elements.options.matching.value = config.matching);
|
||||
config.matchWordsThreshold && (elements.options.wordsThreshold.value = config.matchWordsThreshold.toString());
|
||||
config.matchingMaxComparisons &&
|
||||
(elements.options.matchingMaxComparisons.value = config.matchingMaxComparisons.toString());
|
||||
|
||||
Object.entries(elements.options).forEach(([option, element]) =>
|
||||
element.addEventListener("change", () => {
|
||||
config = { ...config, [option]: element.value };
|
||||
drawAndUpdateUrl(elements.url.input.value, diffString, config, elements);
|
||||
})
|
||||
);
|
||||
|
||||
Object.entries(elements.checkboxes).forEach(([option, checkbox]) =>
|
||||
checkbox.addEventListener("change", () => {
|
||||
config = { ...config, [option]: checkbox.checked };
|
||||
drawAndUpdateUrl(elements.url.input.value, diffString, config, elements);
|
||||
})
|
||||
);
|
||||
|
||||
elements.url.button.addEventListener("click", async e => {
|
||||
e.preventDefault();
|
||||
const newDiffUrl = elements.url.input.value;
|
||||
const newRequest = prepareRequest(newDiffUrl);
|
||||
diffString = await getDiff(newRequest);
|
||||
drawAndUpdateUrl(newDiffUrl, diffString, config, elements);
|
||||
});
|
||||
|
||||
return drawAndUpdateUrl(elements.url.input.value, diffString, config, elements);
|
||||
});
|
||||
295
website/templates/pages/index/content.handlebars
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
<div class="hero hero-homepage">
|
||||
<span class="hero-booticon">
|
||||
<span class="hero-green">diff</span><span class="hero-black">2</span><span class="hero-red">html</span>
|
||||
</span>
|
||||
<h1 class="hero-header">Diff parser and pretty html generator</h1>
|
||||
<h4 class="text-muted">Better diffs, unmatched reviews.</h4>
|
||||
<h2><a class="btn btn-lg" href="demo.html">Demo</a></h2>
|
||||
|
||||
<div class="screenshots screenshots-fan clearfix">
|
||||
|
||||
<img class="screenshot hidden-xs" src="images/snapshot-2.png">
|
||||
|
||||
<a class="screenshot" href="demo.html">
|
||||
<img src="images/snapshot-3.png">
|
||||
</a>
|
||||
|
||||
<a class="screenshot hidden-xs" href="demo.html">
|
||||
<img src="images/snapshot-1.png">
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="row row-padded-small row-bordered">
|
||||
<div class="col-sm-8 col-sm-offset-2 text-center">
|
||||
<h2 class="m-b-md">Each diff provides a comprehensive visualization of the code changes,
|
||||
helping developers identify problems and better understand the changes.</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row row-padded-small homepage-grid row-bordered p-t text-center">
|
||||
<div class="col-sm-4">
|
||||
<span class="svg-icon-large">
|
||||
<svg aria-hidden="true" class="octicon octicon-diff" height="16" version="1.1" viewBox="0 0 14 16"
|
||||
width="14">
|
||||
<path
|
||||
d="M6 7h2v1H6v2h-1V8H3v-1h2V5h1v2zM3 13h5v-1H3v1z m4.5-11l3.5 3.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V3c0-0.55 0.45-1 1-1h6.5z m2.5 4L7 3H1v12h9V6zM8.5 0S3 0 3 0v1h5l4 4v8h1V4.5L8.5 0z">
|
||||
</path>
|
||||
</svg>
|
||||
</span>
|
||||
<h5><strong>Line by Line and Side by Side changes</strong></h5>
|
||||
<p class="text-muted">Each diff features a line by line and side by side preview of your
|
||||
changes.</p>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-4">
|
||||
<span class="svg-icon-large">
|
||||
<svg aria-hidden="true" class="octicon octicon-tasklist" height="16" version="1.1" viewBox="0 0 16 16"
|
||||
width="16">
|
||||
<path
|
||||
d="M15.41 9H7.59c-0.59 0-0.59-0.41-0.59-1s0-1 0.59-1h7.81c0.59 0 0.59 0.41 0.59 1s0 1-0.59 1zM9.59 4c-0.59 0-0.59-0.41-0.59-1s0-1 0.59-1h5.81c0.59 0 0.59 0.41 0.59 1s0 1-0.59 1H9.59zM0 3.91l1.41-1.3 1.59 1.59L7.09 0l1.41 1.41-5.5 5.5L0 3.91z m7.59 8.09h7.81c0.59 0 0.59 0.41 0.59 1s0 1-0.59 1H7.59c-0.59 0-0.59-0.41-0.59-1s0-1 0.59-1z">
|
||||
</path>
|
||||
</svg>
|
||||
</span>
|
||||
<h5><strong>Code syntax highlight</strong></h5>
|
||||
<p class="text-muted">All the code changes are syntax highlighted using <a
|
||||
href="https://highlightjs.org/">highlight.js</a>,
|
||||
providing more readability.</p>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-4">
|
||||
<span class="svg-icon-large">
|
||||
<svg aria-hidden="true" class="octicon octicon-clippy" height="16" version="1.1" viewBox="0 0 14 16"
|
||||
width="14">
|
||||
<path
|
||||
d="M2 12h4v1H2v-1z m5-6H2v1h5v-1z m2 3V7L6 10l3 3V11h5V9H9z m-4.5-1H2v1h2.5v-1zM2 11h2.5v-1H2v1z m9 1h1v2c-0.02 0.28-0.11 0.52-0.3 0.7s-0.42 0.28-0.7 0.3H1c-0.55 0-1-0.45-1-1V3c0-0.55 0.45-1 1-1h3C4 0.89 4.89 0 6 0s2 0.89 2 2h3c0.55 0 1 0.45 1 1v5h-1V5H1v9h10V12zM2 4h8c0-0.55-0.45-1-1-1h-1c-0.55 0-1-0.45-1-1s-0.45-1-1-1-1 0.45-1 1-0.45 1-1 1h-1c-0.55 0-1 0.45-1 1z">
|
||||
</path>
|
||||
</svg>
|
||||
</span>
|
||||
<h5><strong>Line similarity matching</strong></h5>
|
||||
<p class="text-muted">Similar lines are paired, allowing for easier change tracking.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="install" class="row-padded-small row-centered row-bordered">
|
||||
<div class="col-md-12">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3><strong>Install with Bower</strong></h3>
|
||||
<p>You can install and manage diff2html's CSS and JS using Bower:</p>
|
||||
<div class="homepage-code-example">
|
||||
<p><span class="unselectable">> $ </span><span class="text-muted">bower install
|
||||
diff2html</span></p>
|
||||
<span class="btn-clipboard" data-clipboard-text="bower install diff2html"
|
||||
title="Copy">Copy</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3><strong>Install with npm</strong></h3>
|
||||
<p>You can also install diff2html using npm:</p>
|
||||
<div class="homepage-code-example">
|
||||
<p><span class="unselectable">> $ </span><span class="text-muted">npm install
|
||||
diff2html</span></p>
|
||||
<span class="btn-clipboard" data-clipboard-text="npm install diff2html" title="Copy">Copy</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row row-padded-small">
|
||||
<div class="col-md-12">
|
||||
<a href="https://github.com/rtfpessoa/diff2html#how-to-use" target="_blank">
|
||||
Find usage examples in the Docs
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="cli" class="row row-padded-small row-centered row-bordered">
|
||||
<div class="col-xs-10 col-xs-offset-1 col-md-6 col-md-offset-0">
|
||||
<h3><strong>With command line integration</strong></h3>
|
||||
<h4 class="m-b-md">We work hard to make sure you can have your diffs in a simple and flexible
|
||||
way. Go <a href="https://github.com/rtfpessoa/diff2html-cli" target="_blank">here full
|
||||
documentation</a>.</h4>
|
||||
</div>
|
||||
<div class="col-xs-10 col-xs-offset-1 col-md-6 col-md-offset-0">
|
||||
<div class="homepage-terminal-example">
|
||||
<p class="m-b-md">
|
||||
<span class="unselectable">> $ </span><span class="text-muted">npm install -g
|
||||
diff2html-cli</span><br>
|
||||
<span class="unselectable">diff2html cli installed!</span>
|
||||
</p>
|
||||
<p class="m-b-md">
|
||||
<span class="unselectable">> $ </span><span class="text-muted">diff2html</span><br>
|
||||
<span class="unselectable">Previous commit changes on your browser</span>
|
||||
</p>
|
||||
<p>
|
||||
<span class="unselectable">> $ <span class="text-muted">is that it?</span><br>
|
||||
Yup, it's that simple.</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="users" class="row row-padded-small row-centered row-bordered">
|
||||
<div class="col-md-12">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3><strong>Projects using diff2html</strong></h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row row-eq-height">
|
||||
<div class="col-xs-12 col-sm-6 col-md-3 m-b-lg">
|
||||
<div class="panel panel-default panel-profile m-b-0">
|
||||
<div class="panel-body text-center">
|
||||
<h5 class="panel-title">diff2html-cli</h5>
|
||||
<p class="m-b">diff2html from your terminal to the browser.</p>
|
||||
<a href="https://github.com/rtfpessoa/diff2html-cli" target="_blank"
|
||||
class="btn btn-primary-outline btn-sm m-b">
|
||||
<span class="icon icon-add-user"></span> View GitHub
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-6 col-md-3 m-b-lg">
|
||||
<div class="panel panel-default panel-profile m-b-0">
|
||||
<div class="panel-body text-center">
|
||||
<h5 class="panel-title">Codacy</h5>
|
||||
<p class="m-b">Check code style, security, duplication, complexity and coverage on every
|
||||
change.</p>
|
||||
<a href="https://www.codacy.com" target="_blank" class="btn btn-primary-outline btn-sm m-b">
|
||||
<span class="icon icon-add-user"></span> Website
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-6 col-md-3 m-b-lg">
|
||||
<div class="panel panel-default panel-profile m-b-0">
|
||||
<div class="panel-body text-center">
|
||||
<h5 class="panel-title">Ungit</h5>
|
||||
<p class="m-b">The easiest way to use git. On any platform. Anywhere.</p>
|
||||
<a href="https://github.com/FredrikNoren/ungit" target="_blank"
|
||||
class="btn btn-primary-outline btn-sm m-b">
|
||||
<span class="icon icon-add-user"></span> View GitHub
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-6 col-md-3 m-b-lg">
|
||||
<div class="panel panel-default panel-profile m-b-0">
|
||||
<div class="panel-body text-center">
|
||||
<h5 class="panel-title">Diffy</h5>
|
||||
<p class="m-b">Share your diffs and explain your ideas without committing.</p>
|
||||
<a href="https://diffy.org/" target="_blank" class="btn btn-primary-outline btn-sm m-b">
|
||||
<span class="icon icon-add-user"></span> Website
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-6 col-md-3 m-b-lg">
|
||||
<div class="panel panel-default panel-profile m-b-0">
|
||||
<div class="panel-body text-center">
|
||||
<h5 class="panel-title">diff-pane</h5>
|
||||
<p class="m-b">Atom - Diff two panes.</p>
|
||||
<a href="https://github.com/t-ari/diff-pane" target="_blank"
|
||||
class="btn btn-primary-outline btn-sm m-b">
|
||||
<span class="icon icon-add-user"></span> View GitHub
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-6 col-md-3 m-b-lg">
|
||||
<div class="panel panel-default panel-profile m-b-0">
|
||||
<div class="panel-body text-center">
|
||||
<h5 class="panel-title">node-giff</h5>
|
||||
<p class="m-b">Display git diff on browser.</p>
|
||||
<a href="https://github.com/do7be/node-giff" target="_blank"
|
||||
class="btn btn-primary-outline btn-sm m-b">
|
||||
<span class="icon icon-add-user"></span> View GitHub
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-6 col-md-3 m-b-lg">
|
||||
<div class="panel panel-default panel-profile m-b-0">
|
||||
<div class="panel-body text-center">
|
||||
<h5 class="panel-title">edgar-monitor</h5>
|
||||
<p class="m-b">A module that processes new Edgar filings and sends out
|
||||
notifications.</p>
|
||||
<a href="https://github.com/buzzfeed-openlab/edgar-monitor" target="_blank"
|
||||
class="btn btn-primary-outline btn-sm m-b">
|
||||
<span class="icon icon-add-user"></span> View GitHub
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-6 col-md-3 m-b-lg">
|
||||
<div class="panel panel-default panel-profile m-b-0">
|
||||
<div class="panel-body text-center">
|
||||
<h5 class="panel-title">node-git</h5>
|
||||
<p class="m-b">Execute Git Command by Node.js.</p>
|
||||
<a href="https://github.com/liangshuai/node-git" target="_blank"
|
||||
class="btn btn-primary-outline btn-sm m-b">
|
||||
<span class="icon icon-add-user"></span> View GitHub
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-6 col-md-3 m-b-lg">
|
||||
<div class="panel panel-default panel-profile m-b-0">
|
||||
<div class="panel-body text-center">
|
||||
<h5 class="panel-title">Jenkins</h5>
|
||||
<p class="m-b">Show diffs between builds</p>
|
||||
<a href="https://wiki.jenkins-ci.org/display/JENKINS/Last+Changes+Plugin" target="_blank"
|
||||
class="btn btn-primary-outline btn-sm m-b">
|
||||
<span class="icon icon-add-user"></span> Website
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-6 col-md-3 m-b-lg">
|
||||
<div class="panel panel-default panel-profile m-b-0">
|
||||
<div class="panel-body text-center">
|
||||
<h5 class="panel-title">Light Review</h5>
|
||||
<p class="m-b">Code Reviews with maximum control for the leading developers</p>
|
||||
<a href="http://light-review.com/" target="_blank"
|
||||
class="btn btn-primary-outline btn-sm m-b">
|
||||
<span class="icon icon-add-user"></span> Website
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-6 col-md-3 m-b-lg">
|
||||
<div class="panel panel-default panel-profile m-b-0">
|
||||
<div class="panel-body text-center">
|
||||
<h5 class="panel-title">Simple Git</h5>
|
||||
<p class="m-b">A simple package to be able to drive GIT</p>
|
||||
<a href="https://github.com/mauricioszabo/simple-git" target="_blank"
|
||||
class="btn btn-primary-outline btn-sm m-b">
|
||||
<span class="icon icon-add-user"></span> View GitHub
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row row-padded-small text-center">
|
||||
<div class="col-sm-8 col-sm-offset-2 text-center">
|
||||
<h3><strong>Open Source</strong></h3>
|
||||
<h4 class="m-b-md">diff2html is open source.
|
||||
If you'd like to be part of the diff2html community or help us improve,
|
||||
find us on <a href="https://github.com/rtfpessoa/diff2html" target="_blank">GitHub</a> and
|
||||
<a href="https://gitter.im/rtfpessoa/diff2html" target="_blank">Gitter</a>. Need any help?
|
||||
</h4>
|
||||
<a class="btn btn-md" href="https://github.com/rtfpessoa/diff2html#how-to-use" target="_blank">
|
||||
Read more in the Docs
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 121 KiB |
|
|
@ -1,5 +0,0 @@
|
|||
<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/1.5.10/clipboard.min.js"></script>
|
||||
|
||||
<script>
|
||||
new Clipboard(document.getElementsByClassName("btn-clipboard"));
|
||||
</script>
|
||||