diff2html/src/ui/js/diff2html-ui.ts
2019-12-22 18:00:45 +00:00

220 lines
7 KiB
TypeScript

import HighlightJS from "highlight.js";
import * as HighlightJSInternals from "./highlight.js-internals";
import { html, Diff2HtmlConfig, defaultDiff2HtmlConfig } from "../../diff2html";
import { DiffFile } from "../../render-utils";
interface Diff2HtmlUIConfig extends Diff2HtmlConfig {
synchronisedScroll?: boolean;
}
const defaultDiff2HtmlUIConfig = {
...defaultDiff2HtmlConfig,
synchronisedScroll: true
};
export default 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;
this.initSelection();
if (this.config.synchronisedScroll) this.synchronisedScroll();
}
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;
} else {
left.scrollTop = right.scrollTop;
}
};
left.addEventListener("scroll", onScroll);
right.addEventListener("scroll", onScroll);
});
}
fileListCloseable(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 = "";
hideBtn.style.display = "";
fileList.style.display = "";
}
function hide(): void {
showBtn.style.display = "none";
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 === null || td.textContent === null) return;
text += (i ? "\n" : "") + td.textContent.replace(/(?:\r\n|\r|\n)/g, "");
});
}
return text;
}
}
// TODO: Avoid disabling types
// eslint-disable-next-line
// @ts-ignore
global.Diff2HtmlUI = Diff2HtmlUI;