refactor: Unify escaping
This commit is contained in:
parent
f8f5c10c57
commit
0f08c85938
7 changed files with 188 additions and 200 deletions
|
|
@ -299,7 +299,7 @@ describe("Diff2Html", () => {
|
||||||
' $a="<table><tr><td>\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" +
|
" $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" +
|
" - 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" +
|
"@@ -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" +
|
" - 1.1.7:\n" +
|
||||||
" - Fix diff flickering issue and optimization [#865](https://github.com/FredrikNoren/ungit/pull/865)\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 credential dialog issue [#864](https://github.com/FredrikNoren/ungit/pull/864)\n" +
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,50 @@
|
||||||
import * as renderUtils from "../render-utils";
|
import { escapeForHtml, getHtmlId, filenameDiff, diffHighlight } from "../render-utils";
|
||||||
import { DiffStyleType, LineMatchingType } from "../types";
|
import { DiffStyleType, LineMatchingType } from "../types";
|
||||||
|
|
||||||
describe("Utils", () => {
|
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", () => {
|
describe("getHtmlId", () => {
|
||||||
it("should generate file unique id", () => {
|
it("should generate file unique id", () => {
|
||||||
const result = renderUtils.getHtmlId({
|
const result = getHtmlId({
|
||||||
oldName: "sample.js",
|
oldName: "sample.js",
|
||||||
newName: "sample.js"
|
newName: "sample.js"
|
||||||
});
|
});
|
||||||
expect("d2h-960013").toEqual(result);
|
expect("d2h-960013").toEqual(result);
|
||||||
});
|
});
|
||||||
it("should generate file unique id for empty hashes", () => {
|
it("should generate file unique id for empty hashes", () => {
|
||||||
const result = renderUtils.getHtmlId({
|
const result = getHtmlId({
|
||||||
oldName: "sample.js",
|
oldName: "sample.js",
|
||||||
newName: "sample.js"
|
newName: "sample.js"
|
||||||
});
|
});
|
||||||
|
|
@ -21,49 +54,49 @@ describe("Utils", () => {
|
||||||
|
|
||||||
describe("getDiffName", () => {
|
describe("getDiffName", () => {
|
||||||
it("should generate the file name for a changed file", () => {
|
it("should generate the file name for a changed file", () => {
|
||||||
const result = renderUtils.filenameDiff({
|
const result = filenameDiff({
|
||||||
oldName: "sample.js",
|
oldName: "sample.js",
|
||||||
newName: "sample.js"
|
newName: "sample.js"
|
||||||
});
|
});
|
||||||
expect("sample.js").toEqual(result);
|
expect("sample.js").toEqual(result);
|
||||||
});
|
});
|
||||||
it("should generate the file name for a changed file and full rename", () => {
|
it("should generate the file name for a changed file and full rename", () => {
|
||||||
const result = renderUtils.filenameDiff({
|
const result = filenameDiff({
|
||||||
oldName: "sample1.js",
|
oldName: "sample1.js",
|
||||||
newName: "sample2.js"
|
newName: "sample2.js"
|
||||||
});
|
});
|
||||||
expect("sample1.js → sample2.js").toEqual(result);
|
expect("sample1.js → sample2.js").toEqual(result);
|
||||||
});
|
});
|
||||||
it("should generate the file name for a changed file and prefix rename", () => {
|
it("should generate the file name for a changed file and prefix rename", () => {
|
||||||
const result = renderUtils.filenameDiff({
|
const result = filenameDiff({
|
||||||
oldName: "src/path/sample.js",
|
oldName: "src/path/sample.js",
|
||||||
newName: "source/path/sample.js"
|
newName: "source/path/sample.js"
|
||||||
});
|
});
|
||||||
expect("{src → source}/path/sample.js").toEqual(result);
|
expect("{src → source}/path/sample.js").toEqual(result);
|
||||||
});
|
});
|
||||||
it("should generate the file name for a changed file and suffix rename", () => {
|
it("should generate the file name for a changed file and suffix rename", () => {
|
||||||
const result = renderUtils.filenameDiff({
|
const result = filenameDiff({
|
||||||
oldName: "src/path/sample1.js",
|
oldName: "src/path/sample1.js",
|
||||||
newName: "src/path/sample2.js"
|
newName: "src/path/sample2.js"
|
||||||
});
|
});
|
||||||
expect("src/path/{sample1.js → sample2.js}").toEqual(result);
|
expect("src/path/{sample1.js → sample2.js}").toEqual(result);
|
||||||
});
|
});
|
||||||
it("should generate the file name for a changed file and middle rename", () => {
|
it("should generate the file name for a changed file and middle rename", () => {
|
||||||
const result = renderUtils.filenameDiff({
|
const result = filenameDiff({
|
||||||
oldName: "src/really/big/path/sample.js",
|
oldName: "src/really/big/path/sample.js",
|
||||||
newName: "src/small/path/sample.js"
|
newName: "src/small/path/sample.js"
|
||||||
});
|
});
|
||||||
expect("src/{really/big → small}/path/sample.js").toEqual(result);
|
expect("src/{really/big → small}/path/sample.js").toEqual(result);
|
||||||
});
|
});
|
||||||
it("should generate the file name for a deleted file", () => {
|
it("should generate the file name for a deleted file", () => {
|
||||||
const result = renderUtils.filenameDiff({
|
const result = filenameDiff({
|
||||||
oldName: "src/my/file.js",
|
oldName: "src/my/file.js",
|
||||||
newName: "/dev/null"
|
newName: "/dev/null"
|
||||||
});
|
});
|
||||||
expect("src/my/file.js").toEqual(result);
|
expect("src/my/file.js").toEqual(result);
|
||||||
});
|
});
|
||||||
it("should generate the file name for a new file", () => {
|
it("should generate the file name for a new file", () => {
|
||||||
const result = renderUtils.filenameDiff({
|
const result = filenameDiff({
|
||||||
oldName: "/dev/null",
|
oldName: "/dev/null",
|
||||||
newName: "src/my/file.js"
|
newName: "src/my/file.js"
|
||||||
});
|
});
|
||||||
|
|
@ -73,7 +106,7 @@ describe("Utils", () => {
|
||||||
|
|
||||||
describe("diffHighlight", () => {
|
describe("diffHighlight", () => {
|
||||||
it("should highlight two lines", () => {
|
it("should highlight two lines", () => {
|
||||||
const result = renderUtils.diffHighlight("-var myVar = 2;", "+var myVariable = 3;", false, {
|
const result = diffHighlight("-var myVar = 2;", "+var myVariable = 3;", false, {
|
||||||
matching: LineMatchingType.WORDS
|
matching: LineMatchingType.WORDS
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -89,7 +122,7 @@ describe("Utils", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it("should highlight two lines char by char", () => {
|
it("should highlight two lines char by char", () => {
|
||||||
const result = renderUtils.diffHighlight("-var myVar = 2;", "+var myVariable = 3;", false, {
|
const result = diffHighlight("-var myVar = 2;", "+var myVariable = 3;", false, {
|
||||||
diffStyle: DiffStyleType.CHAR
|
diffStyle: DiffStyleType.CHAR
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -105,7 +138,7 @@ describe("Utils", () => {
|
||||||
}).toEqual(result);
|
}).toEqual(result);
|
||||||
});
|
});
|
||||||
it("should highlight combined diff lines", () => {
|
it("should highlight combined diff lines", () => {
|
||||||
const result = renderUtils.diffHighlight(" -var myVar = 2;", " +var myVariable = 3;", true, {
|
const result = diffHighlight(" -var myVar = 2;", " +var myVariable = 3;", true, {
|
||||||
diffStyle: DiffStyleType.WORD,
|
diffStyle: DiffStyleType.WORD,
|
||||||
matching: LineMatchingType.WORDS,
|
matching: LineMatchingType.WORDS,
|
||||||
matchWordsThreshold: 1.0
|
matchWordsThreshold: 1.0
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { escapeForRegExp, escapeForHtml, unifyPath, hashCode } from "../utils";
|
import { escapeForRegExp, unifyPath, hashCode } from "../utils";
|
||||||
|
|
||||||
describe("Utils", () => {
|
describe("Utils", () => {
|
||||||
describe("escapeForRegExp", () => {
|
describe("escapeForRegExp", () => {
|
||||||
|
|
@ -12,53 +12,20 @@ describe("Utils", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("escapeForHtml", () => {
|
describe("unifyPath", () => {
|
||||||
it("should escape & with &", () => {
|
it("should unify windows style path", () => {
|
||||||
const result = escapeForHtml("&");
|
const result = unifyPath("\\Users\\Downloads\\diff.html");
|
||||||
expect(result).toEqual("&");
|
expect(result).toEqual("/Users/Downloads/diff.html");
|
||||||
});
|
|
||||||
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("unifyPath", () => {
|
describe("hashCode", () => {
|
||||||
it("should unify windows style path", () => {
|
it("should create consistent hash for a text piece", () => {
|
||||||
const result = unifyPath("\\Users\\Downloads\\diff.html");
|
const string = "/home/diff2html/diff.html";
|
||||||
expect(result).toEqual("/Users/Downloads/diff.html");
|
expect(hashCode(string)).toEqual(hashCode(string));
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
it("should create different hash for different text pieces", () => {
|
||||||
describe("hashCode", () => {
|
expect(hashCode("/home/diff2html/diff1.html")).not.toEqual(hashCode("/home/diff2html/diff2.html"));
|
||||||
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,8 +1,7 @@
|
||||||
import * as utils from "./utils";
|
|
||||||
import HoganJsUtils from "./hoganjs-utils";
|
import HoganJsUtils from "./hoganjs-utils";
|
||||||
import * as Rematch from "./rematch";
|
import * as Rematch from "./rematch";
|
||||||
import * as renderUtils from "./render-utils";
|
import * as renderUtils from "./render-utils";
|
||||||
import { DiffFile, DiffBlock, DiffLine, LineType } from "./types";
|
import { DiffFile, DiffLine, LineType } from "./types";
|
||||||
|
|
||||||
export interface LineByLineRendererConfig extends renderUtils.RenderConfig {
|
export interface LineByLineRendererConfig extends renderUtils.RenderConfig {
|
||||||
renderNothingWhenEmpty?: boolean;
|
renderNothingWhenEmpty?: boolean;
|
||||||
|
|
@ -72,53 +71,6 @@ export default class LineByLineRenderer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Make this private after improving tests
|
|
||||||
makeColumnLineNumberHtml(block: DiffBlock): string {
|
|
||||||
return this.hoganUtils.render(genericTemplatesPath, "column-line-number", {
|
|
||||||
CSSLineClass: renderUtils.CSSLineClass,
|
|
||||||
blockHeader: utils.escapeForHtml(block.header),
|
|
||||||
lineClass: "d2h-code-linenumber",
|
|
||||||
contentClass: "d2h-code-line"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Make this private after improving tests
|
|
||||||
makeLineHtml(
|
|
||||||
isCombined: boolean,
|
|
||||||
type: renderUtils.CSSLineClass,
|
|
||||||
content: string,
|
|
||||||
oldNumber?: number,
|
|
||||||
newNumber?: number,
|
|
||||||
possiblePrefix?: string
|
|
||||||
): string {
|
|
||||||
const lineNumberTemplate = this.hoganUtils.render(baseTemplatesPath, "numbers", {
|
|
||||||
oldNumber: oldNumber || "",
|
|
||||||
newNumber: newNumber || ""
|
|
||||||
});
|
|
||||||
|
|
||||||
let lineWithoutPrefix = content;
|
|
||||||
let prefix = possiblePrefix;
|
|
||||||
|
|
||||||
if (!prefix) {
|
|
||||||
const lineWithPrefix = renderUtils.deconstructLine(content, isCombined);
|
|
||||||
prefix = lineWithPrefix.prefix;
|
|
||||||
lineWithoutPrefix = lineWithPrefix.content;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prefix === " ") {
|
|
||||||
prefix = " ";
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.hoganUtils.render(genericTemplatesPath, "line", {
|
|
||||||
type: type,
|
|
||||||
lineClass: "d2h-code-linenumber",
|
|
||||||
contentClass: "d2h-code-line",
|
|
||||||
prefix: prefix,
|
|
||||||
content: lineWithoutPrefix,
|
|
||||||
lineNumber: lineNumberTemplate
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Make this private after improving tests
|
// TODO: Make this private after improving tests
|
||||||
generateEmptyDiff(): string {
|
generateEmptyDiff(): string {
|
||||||
return this.hoganUtils.render(genericTemplatesPath, "empty-diff", {
|
return this.hoganUtils.render(genericTemplatesPath, "empty-diff", {
|
||||||
|
|
@ -127,47 +79,20 @@ export default class LineByLineRenderer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Make this private after improving tests
|
|
||||||
processLines(isCombined: boolean, oldLines: DiffLine[], newLines: DiffLine[]): string {
|
|
||||||
let lines = "";
|
|
||||||
|
|
||||||
for (let i = 0; i < oldLines.length; i++) {
|
|
||||||
const oldLine = oldLines[i];
|
|
||||||
const oldEscapedLine = utils.escapeForHtml(oldLine.content);
|
|
||||||
lines += this.makeLineHtml(
|
|
||||||
isCombined,
|
|
||||||
renderUtils.toCSSClass(oldLine.type),
|
|
||||||
oldEscapedLine,
|
|
||||||
oldLine.oldNumber,
|
|
||||||
oldLine.newNumber
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let j = 0; j < newLines.length; j++) {
|
|
||||||
const newLine = newLines[j];
|
|
||||||
const newEscapedLine = utils.escapeForHtml(newLine.content);
|
|
||||||
lines += this.makeLineHtml(
|
|
||||||
isCombined,
|
|
||||||
renderUtils.toCSSClass(newLine.type),
|
|
||||||
newEscapedLine,
|
|
||||||
newLine.oldNumber,
|
|
||||||
newLine.newNumber
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Make this private after improving tests
|
// TODO: Make this private after improving tests
|
||||||
generateFileHtml(file: DiffFile): string {
|
generateFileHtml(file: DiffFile): string {
|
||||||
const distance = Rematch.newDistanceFn(
|
const matcher = Rematch.newMatcherFn(
|
||||||
(e: DiffLine) => renderUtils.deconstructLine(e.content, file.isCombined).content
|
Rematch.newDistanceFn((e: DiffLine) => renderUtils.deconstructLine(e.content, file.isCombined).content)
|
||||||
);
|
);
|
||||||
const matcher = Rematch.newMatcherFn(distance);
|
|
||||||
|
|
||||||
return file.blocks
|
return file.blocks
|
||||||
.map(block => {
|
.map(block => {
|
||||||
let lines = this.makeColumnLineNumberHtml(block);
|
let lines = this.hoganUtils.render(genericTemplatesPath, "column-line-number", {
|
||||||
|
CSSLineClass: renderUtils.CSSLineClass,
|
||||||
|
blockHeader: block.header,
|
||||||
|
lineClass: "d2h-code-linenumber",
|
||||||
|
contentClass: "d2h-code-line"
|
||||||
|
});
|
||||||
let oldLines: DiffLine[] = [];
|
let oldLines: DiffLine[] = [];
|
||||||
let newLines: DiffLine[] = [];
|
let newLines: DiffLine[] = [];
|
||||||
|
|
||||||
|
|
@ -242,8 +167,7 @@ export default class LineByLineRenderer {
|
||||||
|
|
||||||
for (let i = 0; i < block.lines.length; i++) {
|
for (let i = 0; i < block.lines.length; i++) {
|
||||||
const diffLine = block.lines[i];
|
const diffLine = block.lines[i];
|
||||||
const { prefix, content: line } = renderUtils.deconstructLine(diffLine.content, file.isCombined);
|
const { prefix, content } = renderUtils.deconstructLine(diffLine.content, file.isCombined);
|
||||||
const escapedLine = utils.escapeForHtml(line);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
diffLine.type !== LineType.INSERT &&
|
diffLine.type !== LineType.INSERT &&
|
||||||
|
|
@ -256,7 +180,7 @@ export default class LineByLineRenderer {
|
||||||
lines += this.makeLineHtml(
|
lines += this.makeLineHtml(
|
||||||
file.isCombined,
|
file.isCombined,
|
||||||
renderUtils.toCSSClass(diffLine.type),
|
renderUtils.toCSSClass(diffLine.type),
|
||||||
escapedLine,
|
content,
|
||||||
diffLine.oldNumber,
|
diffLine.oldNumber,
|
||||||
diffLine.newNumber,
|
diffLine.newNumber,
|
||||||
prefix
|
prefix
|
||||||
|
|
@ -265,7 +189,7 @@ export default class LineByLineRenderer {
|
||||||
lines += this.makeLineHtml(
|
lines += this.makeLineHtml(
|
||||||
file.isCombined,
|
file.isCombined,
|
||||||
renderUtils.toCSSClass(diffLine.type),
|
renderUtils.toCSSClass(diffLine.type),
|
||||||
escapedLine,
|
content,
|
||||||
diffLine.oldNumber,
|
diffLine.oldNumber,
|
||||||
diffLine.newNumber,
|
diffLine.newNumber,
|
||||||
prefix
|
prefix
|
||||||
|
|
@ -286,4 +210,70 @@ export default class LineByLineRenderer {
|
||||||
})
|
})
|
||||||
.join("\n");
|
.join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Make this private after improving tests
|
||||||
|
makeLineHtml(
|
||||||
|
isCombined: boolean,
|
||||||
|
type: renderUtils.CSSLineClass,
|
||||||
|
content: string,
|
||||||
|
oldNumber?: number,
|
||||||
|
newNumber?: number,
|
||||||
|
possiblePrefix?: string
|
||||||
|
): string {
|
||||||
|
const lineNumberTemplate = this.hoganUtils.render(baseTemplatesPath, "numbers", {
|
||||||
|
oldNumber: oldNumber || "",
|
||||||
|
newNumber: newNumber || ""
|
||||||
|
});
|
||||||
|
|
||||||
|
let lineWithoutPrefix = content;
|
||||||
|
let prefix = possiblePrefix;
|
||||||
|
|
||||||
|
if (!prefix) {
|
||||||
|
const lineWithPrefix = renderUtils.deconstructLine(content, isCombined);
|
||||||
|
prefix = lineWithPrefix.prefix;
|
||||||
|
lineWithoutPrefix = lineWithPrefix.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prefix === " ") {
|
||||||
|
prefix = " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.hoganUtils.render(genericTemplatesPath, "line", {
|
||||||
|
type: type,
|
||||||
|
lineClass: "d2h-code-linenumber",
|
||||||
|
contentClass: "d2h-code-line",
|
||||||
|
prefix: prefix,
|
||||||
|
content: lineWithoutPrefix,
|
||||||
|
lineNumber: lineNumberTemplate
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Make this private after improving tests
|
||||||
|
processLines(isCombined: boolean, oldLines: DiffLine[], newLines: DiffLine[]): string {
|
||||||
|
let lines = "";
|
||||||
|
|
||||||
|
for (let i = 0; i < oldLines.length; i++) {
|
||||||
|
const oldLine = oldLines[i];
|
||||||
|
lines += this.makeLineHtml(
|
||||||
|
isCombined,
|
||||||
|
renderUtils.toCSSClass(oldLine.type),
|
||||||
|
oldLine.content,
|
||||||
|
oldLine.oldNumber,
|
||||||
|
oldLine.newNumber
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let j = 0; j < newLines.length; j++) {
|
||||||
|
const newLine = newLines[j];
|
||||||
|
lines += this.makeLineHtml(
|
||||||
|
isCombined,
|
||||||
|
renderUtils.toCSSClass(newLine.type),
|
||||||
|
newLine.content,
|
||||||
|
newLine.oldNumber,
|
||||||
|
newLine.newNumber
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import * as jsDiff from "diff";
|
import * as jsDiff from "diff";
|
||||||
|
|
||||||
import { unifyPath, escapeForHtml, hashCode } from "./utils";
|
import { unifyPath, hashCode } from "./utils";
|
||||||
import * as rematch from "./rematch";
|
import * as rematch from "./rematch";
|
||||||
import { LineMatchingType, DiffStyleType, LineType, DiffLineParts, DiffFile, DiffFileName } from "./types";
|
import { LineMatchingType, DiffStyleType, LineType, DiffLineParts, DiffFile, DiffFileName } from "./types";
|
||||||
|
|
||||||
|
|
@ -75,6 +75,21 @@ function prefixLength(isCombined: boolean): number {
|
||||||
return isCombined ? 2 : 1;
|
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
|
* Deconstructs diff @line by separating the content from the prefix type
|
||||||
*/
|
*/
|
||||||
|
|
@ -82,7 +97,7 @@ export function deconstructLine(line: string, isCombined: boolean): DiffLinePart
|
||||||
const indexToSplit = prefixLength(isCombined);
|
const indexToSplit = prefixLength(isCombined);
|
||||||
return {
|
return {
|
||||||
prefix: line.substring(0, indexToSplit),
|
prefix: line.substring(0, indexToSplit),
|
||||||
content: line.substring(indexToSplit)
|
content: escapeForHtml(line.substring(indexToSplit))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -209,11 +224,11 @@ export function diffHighlight(
|
||||||
return {
|
return {
|
||||||
oldLine: {
|
oldLine: {
|
||||||
prefix: line1.prefix,
|
prefix: line1.prefix,
|
||||||
content: escapeForHtml(line1.content)
|
content: line1.content
|
||||||
},
|
},
|
||||||
newLine: {
|
newLine: {
|
||||||
prefix: line2.prefix,
|
prefix: line2.prefix,
|
||||||
content: escapeForHtml(line2.content)
|
content: line2.content
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -248,11 +263,10 @@ export function diffHighlight(
|
||||||
const highlightedLine = diff.reduce((highlightedLine, part) => {
|
const highlightedLine = diff.reduce((highlightedLine, part) => {
|
||||||
const elemType = part.added ? "ins" : part.removed ? "del" : null;
|
const elemType = part.added ? "ins" : part.removed ? "del" : null;
|
||||||
const addClass = changedWords.indexOf(part) > -1 ? ' class="d2h-change"' : "";
|
const addClass = changedWords.indexOf(part) > -1 ? ' class="d2h-change"' : "";
|
||||||
const escapedValue = escapeForHtml(part.value);
|
|
||||||
|
|
||||||
return elemType !== null
|
return elemType !== null
|
||||||
? `${highlightedLine}<${elemType}${addClass}>${escapedValue}</${elemType}>`
|
? `${highlightedLine}<${elemType}${addClass}>${part.value}</${elemType}>`
|
||||||
: `${highlightedLine}${escapedValue}`;
|
: `${highlightedLine}${part.value}`;
|
||||||
}, "");
|
}, "");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import * as utils from "./utils";
|
|
||||||
import HoganJsUtils from "./hoganjs-utils";
|
import HoganJsUtils from "./hoganjs-utils";
|
||||||
import * as Rematch from "./rematch";
|
import * as Rematch from "./rematch";
|
||||||
import * as renderUtils from "./render-utils";
|
import * as renderUtils from "./render-utils";
|
||||||
|
|
@ -47,20 +46,10 @@ export default class SideBySideRenderer {
|
||||||
return this.hoganUtils.render(genericTemplatesPath, "wrapper", { content: diffsHtml });
|
return this.hoganUtils.render(genericTemplatesPath, "wrapper", { content: diffsHtml });
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Make this private after improving tests
|
|
||||||
generateEmptyDiff(): FileHtml {
|
|
||||||
return {
|
|
||||||
right: "",
|
|
||||||
left:
|
|
||||||
this.hoganUtils.render(genericTemplatesPath, "empty-diff", {
|
|
||||||
contentClass: "d2h-code-side-line",
|
|
||||||
CSSLineClass: renderUtils.CSSLineClass
|
|
||||||
}) || ""
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Make this private after improving tests
|
// TODO: Make this private after improving tests
|
||||||
makeFileDiffHtml(file: DiffFile, diffs: FileHtml): string {
|
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 fileDiffTemplate = this.hoganUtils.template(baseTemplatesPath, "file-diff");
|
||||||
const filePathTemplate = this.hoganUtils.template(genericTemplatesPath, "file-path");
|
const filePathTemplate = this.hoganUtils.template(genericTemplatesPath, "file-path");
|
||||||
const fileIconTemplate = this.hoganUtils.template(iconsBaseTemplatesPath, "file");
|
const fileIconTemplate = this.hoganUtils.template(iconsBaseTemplatesPath, "file");
|
||||||
|
|
@ -83,21 +72,21 @@ export default class SideBySideRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Make this private after improving tests
|
// TODO: Make this private after improving tests
|
||||||
makeSideHtml(blockHeader: string): string {
|
generateEmptyDiff(): FileHtml {
|
||||||
return this.hoganUtils.render(genericTemplatesPath, "column-line-number", {
|
return {
|
||||||
CSSLineClass: renderUtils.CSSLineClass,
|
right: "",
|
||||||
blockHeader: utils.escapeForHtml(blockHeader),
|
left: this.hoganUtils.render(genericTemplatesPath, "empty-diff", {
|
||||||
lineClass: "d2h-code-side-linenumber",
|
contentClass: "d2h-code-side-line",
|
||||||
contentClass: "d2h-code-side-line"
|
CSSLineClass: renderUtils.CSSLineClass
|
||||||
});
|
})
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Make this private after improving tests
|
// TODO: Make this private after improving tests
|
||||||
generateSideBySideFileHtml(file: DiffFile): FileHtml {
|
generateSideBySideFileHtml(file: DiffFile): FileHtml {
|
||||||
const distance = Rematch.newDistanceFn(
|
const matcher = Rematch.newMatcherFn(
|
||||||
(e: DiffLine) => renderUtils.deconstructLine(e.content, file.isCombined).content
|
Rematch.newDistanceFn((e: DiffLine) => renderUtils.deconstructLine(e.content, file.isCombined).content)
|
||||||
);
|
);
|
||||||
const matcher = Rematch.newMatcherFn(distance);
|
|
||||||
|
|
||||||
const fileHtml = {
|
const fileHtml = {
|
||||||
right: "",
|
right: "",
|
||||||
|
|
@ -183,8 +172,7 @@ export default class SideBySideRenderer {
|
||||||
|
|
||||||
for (let i = 0; i < block.lines.length; i++) {
|
for (let i = 0; i < block.lines.length; i++) {
|
||||||
const diffLine = block.lines[i];
|
const diffLine = block.lines[i];
|
||||||
const { prefix, content: line } = renderUtils.deconstructLine(diffLine.content, file.isCombined);
|
const { prefix, content } = renderUtils.deconstructLine(diffLine.content, file.isCombined);
|
||||||
const escapedLine = utils.escapeForHtml(line);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
diffLine.type !== LineType.INSERT &&
|
diffLine.type !== LineType.INSERT &&
|
||||||
|
|
@ -197,14 +185,14 @@ export default class SideBySideRenderer {
|
||||||
fileHtml.left += this.generateSingleLineHtml(
|
fileHtml.left += this.generateSingleLineHtml(
|
||||||
file.isCombined,
|
file.isCombined,
|
||||||
renderUtils.toCSSClass(diffLine.type),
|
renderUtils.toCSSClass(diffLine.type),
|
||||||
escapedLine,
|
content,
|
||||||
diffLine.oldNumber,
|
diffLine.oldNumber,
|
||||||
prefix
|
prefix
|
||||||
);
|
);
|
||||||
fileHtml.right += this.generateSingleLineHtml(
|
fileHtml.right += this.generateSingleLineHtml(
|
||||||
file.isCombined,
|
file.isCombined,
|
||||||
renderUtils.toCSSClass(diffLine.type),
|
renderUtils.toCSSClass(diffLine.type),
|
||||||
escapedLine,
|
content,
|
||||||
diffLine.newNumber,
|
diffLine.newNumber,
|
||||||
prefix
|
prefix
|
||||||
);
|
);
|
||||||
|
|
@ -213,7 +201,7 @@ export default class SideBySideRenderer {
|
||||||
fileHtml.right += this.generateSingleLineHtml(
|
fileHtml.right += this.generateSingleLineHtml(
|
||||||
file.isCombined,
|
file.isCombined,
|
||||||
renderUtils.toCSSClass(diffLine.type),
|
renderUtils.toCSSClass(diffLine.type),
|
||||||
escapedLine,
|
content,
|
||||||
diffLine.newNumber,
|
diffLine.newNumber,
|
||||||
prefix
|
prefix
|
||||||
);
|
);
|
||||||
|
|
@ -233,6 +221,16 @@ export default class SideBySideRenderer {
|
||||||
return fileHtml;
|
return fileHtml;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Make this private after improving tests
|
||||||
|
makeSideHtml(blockHeader: string): string {
|
||||||
|
return this.hoganUtils.render(genericTemplatesPath, "column-line-number", {
|
||||||
|
CSSLineClass: renderUtils.CSSLineClass,
|
||||||
|
blockHeader: blockHeader,
|
||||||
|
lineClass: "d2h-code-side-linenumber",
|
||||||
|
contentClass: "d2h-code-side-line"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Make this private after improving tests
|
// TODO: Make this private after improving tests
|
||||||
processLines(isCombined: boolean, oldLines: DiffLine[], newLines: DiffLine[]): FileHtml {
|
processLines(isCombined: boolean, oldLines: DiffLine[], newLines: DiffLine[]): FileHtml {
|
||||||
const fileHtml = {
|
const fileHtml = {
|
||||||
|
|
@ -251,8 +249,8 @@ export default class SideBySideRenderer {
|
||||||
let newPrefix;
|
let newPrefix;
|
||||||
|
|
||||||
if (oldLine) {
|
if (oldLine) {
|
||||||
const { prefix, content: line } = renderUtils.deconstructLine(oldLine.content, isCombined);
|
const { prefix, content } = renderUtils.deconstructLine(oldLine.content, isCombined);
|
||||||
oldContent = utils.escapeForHtml(line);
|
oldContent = content;
|
||||||
oldPrefix = prefix;
|
oldPrefix = prefix;
|
||||||
} else {
|
} else {
|
||||||
oldContent = "";
|
oldContent = "";
|
||||||
|
|
@ -260,8 +258,8 @@ export default class SideBySideRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newLine) {
|
if (newLine) {
|
||||||
const { prefix, content: line } = renderUtils.deconstructLine(newLine.content, isCombined);
|
const { prefix, content } = renderUtils.deconstructLine(newLine.content, isCombined);
|
||||||
newContent = utils.escapeForHtml(line);
|
newContent = content;
|
||||||
newPrefix = prefix;
|
newPrefix = prefix;
|
||||||
} else {
|
} else {
|
||||||
newContent = "";
|
newContent = "";
|
||||||
|
|
|
||||||
14
src/utils.ts
14
src/utils.ts
|
|
@ -30,20 +30,6 @@ export function escapeForRegExp(str: string): string {
|
||||||
return str.replace(regex, "\\$&");
|
return str.replace(regex, "\\$&");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Escapes all required characters for safe HTML rendering
|
|
||||||
*/
|
|
||||||
export function escapeForHtml(str: string): string {
|
|
||||||
return str
|
|
||||||
.slice(0)
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
.replace(/"/g, """)
|
|
||||||
.replace(/'/g, "'")
|
|
||||||
.replace(/\//g, "/");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts all '\' in @path to unix style '/'
|
* Converts all '\' in @path to unix style '/'
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue