| import { parsePatch } from "diff" |
| import { createMemo, For } from "solid-js" |
| import { ContentCode } from "./content-code" |
| import styles from "./content-diff.module.css" |
|
|
| type DiffRow = { |
| left: string |
| right: string |
| type: "added" | "removed" | "unchanged" | "modified" |
| } |
|
|
| interface Props { |
| diff: string |
| lang?: string |
| } |
|
|
| export function ContentDiff(props: Props) { |
| const rows = createMemo(() => { |
| const diffRows: DiffRow[] = [] |
|
|
| try { |
| const patches = parsePatch(props.diff) |
|
|
| for (const patch of patches) { |
| for (const hunk of patch.hunks) { |
| const lines = hunk.lines |
| let i = 0 |
|
|
| while (i < lines.length) { |
| const line = lines[i] |
| const content = line.slice(1) |
| const prefix = line[0] |
|
|
| if (prefix === "-") { |
| |
| const removals: string[] = [content] |
| let j = i + 1 |
|
|
| |
| while (j < lines.length && lines[j][0] === "-") { |
| removals.push(lines[j].slice(1)) |
| j++ |
| } |
|
|
| |
| const additions: string[] = [] |
| while (j < lines.length && lines[j][0] === "+") { |
| additions.push(lines[j].slice(1)) |
| j++ |
| } |
|
|
| |
| const maxLength = Math.max(removals.length, additions.length) |
| for (let k = 0; k < maxLength; k++) { |
| const hasLeft = k < removals.length |
| const hasRight = k < additions.length |
|
|
| if (hasLeft && hasRight) { |
| |
| diffRows.push({ |
| left: removals[k], |
| right: additions[k], |
| type: "modified", |
| }) |
| } else if (hasLeft) { |
| |
| diffRows.push({ |
| left: removals[k], |
| right: "", |
| type: "removed", |
| }) |
| } else if (hasRight) { |
| |
| diffRows.push({ |
| left: "", |
| right: additions[k], |
| type: "added", |
| }) |
| } |
| } |
|
|
| i = j |
| } else if (prefix === "+") { |
| |
| diffRows.push({ |
| left: "", |
| right: content, |
| type: "added", |
| }) |
| i++ |
| } else if (prefix === " ") { |
| diffRows.push({ |
| left: content === "" ? " " : content, |
| right: content === "" ? " " : content, |
| type: "unchanged", |
| }) |
| i++ |
| } else { |
| i++ |
| } |
| } |
| } |
| } |
| } catch (error) { |
| console.error("Failed to parse patch:", error) |
| return [] |
| } |
|
|
| return diffRows |
| }) |
|
|
| const mobileRows = createMemo(() => { |
| const mobileBlocks: { |
| type: "removed" | "added" | "unchanged" |
| lines: string[] |
| }[] = [] |
| const currentRows = rows() |
|
|
| let i = 0 |
| while (i < currentRows.length) { |
| const removedLines: string[] = [] |
| const addedLines: string[] = [] |
|
|
| |
| while ( |
| i < currentRows.length && |
| (currentRows[i].type === "modified" || currentRows[i].type === "removed" || currentRows[i].type === "added") |
| ) { |
| const row = currentRows[i] |
| if (row.left && (row.type === "removed" || row.type === "modified")) { |
| removedLines.push(row.left) |
| } |
| if (row.right && (row.type === "added" || row.type === "modified")) { |
| addedLines.push(row.right) |
| } |
| i++ |
| } |
|
|
| |
| if (removedLines.length > 0) { |
| mobileBlocks.push({ type: "removed", lines: removedLines }) |
| } |
| if (addedLines.length > 0) { |
| mobileBlocks.push({ type: "added", lines: addedLines }) |
| } |
|
|
| |
| if (i < currentRows.length && currentRows[i].type === "unchanged") { |
| mobileBlocks.push({ |
| type: "unchanged", |
| lines: [currentRows[i].left], |
| }) |
| i++ |
| } |
| } |
|
|
| return mobileBlocks |
| }) |
|
|
| return ( |
| <div class={styles.root}> |
| <div data-component="desktop"> |
| <For each={rows()}> |
| {(row) => ( |
| <div data-component="diff-row" data-type={row.type}> |
| <div |
| data-slot="before" |
| data-diff-type={row.type === "removed" || row.type === "modified" ? "removed" : ""} |
| > |
| <ContentCode code={row.left} flush lang={props.lang} /> |
| </div> |
| <div data-slot="after" data-diff-type={row.type === "added" || row.type === "modified" ? "added" : ""}> |
| <ContentCode code={row.right} lang={props.lang} flush /> |
| </div> |
| </div> |
| )} |
| </For> |
| </div> |
| |
| <div data-component="mobile"> |
| <For each={mobileRows()}> |
| {(block) => ( |
| <div data-component="diff-block" data-type={block.type}> |
| <For each={block.lines}> |
| {(line) => ( |
| <div data-diff-type={block.type === "removed" ? "removed" : block.type === "added" ? "added" : ""}> |
| <ContentCode code={line} lang={props.lang} flush /> |
| </div> |
| )} |
| </For> |
| </div> |
| )} |
| </For> |
| </div> |
| </div> |
| ) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|