Compare commits

...

2 commits

Author SHA1 Message Date
Rodrigo Fernandes
dc3c4be2b2
wip: add highlight to lazy loading 2021-10-15 22:36:23 +01:00
Rodrigo Fernandes
3824999a16
wip: initial lazy loading support 2021-10-15 22:18:21 +01:00
4 changed files with 94 additions and 45 deletions

View file

@ -12,6 +12,7 @@ export interface Diff2HtmlConfig
HoganJsUtilsConfig { HoganJsUtilsConfig {
outputFormat?: OutputFormatType; outputFormat?: OutputFormatType;
drawFileList?: boolean; drawFileList?: boolean;
lazy?: boolean;
} }
export const defaultDiff2HtmlConfig = { export const defaultDiff2HtmlConfig = {
@ -19,6 +20,7 @@ export const defaultDiff2HtmlConfig = {
...defaultSideBySideRendererConfig, ...defaultSideBySideRendererConfig,
outputFormat: OutputFormatType.LINE_BY_LINE, outputFormat: OutputFormatType.LINE_BY_LINE,
drawFileList: true, drawFileList: true,
lazy: false,
}; };
export function parse(diffInput: string, configuration: Diff2HtmlConfig = {}): DiffFile[] { export function parse(diffInput: string, configuration: Diff2HtmlConfig = {}): DiffFile[] {
@ -36,8 +38,18 @@ export function html(diffInput: string | DiffFile[], configuration: Diff2HtmlCon
const diffOutput = const diffOutput =
config.outputFormat === 'side-by-side' config.outputFormat === 'side-by-side'
? new SideBySideRenderer(hoganUtils, config).render(diffJson) ? new SideBySideRenderer(hoganUtils, config).render(config.lazy ? [] : diffJson)
: new LineByLineRenderer(hoganUtils, config).render(diffJson); : new LineByLineRenderer(hoganUtils, config).render(config.lazy ? [] : diffJson);
return fileList + diffOutput; 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);
}

View file

@ -55,6 +55,11 @@ export default class LineByLineRenderer {
return this.hoganUtils.render(genericTemplatesPath, 'wrapper', { content: diffsHtml }); 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 { makeFileDiffHtml(file: DiffFile, diffs: string): string {
if (this.config.renderNothingWhenEmpty && Array.isArray(file.blocks) && file.blocks.length === 0) return ''; if (this.config.renderNothingWhenEmpty && Array.isArray(file.blocks) && file.blocks.length === 0) return '';

View file

@ -40,21 +40,16 @@ export default class SideBySideRenderer {
} }
render(diffFiles: DiffFile[]): string { render(diffFiles: DiffFile[]): string {
const diffsHtml = diffFiles const diffsHtml = diffFiles.map(this.renderFile).join('\n');
.map(file => {
let diffs;
if (file.blocks.length) {
diffs = this.generateFileHtml(file);
} else {
diffs = this.generateEmptyDiff();
}
return this.makeFileDiffHtml(file, diffs);
})
.join('\n');
return this.hoganUtils.render(genericTemplatesPath, 'wrapper', { content: diffsHtml }); 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 { makeFileDiffHtml(file: DiffFile, diffs: FileHtml): string {
if (this.config.renderNothingWhenEmpty && Array.isArray(file.blocks) && file.blocks.length === 0) return ''; if (this.config.renderNothingWhenEmpty && Array.isArray(file.blocks) && file.blocks.length === 0) return '';

View file

@ -1,6 +1,6 @@
import { closeTags, nodeStream, mergeStreams, getLanguage } from './highlight.js-helpers'; 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 { DiffFile } from '../../types';
import { HighlightResult, HLJSApi } from 'highlight.js'; import { HighlightResult, HLJSApi } from 'highlight.js';
@ -15,6 +15,7 @@ export interface Diff2HtmlUIConfig extends Diff2HtmlConfig {
*/ */
smartSelection?: boolean; smartSelection?: boolean;
fileContentToggle?: boolean; fileContentToggle?: boolean;
lazy?: boolean;
} }
export const defaultDiff2HtmlUIConfig = { export const defaultDiff2HtmlUIConfig = {
@ -29,10 +30,12 @@ export const defaultDiff2HtmlUIConfig = {
*/ */
smartSelection: true, smartSelection: true,
fileContentToggle: true, fileContentToggle: true,
lazy: true,
}; };
export class Diff2HtmlUI { export class Diff2HtmlUI {
readonly config: typeof defaultDiff2HtmlUIConfig; readonly config: typeof defaultDiff2HtmlUIConfig;
readonly diffFiles: DiffFile[];
readonly diffHtml: string; readonly diffHtml: string;
readonly targetElement: HTMLElement; readonly targetElement: HTMLElement;
readonly hljs: HLJSApi | null = null; readonly hljs: HLJSApi | null = null;
@ -41,13 +44,20 @@ export class Diff2HtmlUI {
constructor(target: HTMLElement, diffInput?: string | DiffFile[], config: Diff2HtmlUIConfig = {}, hljs?: HLJSApi) { constructor(target: HTMLElement, diffInput?: string | DiffFile[], config: Diff2HtmlUIConfig = {}, hljs?: HLJSApi) {
this.config = { ...defaultDiff2HtmlUIConfig, ...config }; 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; this.targetElement = target;
if (hljs !== undefined) this.hljs = hljs; if (hljs !== undefined) this.hljs = hljs;
} }
draw(): void { draw(): void {
this.targetElement.innerHTML = this.diffHtml; this.targetElement.innerHTML = this.diffHtml;
if (this.config.lazy) this.bindDrawFiles();
if (this.config.synchronisedScroll) this.synchronisedScroll(); if (this.config.synchronisedScroll) this.synchronisedScroll();
if (this.config.highlight) this.highlightCode(); if (this.config.highlight) this.highlightCode();
if (this.config.fileListToggle) this.fileListToggle(this.config.fileListStartVisible); if (this.config.fileListToggle) this.fileListToggle(this.config.fileListStartVisible);
@ -76,6 +86,27 @@ export class Diff2HtmlUI {
}); });
} }
bindDrawFiles(): void {
const fileListItems: NodeListOf<HTMLElement> = 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 { fileListToggle(startVisible: boolean): void {
const showBtn: HTMLElement | null = this.targetElement.querySelector('.d2h-show'); const showBtn: HTMLElement | null = this.targetElement.querySelector('.d2h-show');
const hideBtn: HTMLElement | null = this.targetElement.querySelector('.d2h-hide'); 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 // Collect all the diff files and execute the highlight on their lines
const files = this.targetElement.querySelectorAll('.d2h-file-wrapper'); 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 // HACK: help Typescript know that `this.hljs` is defined since we already checked it
if (this.hljs === null) return; 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 text = line.textContent;
const codeLines = file.querySelectorAll('.d2h-code-line-ctn'); const lineParent = line.parentNode;
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; if (text === null || lineParent === null || !this.isElement(lineParent)) return;
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 result: HighlightResult = closeTags( const originalStream = nodeStream(line);
this.hljs.highlight(text, { if (originalStream.length) {
language: hljsLanguage, const resultNode = document.createElementNS('http://www.w3.org/1999/xhtml', 'div');
ignoreIllegals: true, resultNode.innerHTML = result.value;
}), result.value = mergeStreams(originalStream, nodeStream(resultNode), text);
); }
const originalStream = nodeStream(line); line.classList.add('hljs');
if (originalStream.length) { if (result.language) {
const resultNode = document.createElementNS('http://www.w3.org/1999/xhtml', 'div'); line.classList.add(result.language);
resultNode.innerHTML = result.value; }
result.value = mergeStreams(originalStream, nodeStream(resultNode), text); line.innerHTML = result.value;
}
line.classList.add('hljs');
if (result.language) {
line.classList.add(result.language);
}
line.innerHTML = result.value;
});
}); });
} }