220 lines
7 KiB
TypeScript
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;
|