diff --git a/angular.json b/angular.json
index 924cbd9..27949fd 100644
--- a/angular.json
+++ b/angular.json
@@ -103,7 +103,7 @@
"projectType": "library",
"root": "projects/ngx-diff",
"sourceRoot": "projects/ngx-diff/src",
- "prefix": "lib",
+ "prefix": "ngx",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
diff --git a/projects/ngx-diff/.eslintrc.json b/projects/ngx-diff/.eslintrc.json
index 6eaff7b..a443403 100644
--- a/projects/ngx-diff/.eslintrc.json
+++ b/projects/ngx-diff/.eslintrc.json
@@ -1,18 +1,11 @@
{
"extends": "../../.eslintrc.json",
- "ignorePatterns": [
- "!**/*"
- ],
+ "ignorePatterns": ["!**/*"],
"overrides": [
{
- "files": [
- "*.ts"
- ],
+ "files": ["*.ts"],
"parserOptions": {
- "project": [
- "projects/ngx-diff/tsconfig.lib.json",
- "projects/ngx-diff/tsconfig.spec.json"
- ],
+ "project": ["projects/ngx-diff/tsconfig.lib.json", "projects/ngx-diff/tsconfig.spec.json"],
"createDefaultProgram": true
},
"rules": {
@@ -20,7 +13,7 @@
"error",
{
"type": "element",
- "prefix": "lib",
+ "prefix": "ngx",
"style": "kebab-case"
}
],
@@ -28,7 +21,7 @@
"error",
{
"type": "attribute",
- "prefix": "lib",
+ "prefix": "ngx",
"style": "camelCase"
}
],
@@ -38,17 +31,12 @@
"accessibility": "explicit"
}
],
- "arrow-parens": [
- "off",
- "always"
- ],
+ "arrow-parens": ["off", "always"],
"import/order": "off"
}
},
{
- "files": [
- "*.html"
- ],
+ "files": ["*.html"],
"rules": {}
}
]
diff --git a/projects/ngx-diff/src/lib/components/inline-diff/inline-diff.component.scss b/projects/ngx-diff/src/lib/components/inline-diff/inline-diff.component.scss
index 3b88724..3047ff8 100644
--- a/projects/ngx-diff/src/lib/components/inline-diff/inline-diff.component.scss
+++ b/projects/ngx-diff/src/lib/components/inline-diff/inline-diff.component.scss
@@ -18,7 +18,7 @@
--ngx-diff-delete-color: #ffd6d6;
--ngx-diff-equal-color: #ffffff;
--ngx-diff-mix-color: #000;
- --ngx-diff-light-mix-percentage: 2%;
+ --ngx-diff-light-mix-percentage: 4%;
--ngx-diff-heavy-mix-percentage: 10%;
--ngx-diff-inserted-background-color: var(--ngx-diff-insert-color);
diff --git a/projects/ngx-diff/src/lib/components/inline-diff/inline-diff.component.ts b/projects/ngx-diff/src/lib/components/inline-diff/inline-diff.component.ts
index 80f6997..6f44223 100644
--- a/projects/ngx-diff/src/lib/components/inline-diff/inline-diff.component.ts
+++ b/projects/ngx-diff/src/lib/components/inline-diff/inline-diff.component.ts
@@ -70,6 +70,7 @@ export class InlineDiffComponent implements OnInit, OnChanges {
if (type === LineDiffType.Placeholder) {
this.expandPlaceholder(index, lineDiff);
+ this.selectedLine = undefined;
}
this.selectedLineChange.emit({
@@ -108,7 +109,7 @@ export class InlineDiffComponent implements OnInit, OnChanges {
type: LineDiffType.Placeholder,
lineNumberInOldText: null,
lineNumberInNewText: null,
- line: '...',
+ line: `... ${remainingSkippedLines.length} hidden lines ...`,
args: {
skippedLines: remainingSkippedLines,
lineInOldText: lineInOldText + prefix.length,
@@ -265,17 +266,19 @@ export class InlineDiffComponent implements OnInit, OnChanges {
// Take the first 'lineContextSize' lines from this diff to provide context for the last diff
this.outputEqualDiffLines(diffLines.slice(0, this.lineContextSize), diffCalculation);
+ const skippedLines = diffLines.slice(
+ this.lineContextSize,
+ diffLines.length - this.lineContextSize,
+ );
+
// Output a special line indicating that some content is equal and has been skipped
diffCalculation.lines.push({
type: LineDiffType.Placeholder,
lineNumberInOldText: null,
lineNumberInNewText: null,
- line: '...',
+ line: `... ${skippedLines.length} hidden lines ...`,
args: {
- skippedLines: diffLines.slice(
- this.lineContextSize,
- diffLines.length - this.lineContextSize,
- ),
+ skippedLines,
lineInOldText: diffCalculation.lineInOldText,
lineInNewText: diffCalculation.lineInNewText,
},
diff --git a/projects/ngx-diff/src/lib/components/side-by-side-diff/side-by-side-diff.component.html b/projects/ngx-diff/src/lib/components/side-by-side-diff/side-by-side-diff.component.html
new file mode 100644
index 0000000..2d1ffa4
--- /dev/null
+++ b/projects/ngx-diff/src/lib/components/side-by-side-diff/side-by-side-diff.component.html
@@ -0,0 +1,76 @@
+
+ @if (title) {
+ {{ title }}
+ }
+ +++ {{ diffSummary.numLinesAdded }}
+ --- {{ diffSummary.numLinesRemoved }}
+
+@if (isContentEqual) {
+ There are no changes to display.
+}
+@if (!isContentEqual) {
+
+
+
+ @for (lineDiff of beforeLines; track lineDiff; let idx = $index) {
+
+
{{ lineDiff.lineNumber | lineNumber }}
+
+ }
+
+
+
+
+ @for (lineDiff of beforeLines; track lineDiff; let idx = $index) {
+
+ }
+
+
+
+
+
+ @for (lineDiff of afterLines; track lineDiff; let idx = $index) {
+
+
{{ lineDiff.lineNumber | lineNumber }}
+
+ }
+
+
+
+
+ @for (lineDiff of afterLines; track lineDiff; let idx = $index) {
+
+ }
+
+
+
+
+}
diff --git a/projects/ngx-diff/src/lib/components/side-by-side-diff/side-by-side-diff.component.scss b/projects/ngx-diff/src/lib/components/side-by-side-diff/side-by-side-diff.component.scss
new file mode 100644
index 0000000..3b01979
--- /dev/null
+++ b/projects/ngx-diff/src/lib/components/side-by-side-diff/side-by-side-diff.component.scss
@@ -0,0 +1,181 @@
+:host {
+ --ngx-diff-border-color: #888888;
+ --ngx-diff-font-size: 0.9rem;
+ --ngx-diff-font-family: Consolas, Courier, monospace;
+ --ngx-diff-font-color: #000000;
+ --ngx-diff-line-number-font-color: #484848;
+ --ngx-diff-line-number-hover-font-color: #ffffff;
+ --ngx-diff-selected-border-color: #ff8000;
+
+ --ngx-diff-line-number-width: 2rem;
+ --ngx-diff-border-width: 1px;
+ --ngx-diff-line-left-padding: 1rem;
+ --ngx-diff-bottom-spacer-height: 1rem;
+ --ngx-diff-title-bar-padding: 0.5rem;
+ --ngx-diff-title-font-weight: 600;
+
+ --ngx-diff-insert-color: #d6ffd6;
+ --ngx-diff-delete-color: #ffd6d6;
+ --ngx-diff-equal-color: #ffffff;
+ --ngx-diff-mix-color: #000;
+ --ngx-diff-light-mix-percentage: 4%;
+ --ngx-diff-heavy-mix-percentage: 10%;
+
+ --ngx-diff-inserted-background-color: var(--ngx-diff-insert-color);
+ --ngx-diff-deleted-background-color: var(--ngx-diff-delete-color);
+ --ngx-diff-equal-background-color: var(--ngx-diff-equal-color);
+ --ngx-diff-margin-background-color: color-mix(in srgb, var(--ngx-diff-equal-color), var(--ngx-diff-mix-color) var(--ngx-diff-light-mix-percentage));
+
+ --ngx-diff-insert-color-darker: color-mix(in srgb, var(--ngx-diff-insert-color), var(--ngx-diff-mix-color) var(--ngx-diff-light-mix-percentage));
+ --ngx-diff-insert-color-darkest: color-mix(in srgb, var(--ngx-diff-insert-color), var(--ngx-diff-mix-color) var(--ngx-diff-heavy-mix-percentage));
+
+ --ngx-diff-delete-color-darker: color-mix(in srgb, var(--ngx-diff-delete-color), var(--ngx-diff-mix-color) var(--ngx-diff-light-mix-percentage));
+ --ngx-diff-delete-color-darkest: color-mix(in srgb, var(--ngx-diff-delete-color), var(--ngx-diff-mix-color) var(--ngx-diff-heavy-mix-percentage));
+ }
+
+ div.sbs-diff-title-bar {
+ background-color: var(--ngx-diff-margin-background-color);
+ font-family: var(--ngx-diff-font-family);
+ font-size: var(--ngx-diff-font-size);
+ font-weight: var(--ngx-diff-title-font-weight);
+ padding: var(--ngx-diff-title-bar-padding);
+ }
+
+ div.sbs-diff-no-changes-text {
+ font-family: var(--ngx-diff-font-family);
+ font-size: var(--ngx-diff-font-size);
+ font-weight: var(--ngx-diff-title-font-weight);
+ padding: var(--ngx-diff-title-bar-padding);
+ background-color: var(--ngx-diff-equal-background-color);
+ color: var(--ngx-diff-font-color);
+ }
+
+ .sbs-diff-summary-lines-added {
+ color: var(--ngx-diff-insert-color-darkest);
+ }
+
+ .sbs-diff-summary-lines-removed {
+ color: var(--ngx-diff-delete-color-darkest);
+ }
+
+ div.sbs-diff {
+ display: flex;
+ flex-direction: row;
+ border: var(--ngx-diff-border-width) solid var(--ngx-diff-border-color);
+ font-family: var(--ngx-diff-font-family);
+
+ div.sbs-diff-margin:last-of-type {
+ border-left: var(--ngx-diff-border-width) solid var(--ngx-diff-border-color);
+ }
+ }
+
+ div.sbs-diff-content {
+ position: relative;
+ top: 0px;
+ left: 0px;
+ flex-grow: 1;
+ overflow-x: auto;
+ overflow-y: hidden;
+ }
+
+ div.sbs-diff-content-wrapper {
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ width: 100%;
+ }
+
+ div.sbs-diff-old {
+ width: var(--ngx-diff-line-number-width);
+ text-align: center;
+ font-size: var(--ngx-diff-font-size);
+ }
+
+ div.sbs-diff-new {
+ width: var(--ngx-diff-line-number-width);
+ text-align: center;
+ border-right: var(--ngx-diff-border-width) solid var(--border-color);
+ font-size: var(--ngx-diff-font-size);
+ }
+
+ div.sbs-diff-text {
+ white-space: pre;
+ padding-left: var(--ngx-diff-line-left-padding);
+ font-size: var(--ngx-diff-font-size);
+ color: var(--ngx-diff-font-color);
+ }
+
+ .sbs-diff-equal {
+ background-color: var(--ngx-diff-margin-background-color);
+
+ &.line-content {
+ background-color: var(--ngx-diff-equal-background-color);
+ }
+ }
+
+ .sbs-diff-delete {
+ background-color: var(--ngx-diff-delete-color-darker);
+
+ &.line-content {
+ background-color: var(--ngx-diff-deleted-background-color);
+ }
+ }
+
+ .sbs-diff-insert {
+ background-color: var(--ngx-diff-insert-color-darker);
+
+ &.line-content {
+ background-color: var(--ngx-diff-inserted-background-color);
+ }
+ }
+
+ .sbs-diff-delete > div {
+ display: inline-block;
+ }
+
+ .sbs-diff-insert > div {
+ display: inline-block;
+ }
+
+ .sbs-diff-equal > div {
+ display: inline-block;
+ }
+
+ .dmp-margin-bottom-spacer {
+ height: var(--ngx-diff-bottom-spacer-height);
+ background-color: var(--ngx-diff-margin-background-color);
+ border-right: var(--ngx-diff-border-width) solid var(--border-color);
+
+ &.line-content {
+ background-color: var(--ngx-diff-equal-background-color);
+ }
+ }
+
+ .line-selector {
+ color: var(--ngx-diff-line-number-font-color);
+
+ .sbs-diff-before, .sbs-diff-after {
+ width: var(--ngx-diff-line-number-width);
+ text-align: center;
+ }
+
+ &:hover {
+ cursor: pointer;
+ color: var(--ngx-diff-line-number-hover-font-color);
+ }
+
+ &.selected {
+ border-top: var(--ngx-diff-border-width) solid var(--ngx-diff-selected-border-color);
+ border-left: var(--ngx-diff-border-width) solid var(--ngx-diff-selected-border-color);
+ border-bottom: var(--ngx-diff-border-width) solid var(--ngx-diff-selected-border-color);
+ }
+ }
+
+ .line-content.selected {
+ border-top: var(--ngx-diff-border-width) solid var(--ngx-diff-selected-border-color);
+ border-right: var(--ngx-diff-border-width) solid var(--ngx-diff-selected-border-color);
+ border-bottom: var(--ngx-diff-border-width) solid var(--ngx-diff-selected-border-color);
+ }
diff --git a/projects/ngx-diff/src/lib/components/side-by-side-diff/side-by-side-diff.component.spec.ts b/projects/ngx-diff/src/lib/components/side-by-side-diff/side-by-side-diff.component.spec.ts
new file mode 100644
index 0000000..8978376
--- /dev/null
+++ b/projects/ngx-diff/src/lib/components/side-by-side-diff/side-by-side-diff.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { SideBySideDiffComponent } from './side-by-side-diff.component';
+
+describe('SideBySideDiffComponent', () => {
+ let component: SideBySideDiffComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [SideBySideDiffComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(SideBySideDiffComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/projects/ngx-diff/src/lib/components/side-by-side-diff/side-by-side-diff.component.ts b/projects/ngx-diff/src/lib/components/side-by-side-diff/side-by-side-diff.component.ts
new file mode 100644
index 0000000..aa4d507
--- /dev/null
+++ b/projects/ngx-diff/src/lib/components/side-by-side-diff/side-by-side-diff.component.ts
@@ -0,0 +1,427 @@
+import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core';
+import { Diff, DiffOp } from 'diff-match-patch-ts';
+import { DiffMatchPatchService } from '../../services/diff-match-patch/diff-match-patch.service';
+
+import { LineNumberPipe } from '../../pipes/line-number/line-number.pipe';
+import { LineDiffType } from '../../common/line-diff-type';
+import { NgClass } from '@angular/common';
+import { LineSelectEvent } from '../../common/line-select-event';
+
+interface IDiffCalculation {
+ beforeLineNumber: number;
+ afterLineNumber: number;
+}
+
+interface ILine {
+ type: LineDiffType;
+ lineNumber: number | null;
+ line: string | null;
+ cssClass: string;
+ args?: {
+ skippedLines: string[];
+ beforeLineNumber: number;
+ afterLineNumber: number;
+ };
+}
+
+@Component({
+ selector: 'ngx-side-by-side-diff',
+ standalone: true,
+ imports: [NgClass, LineNumberPipe],
+ templateUrl: './side-by-side-diff.component.html',
+ styleUrl: './side-by-side-diff.component.scss',
+})
+export class SideBySideDiffComponent implements OnInit, OnChanges {
+ /**
+ * Optional title to be displayed at the top of the diff.
+ */
+ @Input({ required: false })
+ public title?: string;
+
+ @Input({ required: true })
+ public before?: string;
+
+ @Input({ required: true })
+ public after?: string;
+
+ /**
+ * The number of lines of context to provide either side of a DiffOp.Insert or DiffOp.Delete diff.
+ * Context is taken from a DiffOp.Equal section.
+ */
+ @Input({ required: false })
+ public lineContextSize?: number;
+
+ @Output()
+ public selectedLineChange = new EventEmitter();
+
+ public isContentEqual = false;
+ public diffSummary = {
+ numLinesAdded: 0,
+ numLinesRemoved: 0,
+ };
+
+ public beforeLines: ILine[] = [];
+ public afterLines: ILine[] = [];
+ public selectedLineIndex?: number;
+
+ public constructor(private readonly dmp: DiffMatchPatchService) {}
+
+ public ngOnInit(): void {
+ this.update();
+ }
+
+ public ngOnChanges(): void {
+ this.update();
+ }
+
+ public selectLine(index: number): void {
+ this.selectedLineIndex = index;
+
+ const selectedBeforeLine = this.beforeLines[index];
+ const selectedAfterLine = this.afterLines[index];
+
+ const type = selectedAfterLine.type;
+
+ const line =
+ (type === LineDiffType.Delete ? selectedBeforeLine.line : selectedAfterLine.line) ?? '';
+
+ let lineNumberInOldText: number | null = null;
+ let lineNumberInNewText: number | null = null;
+
+ switch (type) {
+ case LineDiffType.Insert: {
+ lineNumberInNewText = selectedAfterLine.lineNumber;
+ break;
+ }
+ case LineDiffType.Delete: {
+ lineNumberInOldText = selectedBeforeLine.lineNumber;
+ break;
+ }
+ case LineDiffType.Equal: {
+ lineNumberInOldText = selectedBeforeLine.lineNumber;
+ lineNumberInNewText = selectedAfterLine.lineNumber;
+ break;
+ }
+ }
+
+ if (type === LineDiffType.Placeholder) {
+ this.expandPlaceholder(index, selectedBeforeLine);
+ this.selectedLineIndex = undefined;
+ }
+
+ this.selectedLineChange.emit({
+ index,
+ type,
+ lineNumberInOldText,
+ lineNumberInNewText,
+ line,
+ });
+ }
+
+ private expandPlaceholder(index: number, placeholder: ILine): void {
+ const replacementLines = this.getPlaceholderReplacementLines(placeholder);
+
+ this.beforeLines.splice(index, 1, ...replacementLines.beforeLineDiffs);
+ this.afterLines.splice(index, 1, ...replacementLines.afterLineDiffs);
+ }
+
+ private getPlaceholderReplacementLines(placeholder: ILine): {
+ beforeLineDiffs: ILine[];
+ afterLineDiffs: ILine[];
+ } {
+ const { skippedLines, beforeLineNumber, afterLineNumber } = placeholder.args!;
+
+ if (this.lineContextSize && skippedLines.length > 2 * this.lineContextSize) {
+ const prefix = skippedLines.slice(0, this.lineContextSize);
+ const remainingSkippedLines = skippedLines.slice(
+ this.lineContextSize,
+ skippedLines.length - this.lineContextSize,
+ );
+ const suffix = skippedLines.slice(
+ skippedLines.length - this.lineContextSize,
+ skippedLines.length,
+ );
+
+ const prefixLines = this.createLineDiffs(prefix, beforeLineNumber, afterLineNumber);
+
+ const newPlaceholder: ILine = {
+ type: LineDiffType.Placeholder,
+ lineNumber: null,
+ line: `... ${remainingSkippedLines.length} hidden lines ...`,
+ args: {
+ skippedLines: remainingSkippedLines,
+ beforeLineNumber: beforeLineNumber + prefix.length,
+ afterLineNumber: afterLineNumber + prefix.length,
+ },
+ cssClass: this.getCssClass(LineDiffType.Placeholder),
+ };
+
+ const numberOfPrefixAndSkippedLines = prefix.length + remainingSkippedLines.length;
+
+ const suffixLines = this.createLineDiffs(
+ suffix,
+ beforeLineNumber + numberOfPrefixAndSkippedLines,
+ afterLineNumber + numberOfPrefixAndSkippedLines,
+ );
+
+ return {
+ beforeLineDiffs: [
+ ...prefixLines.beforeLineDiffs,
+ newPlaceholder,
+ ...suffixLines.beforeLineDiffs,
+ ],
+ afterLineDiffs: [
+ ...prefixLines.afterLineDiffs,
+ newPlaceholder,
+ ...suffixLines.afterLineDiffs,
+ ],
+ };
+ }
+
+ return this.createLineDiffs(skippedLines, beforeLineNumber, afterLineNumber);
+ }
+
+ private createLineDiffs(
+ lines: string[],
+ startLineInOldText: number,
+ startLineInNewText: number,
+ ): { beforeLineDiffs: ILine[]; afterLineDiffs: ILine[] } {
+ let beforeLineNumber = startLineInOldText;
+ let afterLineNumber = startLineInNewText;
+
+ const cssClass = this.getCssClass(LineDiffType.Equal);
+
+ const beforeLineDiffs: ILine[] = [];
+ const afterLineDiffs: ILine[] = [];
+
+ for (const line of lines) {
+ const toInsert = {
+ type: LineDiffType.Equal,
+ line,
+ cssClass,
+ };
+
+ beforeLineDiffs.push({
+ ...toInsert,
+ lineNumber: beforeLineNumber,
+ });
+ beforeLineNumber++;
+
+ afterLineDiffs.push({
+ ...toInsert,
+ lineNumber: afterLineNumber,
+ });
+ afterLineNumber++;
+ }
+
+ return { beforeLineDiffs, afterLineDiffs };
+ }
+
+ private update(): void {
+ const beforeText = this.before ?? '';
+ const afterText = this.after ?? '';
+ this.calculateLineDiffs(this.dmp.computeLineDiff(beforeText, afterText));
+ }
+
+ private calculateLineDiffs(diffs: Diff[]): void {
+ this.beforeLines = [];
+ this.afterLines = [];
+
+ const diffCalculation = {
+ beforeLineNumber: 1,
+ afterLineNumber: 1,
+ };
+
+ this.isContentEqual = diffs.length === 1 && diffs[0][0] === DiffOp.Equal;
+
+ if (this.isContentEqual) {
+ this.beforeLines = [];
+ this.afterLines = [];
+ this.diffSummary = {
+ numLinesAdded: 0,
+ numLinesRemoved: 0,
+ };
+ return;
+ }
+
+ for (let i = 0; i < diffs.length; i++) {
+ const diff = diffs[i];
+ const diffLines: string[] = diff[1].split(/\r?\n/);
+
+ // If the original line had a \r\n at the end then remove the
+ // empty string after it.
+ if (diffLines[diffLines.length - 1].length === 0) {
+ diffLines.pop();
+ }
+
+ switch (diff[0]) {
+ case DiffOp.Equal: {
+ const isFirstDiff = i === 0;
+ const isLastDiff = i === diffs.length - 1;
+ this.outputEqualDiff(diffLines, diffCalculation, isFirstDiff, isLastDiff);
+ break;
+ }
+ case DiffOp.Delete: {
+ this.outputDeleteDiff(diffLines, diffCalculation);
+ break;
+ }
+ case DiffOp.Insert: {
+ this.outputInsertDiff(diffLines, diffCalculation);
+ break;
+ }
+ }
+ }
+
+ this.diffSummary = {
+ numLinesAdded: this.afterLines.filter((x) => x.type === LineDiffType.Insert).length,
+ numLinesRemoved: this.beforeLines.filter((x) => x.type === LineDiffType.Delete).length,
+ };
+ }
+
+ /* If the number of diffLines is greater than lineContextSize then we may need to adjust the diff
+ * that is output.
+ * > If the first diff of a document is DiffOp.Equal then the leading lines can be dropped
+ * leaving the last 'lineContextSize' lines for context.
+ * > If the last diff of a document is DiffOp.Equal then the trailing lines can be dropped
+ * leaving the first 'lineContextSize' lines for context.
+ * > If the diff is a DiffOp.Equal occurs in the middle then the diffs either side of it must be
+ * DiffOp.Insert or DiffOp.Delete. If it has more than 2 * 'lineContextSize' lines of content
+ * then the middle lines are dropped leaving the first 'lineContextSize' and last 'lineContextSize'
+ * lines for context. A special line is inserted with '...' indicating that content is skipped.
+ *
+ * A document cannot consist of a single Diff with DiffOp.Equal and reach this function because
+ * in this case the calculateLineDiff method returns early.
+ */
+ private outputEqualDiff(
+ diffLines: string[],
+ diffCalculation: IDiffCalculation,
+ isFirstDiff: boolean,
+ isLastDiff: boolean,
+ ): void {
+ if (this.lineContextSize && diffLines.length > this.lineContextSize) {
+ if (isFirstDiff) {
+ // Take the last 'lineContextSize' lines from the first diff
+ const lineIncrement = diffLines.length - this.lineContextSize;
+ diffCalculation.beforeLineNumber += lineIncrement;
+ diffCalculation.afterLineNumber += lineIncrement;
+ diffLines = diffLines.slice(diffLines.length - this.lineContextSize, diffLines.length);
+ } else if (isLastDiff) {
+ // Take only the first 'lineContextSize' lines from the final diff
+ diffLines = diffLines.slice(0, this.lineContextSize);
+ } else if (diffLines.length > 2 * this.lineContextSize) {
+ // Take the first 'lineContextSize' lines from this diff to provide context for the last diff
+ this.outputEqualDiffLines(diffLines.slice(0, this.lineContextSize), diffCalculation);
+
+ const skippedLines = diffLines.slice(
+ this.lineContextSize,
+ diffLines.length - this.lineContextSize,
+ );
+
+ // Output a special line indicating that some content is equal and has been skipped
+ const skippedLine = {
+ type: LineDiffType.Placeholder,
+ lineNumber: null,
+ line: `... ${skippedLines.length} hidden lines ...`,
+ cssClass: this.getCssClass(LineDiffType.Placeholder),
+ args: {
+ skippedLines,
+ beforeLineNumber: diffCalculation.beforeLineNumber,
+ afterLineNumber: diffCalculation.afterLineNumber,
+ },
+ };
+
+ this.beforeLines.push(skippedLine);
+ this.afterLines.push(skippedLine);
+
+ const numberOfSkippedLines = diffLines.length - 2 * this.lineContextSize;
+ diffCalculation.beforeLineNumber += numberOfSkippedLines;
+ diffCalculation.afterLineNumber += numberOfSkippedLines;
+
+ // Take the last 'lineContextSize' lines from this diff to provide context for the next diff
+ this.outputEqualDiffLines(
+ diffLines.slice(diffLines.length - this.lineContextSize),
+ diffCalculation,
+ );
+ // This if branch has already output the diff lines so we return early to avoid outputting the lines
+ // at the end of the method.
+ return;
+ }
+ }
+ this.outputEqualDiffLines(diffLines, diffCalculation);
+ }
+
+ private outputEqualDiffLines(diffLines: string[], diffCalculation: IDiffCalculation): void {
+ for (const line of diffLines) {
+ this.beforeLines.push({
+ type: LineDiffType.Equal,
+ lineNumber: diffCalculation.beforeLineNumber,
+ line,
+ cssClass: this.getCssClass(LineDiffType.Equal),
+ });
+
+ this.afterLines.push({
+ type: LineDiffType.Equal,
+ lineNumber: diffCalculation.afterLineNumber,
+ line,
+ cssClass: this.getCssClass(LineDiffType.Equal),
+ });
+
+ diffCalculation.beforeLineNumber++;
+ diffCalculation.afterLineNumber++;
+ }
+ }
+
+ private outputDeleteDiff(diffLines: string[], diffCalculation: IDiffCalculation): void {
+ for (const line of diffLines) {
+ this.beforeLines.push({
+ type: LineDiffType.Delete,
+ lineNumber: diffCalculation.beforeLineNumber,
+ line,
+ cssClass: this.getCssClass(LineDiffType.Delete),
+ });
+
+ this.afterLines.push({
+ type: LineDiffType.Delete,
+ lineNumber: null,
+ line: null,
+ cssClass: this.getCssClass(LineDiffType.Delete),
+ });
+
+ diffCalculation.beforeLineNumber++;
+ }
+ }
+
+ private outputInsertDiff(diffLines: string[], diffCalculation: IDiffCalculation): void {
+ for (const line of diffLines) {
+ this.beforeLines.push({
+ type: LineDiffType.Insert,
+ lineNumber: null,
+ line: null,
+ cssClass: this.getCssClass(LineDiffType.Insert),
+ });
+
+ this.afterLines.push({
+ type: LineDiffType.Insert,
+ lineNumber: diffCalculation.afterLineNumber,
+ line,
+ cssClass: this.getCssClass(LineDiffType.Insert),
+ });
+
+ diffCalculation.afterLineNumber++;
+ }
+ }
+
+ private getCssClass(type: LineDiffType): string {
+ switch (type) {
+ case LineDiffType.Placeholder:
+ case LineDiffType.Equal:
+ return 'sbs-diff-equal';
+ case LineDiffType.Insert:
+ return 'sbs-diff-insert';
+ case LineDiffType.Delete:
+ return 'sbs-diff-delete';
+ default:
+ return 'unknown';
+ }
+ }
+}
diff --git a/projects/ngx-diff/src/public-api.ts b/projects/ngx-diff/src/public-api.ts
index 78eca50..8237195 100644
--- a/projects/ngx-diff/src/public-api.ts
+++ b/projects/ngx-diff/src/public-api.ts
@@ -4,6 +4,6 @@
export * from './lib/services/diff-match-patch/diff-match-patch.service';
export * from './lib/components/inline-diff/inline-diff.component';
+export * from './lib/components/side-by-side-diff/side-by-side-diff.component';
export * from './lib/common/line-select-event';
export * from './lib/common/line-diff-type';
-
diff --git a/src/app/app.component.html b/src/app/app.component.html
index 01d5326..ef82f98 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -36,4 +36,13 @@
style="width: 100%"
(selectedLineChange)="selectedLineChange($event)"
/>
+
+
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 71f0355..61aac29 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,4 +1,4 @@
-import { InlineDiffComponent } from 'ngx-diff';
+import { InlineDiffComponent, SideBySideDiffComponent } from 'ngx-diff';
import { Component } from '@angular/core';
@@ -7,7 +7,7 @@ import { Component } from '@angular/core';
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
standalone: true,
- imports: [InlineDiffComponent],
+ imports: [InlineDiffComponent, SideBySideDiffComponent],
})
export class AppComponent {
public oldText = `common text
diff --git a/src/index.html b/src/index.html
index 9837355..a545d72 100644
--- a/src/index.html
+++ b/src/index.html
@@ -1,8 +1,8 @@
-
+
- NgxDiff
+ ngx-diff