Compare commits
2 commits
master
...
support-fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc3c4be2b2 | ||
|
|
3824999a16 |
4 changed files with 94 additions and 45 deletions
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 '';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 '';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue