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>
)
}