diff --git a/.circleci/config.yml b/.circleci/config.yml index e29e513..c7f95d4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -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,6 +29,7 @@ jobs: key: dependency-cache-{{ checksum "yarn.lock" }} paths: - ./node_modules + - run: yarn run build - run: yarn run lint - run: yarn run coverage - run: yarn run codacy @@ -38,40 +39,25 @@ jobs: - docs - build - 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-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: @@ -89,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 diff --git a/.eslintrc.json b/.eslintrc.json index 2ab5590..a20ab3b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -12,14 +12,7 @@ "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", "node", "import", "promise", "@typescript-eslint", "jest"], "globals": { diff --git a/README.md b/README.md index 01022db..5b6cee4 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ # diff2html -[![Codacy Quality Badge](https://api.codacy.com/project/badge/Grade/06412dc3f5a14f568778d0db8a1f7dc8)](https://www.codacy.com/app/rtfpessoa/diff2html?utm_source=github.com&utm_medium=referral&utm_content=rtfpessoa/diff2html&utm_campaign=Badge_Grade) +[![Codacy Quality Badge](https://api.codacy.com/project/badge/Grade/06412dc3f5a14f568778d0db8a1f7dc8)](https://www.codacy.com/app/rtfpessoa/diff2html?utm_source=github.com&utm_medium=referral&utm_content=rtfpessoa/diff2html&utm_campaign=Badge_Grade) [![Codacy Coverage Badge](https://api.codacy.com/project/badge/Coverage/06412dc3f5a14f568778d0db8a1f7dc8)](https://www.codacy.com/app/rtfpessoa/diff2html?utm_source=github.com&utm_medium=referral&utm_content=rtfpessoa/diff2html&utm_campaign=Badge_Coverage) [![Circle CI](https://circleci.com/gh/rtfpessoa/diff2html.svg?style=svg)](https://circleci.com/gh/rtfpessoa/diff2html) -[![Dependency Status](https://dependencyci.com/github/rtfpessoa/diff2html/badge)](https://dependencyci.com/github/rtfpessoa/diff2html) [![npm](https://img.shields.io/npm/v/diff2html.svg)](https://www.npmjs.com/package/diff2html) [![Dependency Status](https://david-dm.org/rtfpessoa/diff2html.svg)](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,15 @@ 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 + - [build/browser/diff2html.min.js](./build/browser/diff2html.min.js) - includes the diff parser and html generator + - [build/browser/diff2html-ui.min.js](./build/browser/diff2html-ui.min.js) - includes the wrapper of diff2html that adds highlight, synchronized scroll, and other nice features + - Node.js + - [build/commonjs-node/diff2html.js](./build/commonjs-node/diff2html.js) - includes the diff parser and html generator ## How to use @@ -62,7 +61,7 @@ Import the stylesheet ```html - + ``` You can also refer to it from a CDN like [CDNJS](https://cdnjs.com/libraries/diff2html). @@ -73,37 +72,41 @@ Import the stylesheet and the library code ```html - + - + ``` -It will now be available as a global variable named `Diff2Html`. +It will now be available as a global variable named `global.Diff2Html`. ```js -var diffHtml = Diff2Html.getPrettyHtml( - '', - {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("", { + 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(""); +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 +114,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 @@ -136,7 +139,7 @@ export class AppDiffComponent implements OnInit { ``` -* `.angular-cli.json` - Add styles +- `.angular-cli.json` - Add styles ```json "styles": [ @@ -148,27 +151,28 @@ export class AppDiffComponent implements OnInit { ```vue - + ``` #### Init ```js -var diff2htmlUi = new Diff2HtmlUI({diff: diffString}); +const targetElement = document.getElementById("destination-elem-id"); +const configuration = { drawFileList: true, matching: "lines" }; + +const diff2htmlUi = new global.Diff2HtmlUI(diffString, targetElement, configuration); // or -var diff2htmlUi = new Diff2HtmlUI({json: diffJson}); +const diff2htmlUi = new global.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 - - + + - - - - + ``` -> 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 global.Diff2HtmlUI(diffString, targetElement, configuration); + diff2htmlUi.draw(); + diff2htmlUi.highlightCode(); }); ``` @@ -282,17 +289,18 @@ $(document).ready(function() { ```html - - + ``` > 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 global.Diff2HtmlUI(lineDiffExample, targetElement, { drawFileList: true, matching: "lines" }); + diff2htmlUi.draw(); + diff2htmlUi.fileListToggle(false); }); ``` @@ -301,11 +309,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 +365,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d + This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! diff --git a/package.json b/package.json index 89b0f8f..27263ad 100644 --- a/package.json +++ b/package.json @@ -32,12 +32,12 @@ "url": "https://www.github.com/rtfpessoa/diff2html/issues" }, "engines": { - "node": ">=4" + "node": "8.* || >=10" }, "scripts": { "lint": "eslint '*/**/*.{js,jsx,ts,tsx}'", "style": "yarn run lint", - "test": "jest", + "test": "yarn run build-scripts && yarn run build-templates && jest", "coverage": "jest --collectCoverage", "coverage-html": "yarn run coverage && open ./coverage/index.html", "codacy": "cat ./coverage/lcov.info | codacy-coverage", diff --git a/scripts/build-browser-bundle.sh b/scripts/build-browser-bundle.sh index 6eb2cc3..c701f17 100755 --- a/scripts/build-browser-bundle.sh +++ b/scripts/build-browser-bundle.sh @@ -22,13 +22,13 @@ rm -rf ${OUTPUT_DIR} mkdir -p ${OUTPUT_DIR} echo "Generating js aggregation file in ${OUTPUT_JS_FILE}" -browserify -e ${INPUT_JS_FILE} -o ${OUTPUT_JS_FILE} +browserify -e ${INPUT_JS_FILE} -o ${OUTPUT_JS_FILE} -s global echo "Minifying ${OUTPUT_JS_FILE} to ${OUTPUT_MIN_JS_FILE}" terser ${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} +browserify -e ${INPUT_JS_UI_FILE} -o ${OUTPUT_JS_UI_FILE} -s global echo "Minifying ${OUTPUT_JS_UI_FILE} to ${OUTPUT_MIN_JS_UI_FILE}" terser ${OUTPUT_JS_UI_FILE} -c -o ${OUTPUT_MIN_JS_UI_FILE} diff --git a/scripts/build-website.ts b/scripts/build-website.ts index 1114fcf..1c17f51 100644 --- a/scripts/build-website.ts +++ b/scripts/build-website.ts @@ -12,9 +12,7 @@ type OptionsType = { const templatesRoot = "website/templates"; const pagesRoot = `${templatesRoot}/pages`; const options: OptionsType = { - all: { - demoUrl: "demo.html?diff=https://github.com/rtfpessoa/diff2html/pull/106" - }, + all: {}, demo: { extraClass: "template-index-min" } diff --git a/scripts/hulk.ts b/scripts/hulk.ts index f078abe..f3e4c4a 100755 --- a/scripts/hulk.ts +++ b/scripts/hulk.ts @@ -172,7 +172,7 @@ function namespace(name: string): string { // Write a template foreach file that matches template extension const templates = extractFiles(options.argv.remain) .map(file => { - const timmedFileContents = fs.readFileSync(file, "utf-8").trim(); + const timmedFileContents = fs.readFileSync(file, "utf8").trim(); if (!timmedFileContents) return; diff --git a/src/__tests__/diff-parser-tests.ts b/src/__tests__/diff-parser-tests.ts index 34a8db3..204282c 100644 --- a/src/__tests__/diff-parser-tests.ts +++ b/src/__tests__/diff-parser-tests.ts @@ -415,7 +415,7 @@ describe("DiffParser", () => { "+test2r\n" ]; - diffs.forEach(function(diff) { + diffs.forEach(diff => { const result = parse(diff); const file1 = result[0]; expect(result.length).toEqual(1); diff --git a/src/__tests__/diff2html-tests.ts b/src/__tests__/diff2html-tests.ts index e859b05..4b74110 100644 --- a/src/__tests__/diff2html-tests.ts +++ b/src/__tests__/diff2html-tests.ts @@ -256,32 +256,32 @@ describe("Diff2Html", () => { }); it("should generate pretty line by line html from diff", () => { - const result = html(diffExample1); + const result = html(diffExample1, { drawFileList: false }); expect(result).toEqual(htmlLineExample1); }); it("should generate pretty line by line html from json", () => { - const result = html(jsonExample1); + const result = html(jsonExample1, { drawFileList: false }); expect(result).toEqual(htmlLineExample1); }); it("should generate pretty diff with files summary", () => { - const result = html(diffExample1, { showFiles: true }); + const result = html(diffExample1, { drawFileList: true }); expect(result).toEqual(htmlLineExample1WithFilesSummary); }); it("should generate pretty side by side html from diff", () => { - const result = html(diffExample1, { outputFormat: "side-by-side" }); + const result = html(diffExample1, { outputFormat: "side-by-side", drawFileList: false }); expect(result).toEqual(htmlSideExample1); }); it("should generate pretty side by side html from json", () => { - const result = html(jsonExample1, { outputFormat: "side-by-side" }); + const result = html(jsonExample1, { outputFormat: "side-by-side", drawFileList: false }); expect(result).toEqual(htmlSideExample1); }); it("should generate pretty side by side html from diff 2", () => { - const result = html(diffExample1, { outputFormat: "side-by-side", showFiles: true }); + const result = html(diffExample1, { outputFormat: "side-by-side", drawFileList: true }); expect(result).toEqual(htmlSideExample1WithFilesSummary); }); @@ -518,7 +518,7 @@ describe("Diff2Html", () => { "\n" + ""; - const result = html(diffExample2); + const result = html(diffExample2, { drawFileList: false }); expect(htmlExample2).toEqual(result); }); }); diff --git a/src/diff-parser.ts b/src/diff-parser.ts index 1410109..872e765 100644 --- a/src/diff-parser.ts +++ b/src/diff-parser.ts @@ -254,7 +254,7 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil return false; } - diffLines.forEach(function(line, lineIndex) { + 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 @@ -340,8 +340,9 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil } if ( - (currentFile && line.startsWith(hunkHeaderPrefix)) || - (currentFile && currentFile.isGitDiff && currentFile.oldName && currentFile.newName && !currentBlock) + currentFile && + (line.startsWith(hunkHeaderPrefix) || + (currentFile.isGitDiff && currentFile.oldName && currentFile.newName && !currentBlock)) ) { startBlock(line); return; @@ -403,7 +404,7 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil currentFile.oldName = getFilename(values[1], undefined, config.srcPrefix); currentFile.newName = getFilename(values[2], undefined, config.dstPrefix); startBlock("Binary file"); - } else if ((values = binaryDiff.exec(line))) { + } else if (binaryDiff.test(line)) { currentFile.isBinary = true; startBlock(line); } else if ((values = similarityIndex.exec(line))) { diff --git a/src/diff2html.ts b/src/diff2html.ts index 7154258..3861bb2 100644 --- a/src/diff2html.ts +++ b/src/diff2html.ts @@ -13,14 +13,14 @@ export interface Diff2HtmlConfig SideBySideRendererConfig, HoganJsUtilsConfig { outputFormat?: OutputFormatType; - showFiles?: boolean; + drawFileList?: boolean; } export const defaultDiff2HtmlConfig = { ...defaultLineByLineRendererConfig, ...defaultSideBySideRendererConfig, outputFormat: "line-by-line" as OutputFormatType, - showFiles: false + drawFileList: true }; export function parse(diffInput: string, configuration: Diff2HtmlConfig = {}): DiffFile[] { @@ -34,7 +34,7 @@ export function html(diffInput: string | DiffFile[], configuration: Diff2HtmlCon const hoganUtils = new HoganJsUtils(config); - const fileList = config.showFiles ? fileListPrinter.render(diffJson, hoganUtils) : ""; + const fileList = config.drawFileList ? fileListPrinter.render(diffJson, hoganUtils) : ""; const diffOutput = config.outputFormat === "side-by-side" diff --git a/src/line-by-line-renderer.ts b/src/line-by-line-renderer.ts index 339322d..062e8f0 100644 --- a/src/line-by-line-renderer.ts +++ b/src/line-by-line-renderer.ts @@ -10,10 +10,10 @@ export interface LineByLineRendererConfig extends renderUtils.RenderConfig { } export const defaultLineByLineRendererConfig = { + ...renderUtils.defaultRenderConfig, renderNothingWhenEmpty: false, matchingMaxComparisons: 2500, - maxLineSizeInBlockForComparison: 200, - ...renderUtils.defaultRenderConfig + maxLineSizeInBlockForComparison: 200 }; const genericTemplatesPath = "generic"; diff --git a/src/ui/js/diff2html-ui.ts b/src/ui/js/diff2html-ui.ts index 5533807..15a4932 100644 --- a/src/ui/js/diff2html-ui.ts +++ b/src/ui/js/diff2html-ui.ts @@ -3,16 +3,24 @@ import * as HighlightJSInternals from "./highlight.js-internals"; import { html, Diff2HtmlConfig, defaultDiff2HtmlConfig } from "../../diff2html"; import { DiffFile } from "../../render-utils"; -interface Diff2HtmlUIConfig extends Diff2HtmlConfig { +export interface Diff2HtmlUIConfig extends Diff2HtmlConfig { synchronisedScroll?: boolean; + highlight?: boolean; + fileListToggle?: boolean; + fileListStartVisible?: boolean; + smartSelection?: boolean; } -const defaultDiff2HtmlUIConfig = { +export const defaultDiff2HtmlUIConfig = { ...defaultDiff2HtmlConfig, - synchronisedScroll: true + synchronisedScroll: true, + highlight: true, + fileListToggle: true, + fileListStartVisible: false, + smartSelection: true }; -export default class Diff2HtmlUI { +export class Diff2HtmlUI { readonly config: typeof defaultDiff2HtmlUIConfig; readonly diffHtml: string; targetElement: HTMLElement; @@ -26,32 +34,32 @@ export default class Diff2HtmlUI { draw(): void { this.targetElement.innerHTML = this.diffHtml; - this.initSelection(); + 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); }); } - fileListCloseable(startVisible: boolean): void { + fileListToggle(startVisible: boolean): void { const hashTag = this.getHashTag(); const showBtn = this.targetElement.querySelector(".d2h-show") as HTMLElement; @@ -61,13 +69,13 @@ export default class Diff2HtmlUI { if (showBtn === null || hideBtn === null || fileList === null) return; function show(): void { - showBtn.style.display = ""; - hideBtn.style.display = ""; - fileList.style.display = ""; + showBtn.style.display = "none"; + hideBtn.style.display = "inline"; + fileList.style.display = "block"; } function hide(): void { - showBtn.style.display = "none"; + showBtn.style.display = "inline"; hideBtn.style.display = "none"; fileList.style.display = "none"; } @@ -204,7 +212,7 @@ export default class Diff2HtmlUI { nodes.forEach((tr, i) => { const td = tr.cells[tr.cells.length === 1 ? 0 : idx]; - if (td === null || td.textContent === null) return; + if (td === undefined || td.textContent === null) return; text += (i ? "\n" : "") + td.textContent.replace(/(?:\r\n|\r|\n)/g, ""); }); @@ -213,8 +221,3 @@ export default class Diff2HtmlUI { return text; } } - -// TODO: Avoid disabling types -// eslint-disable-next-line -// @ts-ignore -global.Diff2HtmlUI = Diff2HtmlUI; diff --git a/src/utils.ts b/src/utils.ts index d9d829b..986bb72 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -48,7 +48,7 @@ export function escapeForHtml(str: string): string { * Converts all '\' in @path to unix style '/' */ export function unifyPath(path: string): string { - return path ? path.replace("\\", "/") : path; + return path ? path.replace(/\\/g, "/") : path; } /** diff --git a/website/templates/pages/demo/demo.js b/website/templates/pages/demo/demo.js index 879822b..14f583f 100644 --- a/website/templates/pages/demo/demo.js +++ b/website/templates/pages/demo/demo.js @@ -1,4 +1,4 @@ -/* global Diff2HtmlUI */ +/* global global */ /* eslint-disable @typescript-eslint/explicit-function-return-type */ /* @@ -16,33 +16,33 @@ const searchParam = "diff"; -function getUrlFromSearch(search) { - try { - return search - .split("?")[1] - .split(searchParam + "=")[1] - .split("&")[0]; - } catch (_ignore) {} - - return null; -} - function getParamsFromSearch(search) { - const map = new Map(); + const map = {}; try { search .split("?")[1] .split("&") .forEach(e => { const values = e.split("="); - map.set(values[0], values[1]); + map[values[0]] = values[1]; }); } catch (_ignore) {} return map; } -function prepareUrl(url) { +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 prepareRequest(url) { + if (!validateUrl(url)) { + console.error("Invalid url provided!"); + return; + } + let fetchUrl; const headers = new Headers(); @@ -93,150 +93,152 @@ function prepareUrl(url) { } return { - originalUrl: url, url: fetchUrl, headers: headers }; } -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 getConfiguration(urlParams) { + // Removing `diff` form `urlParams` to avoid being inserted + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { diff, ...urlParamsRest } = urlParams; + const config = { + ...global.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 }; + }, {}); } -function updateUrl(url) { - const params = getParamsFromSearch(window.location.search); - - if (params[searchParam] === url) return; - - params[searchParam] = url; - - const paramString = Object.keys(params) - .map(function(k) { - return k + "=" + params[k]; - }) - .join("&"); - - window.location = "demo.html?" + paramString; -} - -function draw(req, forced, elements) { - if (!validateUrl(req.url)) { - console.error("Invalid url provided!"); - return; - } - - if (validateUrl(req.originalUrl)) updateUrl(req.originalUrl); - - const outputFormat = elements.outputFormat.val(); - const showFiles = elements.showFiles.is(":checked"); - const matching = elements.matching.val(); - const wordsThreshold = elements.wordsThreshold.val(); - const matchingMaxComparisons = elements.matchingMaxComparisons.val(); - - fetch(req.url, { +function getDiff(request) { + return fetch(request.url, { method: "GET", - headers: req.headers, + headers: request.headers, mode: "cors", cache: "default" }) - .then(function(res) { + .then(res => { return res.text(); }) - .then(function(data) { - const 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; - - elements.outputFormat.value = params.outputFormat; - elements.showFiles.setAttribute("checked", params.showFiles); - elements.matching.value = params.matching; - elements.wordsThreshold.value = params.wordsThreshold; - elements.matchingMaxComparisons.value = params.matchingMaxComparisons; - } - - params.synchronisedScroll = params.synchronisedScroll || true; - - const diff2htmlUi = new Diff2HtmlUI(data, elements.root); - - if (outputFormat === "side-by-side") { - elements.container.css({ width: "100%" }); - } else { - elements.container.css({ width: "" }); - } - - diff2htmlUi.draw(); - diff2htmlUi.fileListCloseable(params.fileListCloseable || false); - if (params.highlight === undefined || params.highlight) { - diff2htmlUi.highlightCode(); - } - - return undefined; - }) - .catch(() => {}); + .catch(error => console.error("Failed to retrieve diff", error)); } -function smartDraw(urlOpt, urlElem, forced) { - const url = urlOpt || urlElem.val(); - const req = prepareUrl(url); - draw(req, forced); +function draw(diffString, config, elements) { + const diff2htmlUi = new global.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(); } -function bind(urlElem) { - $("#url-btn").click(e => { - e.preventDefault(); - const url = urlElem.val(); - smartDraw(url, urlElem); - }); +async function prepareInitialState(elements) { + const urlParams = getParamsFromSearch(window.location.search); + const currentUrl = (urlParams && urlParams[searchParam]) || "https://github.com/rtfpessoa/diff2html/pull/106"; - urlElem.on("paste", e => { - const url = e.originalEvent.clipboardData.getData("Text"); - smartDraw(url, urlElem); - }); + 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]; } -document.addEventListener("DOMContentLoaded", function() { +function updateBrowserUrl(config, newDiffUrl) { + 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); + } +} + +document.addEventListener("DOMContentLoaded", async () => { // Improves browser compatibility require("whatwg-fetch"); - const elements = { - root: document.getElementById("url-diff-container"), - container: document.getElementsByClassName("container"), - url: document.getElementById("url"), - outputFormat: document.getElementById("diff-url-options-output-format"), - showFiles: document.getElementById("diff-url-options-show-files"), - matching: document.getElementById("diff-url-options-matching"), - wordsThreshold: document.getElementById("diff-url-options-match-words-threshold"), - matchingMaxComparisons: document.getElementById("diff-url-options-matching-max-comparisons") + const drawAndUpdateUrl = async (diffUrl, diffString, config, elements) => { + updateBrowserUrl(config, diffUrl); + const newRequest = prepareRequest(diffUrl); + diffString = await getDiff(newRequest); + draw(diffString, config, elements); }; - if (window.location.search) { - const url = getUrlFromSearch(window.location.search); - elements.url.val(url); - smartDraw(url, elements.url); - } + const elements = { + structure: { + container: document.getElementsByClassName("container")[0], + diffTarget: document.getElementById("url-diff-container") + }, + url: { + input: document.getElementById("url"), + button: document.getElementById("url-btn") + }, + options: { + outputFormat: document.getElementById("diff-url-options-output-format"), + matching: document.getElementById("diff-url-options-matching"), + wordsThreshold: document.getElementById("diff-url-options-match-words-threshold"), + matchingMaxComparisons: document.getElementById("diff-url-options-matching-max-comparisons") + }, + checkboxes: { + drawFileList: document.getElementById("diff-url-options-show-files") + } + }; - bind(); + let [config, diffString] = await prepareInitialState(elements); - elements.outputFormat - .add(elements.showFiles) - .add(elements.matching) - .add(elements.wordsThreshold) - .add(elements.matchingMaxComparisons) - .change(() => smartDraw(null, elements.url, true)); + // Update HTML inputs from any changes in URL + elements.options.outputFormat.value = config.outputFormat; + elements.checkboxes.drawFileList.checked = config.drawFileList; + elements.options.matching.value = config.matching; + elements.options.wordsThreshold.value = config.wordsThreshold; + elements.options.matchingMaxComparisons.value = config.matchingMaxComparisons; + + Object.entries(elements.options).forEach(([option, element]) => + element.addEventListener("change", () => { + config[option] = element.value; + drawAndUpdateUrl(elements.url.input.value, diffString, config, elements); + }) + ); + + Object.entries(elements.checkboxes).forEach(([option, checkbox]) => + checkbox.addEventListener("change", () => { + 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); }); /* eslint-enable @typescript-eslint/explicit-function-return-type */ diff --git a/website/templates/pages/demo/demo.partial.mustache b/website/templates/pages/demo/demo.partial.mustache index 1797063..5506027 100644 --- a/website/templates/pages/demo/demo.partial.mustache +++ b/website/templates/pages/demo/demo.partial.mustache @@ -22,7 +22,7 @@
@@ -78,7 +78,7 @@ Can I send a custom url for a friend, colleague or co-worker?

Just add a url parameter called diff to current url using as value your Commit, Pull Request, Merge Request, Diff or Patch url.

-

ex: https://diff2html.xyz/{{ demoUrl }} +

ex: https://diff2html.xyz/demo.html

  • diff --git a/website/templates/pages/index/index.partial.mustache b/website/templates/pages/index/index.partial.mustache index 42b7744..a45762a 100644 --- a/website/templates/pages/index/index.partial.mustache +++ b/website/templates/pages/index/index.partial.mustache @@ -5,17 +5,17 @@

    Diff parser and pretty html generator

    Better diffs, unmatched reviews.

    -

    Demo

    +

    Demo

    - + - diff --git a/website/templates/template.mustache b/website/templates/template.mustache index 4052c64..2981af1 100644 --- a/website/templates/template.mustache +++ b/website/templates/template.mustache @@ -45,7 +45,6 @@ ga('create', 'UA-78351861-2', 'auto'); ga('send', 'pageview'); - @@ -76,7 +75,7 @@
  • - Demo + Demo
  • @@ -121,11 +120,6 @@
  • - - -