diff --git a/src/diff2html.ts b/src/diff2html.ts
index 5daed0e..a57a957 100644
--- a/src/diff2html.ts
+++ b/src/diff2html.ts
@@ -12,6 +12,7 @@ export interface Diff2HtmlConfig
HoganJsUtilsConfig {
outputFormat?: OutputFormatType;
drawFileList?: boolean;
+ lazy?: boolean;
}
export const defaultDiff2HtmlConfig = {
@@ -19,6 +20,7 @@ export const defaultDiff2HtmlConfig = {
...defaultSideBySideRendererConfig,
outputFormat: OutputFormatType.LINE_BY_LINE,
drawFileList: true,
+ lazy: false,
};
export function parse(diffInput: string, configuration: Diff2HtmlConfig = {}): DiffFile[] {
@@ -36,8 +38,18 @@ export function html(diffInput: string | DiffFile[], configuration: Diff2HtmlCon
const diffOutput =
config.outputFormat === 'side-by-side'
- ? new SideBySideRenderer(hoganUtils, config).render(diffJson)
- : new LineByLineRenderer(hoganUtils, config).render(diffJson);
+ ? new SideBySideRenderer(hoganUtils, config).render(config.lazy ? [] : diffJson)
+ : new LineByLineRenderer(hoganUtils, config).render(config.lazy ? [] : diffJson);
return fileList + diffOutput;
}
+
+export function htmlFile(diffFile: DiffFile, configuration: Diff2HtmlConfig = {}): string {
+ const config = { ...defaultDiff2HtmlConfig, ...configuration };
+
+ const hoganUtils = new HoganJsUtils(config);
+
+ return config.outputFormat === 'side-by-side'
+ ? new SideBySideRenderer(hoganUtils, config).renderFile(diffFile)
+ : new LineByLineRenderer(hoganUtils, config).renderFile(diffFile);
+}
diff --git a/src/line-by-line-renderer.ts b/src/line-by-line-renderer.ts
index 4258095..59693bb 100644
--- a/src/line-by-line-renderer.ts
+++ b/src/line-by-line-renderer.ts
@@ -55,6 +55,11 @@ export default class LineByLineRenderer {
return this.hoganUtils.render(genericTemplatesPath, 'wrapper', { content: diffsHtml });
}
+ renderFile(diffFile: DiffFile): string {
+ const diffs = diffFile.blocks.length ? this.generateFileHtml(diffFile) : this.generateEmptyDiff();
+ return this.makeFileDiffHtml(diffFile, diffs);
+ }
+
makeFileDiffHtml(file: DiffFile, diffs: string): string {
if (this.config.renderNothingWhenEmpty && Array.isArray(file.blocks) && file.blocks.length === 0) return '';
diff --git a/src/side-by-side-renderer.ts b/src/side-by-side-renderer.ts
index 9a0fb6b..7233fc7 100644
--- a/src/side-by-side-renderer.ts
+++ b/src/side-by-side-renderer.ts
@@ -40,21 +40,16 @@ export default class SideBySideRenderer {
}
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');
+ const diffsHtml = diffFiles.map(this.renderFile).join('\n');
return this.hoganUtils.render(genericTemplatesPath, 'wrapper', { content: diffsHtml });
}
+ renderFile(diffFile: DiffFile): string {
+ const diffs = diffFile.blocks.length ? this.generateFileHtml(diffFile) : this.generateEmptyDiff();
+ return this.makeFileDiffHtml(diffFile, diffs);
+ }
+
makeFileDiffHtml(file: DiffFile, diffs: FileHtml): string {
if (this.config.renderNothingWhenEmpty && Array.isArray(file.blocks) && file.blocks.length === 0) return '';
diff --git a/src/ui/js/diff2html-ui-base.ts b/src/ui/js/diff2html-ui-base.ts
index 7aaaa5f..2de5ff4 100644
--- a/src/ui/js/diff2html-ui-base.ts
+++ b/src/ui/js/diff2html-ui-base.ts
@@ -1,6 +1,6 @@
import { closeTags, nodeStream, mergeStreams, getLanguage } from './highlight.js-helpers';
-import { html, Diff2HtmlConfig, defaultDiff2HtmlConfig } from '../../diff2html';
+import { html, parse, Diff2HtmlConfig, defaultDiff2HtmlConfig, htmlFile } from '../../diff2html';
import { DiffFile } from '../../types';
import { HighlightResult, HLJSApi } from 'highlight.js';
@@ -15,6 +15,7 @@ export interface Diff2HtmlUIConfig extends Diff2HtmlConfig {
*/
smartSelection?: boolean;
fileContentToggle?: boolean;
+ lazy?: boolean;
}
export const defaultDiff2HtmlUIConfig = {
@@ -29,10 +30,12 @@ export const defaultDiff2HtmlUIConfig = {
*/
smartSelection: true,
fileContentToggle: true,
+ lazy: true,
};
export class Diff2HtmlUI {
readonly config: typeof defaultDiff2HtmlUIConfig;
+ readonly diffFiles: DiffFile[];
readonly diffHtml: string;
readonly targetElement: HTMLElement;
readonly hljs: HLJSApi | null = null;
@@ -41,13 +44,20 @@ export class Diff2HtmlUI {
constructor(target: HTMLElement, diffInput?: string | DiffFile[], config: Diff2HtmlUIConfig = {}, hljs?: HLJSApi) {
this.config = { ...defaultDiff2HtmlUIConfig, ...config };
- this.diffHtml = diffInput !== undefined ? html(diffInput, this.config) : target.innerHTML;
+
+ if (config.lazy && (config.fileListStartVisible ?? true)) {
+ this.config.fileListStartVisible = true;
+ }
+
+ this.diffFiles = typeof diffInput === 'string' ? parse(diffInput, this.config) : diffInput ?? [];
+ this.diffHtml = diffInput !== undefined ? html(this.diffFiles, this.config) : target.innerHTML;
this.targetElement = target;
if (hljs !== undefined) this.hljs = hljs;
}
draw(): void {
this.targetElement.innerHTML = this.diffHtml;
+ if (this.config.lazy) this.bindDrawFiles();
if (this.config.synchronisedScroll) this.synchronisedScroll();
if (this.config.highlight) this.highlightCode();
if (this.config.fileListToggle) this.fileListToggle(this.config.fileListStartVisible);
@@ -76,6 +86,27 @@ export class Diff2HtmlUI {
});
}
+ bindDrawFiles(): void {
+ const fileListItems: NodeListOf = this.targetElement.querySelectorAll('.d2h-file-name');
+ fileListItems.forEach((i, idx) =>
+ i.addEventListener('click', () => {
+ const fileId = i.getAttribute('href');
+ if (fileId && this.targetElement.querySelector(fileId)) {
+ return;
+ }
+
+ const tmpDiv = document.createElement('div');
+ tmpDiv.innerHTML = htmlFile(this.diffFiles[idx], this.config);
+ const fileElem = tmpDiv.querySelector('.d2h-file-wrapper');
+
+ if (fileElem) {
+ this.targetElement.querySelector('.d2h-wrapper')?.appendChild(fileElem);
+ this.highlightFile(fileElem);
+ }
+ }),
+ );
+ }
+
fileListToggle(startVisible: boolean): void {
const showBtn: HTMLElement | null = this.targetElement.querySelector('.d2h-show');
const hideBtn: HTMLElement | null = this.targetElement.querySelector('.d2h-hide');
@@ -138,43 +169,49 @@ export class Diff2HtmlUI {
// Collect all the diff files and execute the highlight on their lines
const files = this.targetElement.querySelectorAll('.d2h-file-wrapper');
- files.forEach(file => {
+ files.forEach(this.highlightFile);
+ }
+
+ highlightFile(file: Element): void {
+ if (this.hljs === null) {
+ throw new Error('Missing a `highlight.js` implementation. Please provide one when instantiating Diff2HtmlUI.');
+ }
+
+ // HACK: help Typescript know that `this.hljs` is defined since we already checked it
+ if (this.hljs === null) return;
+ const language = file.getAttribute('data-lang');
+ const hljsLanguage = language ? getLanguage(language) : 'plaintext';
+
+ // Collect all the code lines and execute the highlight on them
+ const codeLines = file.querySelectorAll('.d2h-code-line-ctn');
+ codeLines.forEach(line => {
// HACK: help Typescript know that `this.hljs` is defined since we already checked it
if (this.hljs === null) return;
- const language = file.getAttribute('data-lang');
- const hljsLanguage = language ? getLanguage(language) : 'plaintext';
-
- // Collect all the code lines and execute the highlight on them
- const codeLines = file.querySelectorAll('.d2h-code-line-ctn');
- codeLines.forEach(line => {
- // HACK: help Typescript know that `this.hljs` is defined since we already checked it
- if (this.hljs === null) return;
-
- const text = line.textContent;
- const lineParent = line.parentNode;
-
- if (text === null || lineParent === null || !this.isElement(lineParent)) return;
-
- const result: HighlightResult = closeTags(
- this.hljs.highlight(text, {
- language: hljsLanguage,
- ignoreIllegals: true,
- }),
- );
-
- const originalStream = nodeStream(line);
- if (originalStream.length) {
- const resultNode = document.createElementNS('http://www.w3.org/1999/xhtml', 'div');
- resultNode.innerHTML = result.value;
- result.value = mergeStreams(originalStream, nodeStream(resultNode), text);
- }
- line.classList.add('hljs');
- if (result.language) {
- line.classList.add(result.language);
- }
- line.innerHTML = result.value;
- });
+ const text = line.textContent;
+ const lineParent = line.parentNode;
+
+ if (text === null || lineParent === null || !this.isElement(lineParent)) return;
+
+ const result: HighlightResult = closeTags(
+ this.hljs.highlight(text, {
+ language: hljsLanguage,
+ ignoreIllegals: true,
+ }),
+ );
+
+ const originalStream = nodeStream(line);
+ if (originalStream.length) {
+ const resultNode = document.createElementNS('http://www.w3.org/1999/xhtml', 'div');
+ resultNode.innerHTML = result.value;
+ result.value = mergeStreams(originalStream, nodeStream(resultNode), text);
+ }
+
+ line.classList.add('hljs');
+ if (result.language) {
+ line.classList.add(result.language);
+ }
+ line.innerHTML = result.value;
});
}