Extract browse HTML into external template
The HTML for the browse view was inlined as a template literal in browse.ts, making it hard to edit and losing syntax highlighting. Move it to browse.html and use placeholder replacement instead. Also update branchDiff to accept the main branch as a parameter so the caller resolves it once and reuses it for diffStat too.
This commit is contained in:
parent
b8f7aea3b0
commit
909189b745
60
src/commands/browse.html
Normal file
60
src/commands/browse.html
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>{{BRANCH}} — sandlot diff</title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css">
|
||||||
|
<style>
|
||||||
|
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; margin: 0; padding: 0; background: #0d1117; color: #e6edf3; }
|
||||||
|
.header { padding: 24px 32px; border-bottom: 1px solid #30363d; }
|
||||||
|
.header h1 { margin: 0 0 8px; font-size: 24px; font-weight: 600; }
|
||||||
|
.header h1 code { background: #1f2937; padding: 2px 8px; border-radius: 6px; font-size: 22px; }
|
||||||
|
.prompt { color: #8b949e; margin: 0 0 16px; font-style: italic; }
|
||||||
|
.meta { display: flex; gap: 32px; }
|
||||||
|
.meta-section h3 { margin: 0 0 6px; font-size: 13px; text-transform: uppercase; color: #8b949e; letter-spacing: 0.05em; }
|
||||||
|
.meta-section pre { margin: 0; font-size: 13px; line-height: 1.5; color: #c9d1d9; white-space: pre-wrap; }
|
||||||
|
.diff-container { padding: 16px 32px; }
|
||||||
|
/* Override diff2html for dark theme */
|
||||||
|
.d2h-wrapper { background: #0d1117; }
|
||||||
|
.d2h-file-header { background: #161b22; border-color: #30363d; color: #e6edf3; }
|
||||||
|
.d2h-file-wrapper { border-color: #30363d; margin-bottom: 16px; border-radius: 6px; overflow: hidden; }
|
||||||
|
.d2h-code-linenumber { background: #161b22; color: #8b949e; border-color: #30363d; }
|
||||||
|
.d2h-code-line { background: #0d1117; color: #e6edf3; }
|
||||||
|
.d2h-code-side-line { background: #0d1117; }
|
||||||
|
.d2h-del { background: rgba(248,81,73,0.1); }
|
||||||
|
.d2h-ins { background: rgba(63,185,80,0.1); }
|
||||||
|
.d2h-del .d2h-code-line-ctn { background: rgba(248,81,73,0.15); }
|
||||||
|
.d2h-ins .d2h-code-line-ctn { background: rgba(63,185,80,0.15); }
|
||||||
|
.d2h-code-line-ctn { color: #e6edf3; }
|
||||||
|
.d2h-info { background: #161b22; color: #8b949e; border-color: #30363d; }
|
||||||
|
.d2h-tag { background: #1f6feb; color: #fff; }
|
||||||
|
.d2h-file-stats-wrapper { display: flex; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="header">
|
||||||
|
<h1><code>{{BRANCH}}</code></h1>
|
||||||
|
{{PROMPT_SECTION}}
|
||||||
|
<div class="meta">
|
||||||
|
{{LOG_SECTION}}
|
||||||
|
{{STAT_SECTION}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="diff-container" id="diff"></div>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/diff2html/bundles/js/diff2html-ui.min.js"></script>
|
||||||
|
<script>
|
||||||
|
const diffString = {{DIFF_JSON}};
|
||||||
|
const targetElement = document.getElementById("diff");
|
||||||
|
const configuration = {
|
||||||
|
drawFileList: true,
|
||||||
|
matching: "lines",
|
||||||
|
outputFormat: "side-by-side",
|
||||||
|
highlight: true,
|
||||||
|
colorScheme: "dark",
|
||||||
|
};
|
||||||
|
const diff2htmlUi = new Diff2HtmlUI(targetElement, diffString, configuration);
|
||||||
|
diff2htmlUi.draw();
|
||||||
|
diff2htmlUi.highlightCode();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -3,13 +3,15 @@ import * as git from "../git.ts"
|
||||||
import { die } from "../fmt.ts"
|
import { die } from "../fmt.ts"
|
||||||
import { requireSession } from "./helpers.ts"
|
import { requireSession } from "./helpers.ts"
|
||||||
|
|
||||||
|
const template = await Bun.file(new URL("browse.html", import.meta.url).pathname).text()
|
||||||
|
|
||||||
export async function action(branch: string) {
|
export async function action(branch: string) {
|
||||||
const { session } = await requireSession(branch)
|
const { session } = await requireSession(branch)
|
||||||
const worktree = session.worktree
|
const worktree = session.worktree
|
||||||
const main = await git.mainBranch(worktree)
|
const main = await git.mainBranch(worktree)
|
||||||
|
|
||||||
const [diff, log, stat] = await Promise.all([
|
const [diff, log, stat] = await Promise.all([
|
||||||
git.branchDiff(branch, worktree),
|
git.branchDiff(branch, main, worktree),
|
||||||
git.commitLog(`${main}..${branch}`, worktree),
|
git.commitLog(`${main}..${branch}`, worktree),
|
||||||
git.diffStat(`${main}...${branch}`, worktree),
|
git.diffStat(`${main}...${branch}`, worktree),
|
||||||
])
|
])
|
||||||
|
|
@ -18,69 +20,12 @@ export async function action(branch: string) {
|
||||||
die(`No changes on branch "${branch}" compared to ${main}.`)
|
die(`No changes on branch "${branch}" compared to ${main}.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const prompt = session.prompt ? escapeHtml(session.prompt) : ""
|
const html = template
|
||||||
const diffJson = JSON.stringify(diff)
|
.replaceAll("{{BRANCH}}", escapeHtml(branch))
|
||||||
|
.replace("{{PROMPT_SECTION}}", session.prompt ? `<p class="prompt">${escapeHtml(session.prompt)}</p>` : "")
|
||||||
const html = `<!DOCTYPE html>
|
.replace("{{LOG_SECTION}}", log ? `<div class="meta-section"><h3>Commits</h3><pre>${escapeHtml(log)}</pre></div>` : "")
|
||||||
<html lang="en">
|
.replace("{{STAT_SECTION}}", stat ? `<div class="meta-section"><h3>Stats</h3><pre>${escapeHtml(stat)}</pre></div>` : "")
|
||||||
<head>
|
.replace("{{DIFF_JSON}}", JSON.stringify(diff))
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>${escapeHtml(branch)} — sandlot diff</title>
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css">
|
|
||||||
<style>
|
|
||||||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; margin: 0; padding: 0; background: #0d1117; color: #e6edf3; }
|
|
||||||
.header { padding: 24px 32px; border-bottom: 1px solid #30363d; }
|
|
||||||
.header h1 { margin: 0 0 8px; font-size: 24px; font-weight: 600; }
|
|
||||||
.header h1 code { background: #1f2937; padding: 2px 8px; border-radius: 6px; font-size: 22px; }
|
|
||||||
.prompt { color: #8b949e; margin: 0 0 16px; font-style: italic; }
|
|
||||||
.meta { display: flex; gap: 32px; }
|
|
||||||
.meta-section h3 { margin: 0 0 6px; font-size: 13px; text-transform: uppercase; color: #8b949e; letter-spacing: 0.05em; }
|
|
||||||
.meta-section pre { margin: 0; font-size: 13px; line-height: 1.5; color: #c9d1d9; white-space: pre-wrap; }
|
|
||||||
.diff-container { padding: 16px 32px; }
|
|
||||||
/* Override diff2html for dark theme */
|
|
||||||
.d2h-wrapper { background: #0d1117; }
|
|
||||||
.d2h-file-header { background: #161b22; border-color: #30363d; color: #e6edf3; }
|
|
||||||
.d2h-file-wrapper { border-color: #30363d; margin-bottom: 16px; border-radius: 6px; overflow: hidden; }
|
|
||||||
.d2h-code-linenumber { background: #161b22; color: #8b949e; border-color: #30363d; }
|
|
||||||
.d2h-code-line { background: #0d1117; color: #e6edf3; }
|
|
||||||
.d2h-code-side-line { background: #0d1117; }
|
|
||||||
.d2h-del { background: rgba(248,81,73,0.1); }
|
|
||||||
.d2h-ins { background: rgba(63,185,80,0.1); }
|
|
||||||
.d2h-del .d2h-code-line-ctn { background: rgba(248,81,73,0.15); }
|
|
||||||
.d2h-ins .d2h-code-line-ctn { background: rgba(63,185,80,0.15); }
|
|
||||||
.d2h-code-line-ctn { color: #e6edf3; }
|
|
||||||
.d2h-info { background: #161b22; color: #8b949e; border-color: #30363d; }
|
|
||||||
.d2h-tag { background: #1f6feb; color: #fff; }
|
|
||||||
.d2h-file-stats-wrapper { display: flex; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="header">
|
|
||||||
<h1><code>${escapeHtml(branch)}</code></h1>
|
|
||||||
${prompt ? `<p class="prompt">${prompt}</p>` : ""}
|
|
||||||
<div class="meta">
|
|
||||||
${log ? `<div class="meta-section"><h3>Commits</h3><pre>${escapeHtml(log)}</pre></div>` : ""}
|
|
||||||
${stat ? `<div class="meta-section"><h3>Stats</h3><pre>${escapeHtml(stat)}</pre></div>` : ""}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="diff-container" id="diff"></div>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/diff2html/bundles/js/diff2html-ui.min.js"></script>
|
|
||||||
<script>
|
|
||||||
const diffString = ${diffJson};
|
|
||||||
const targetElement = document.getElementById("diff");
|
|
||||||
const configuration = {
|
|
||||||
drawFileList: true,
|
|
||||||
matching: "lines",
|
|
||||||
outputFormat: "side-by-side",
|
|
||||||
highlight: true,
|
|
||||||
colorScheme: "dark",
|
|
||||||
};
|
|
||||||
const diff2htmlUi = new Diff2HtmlUI(targetElement, diffString, configuration);
|
|
||||||
diff2htmlUi.draw();
|
|
||||||
diff2htmlUi.highlightCode();
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>`
|
|
||||||
|
|
||||||
const tmpPath = `/tmp/sandlot-browse-${branch}.html`
|
const tmpPath = `/tmp/sandlot-browse-${branch}.html`
|
||||||
await Bun.write(tmpPath, html)
|
await Bun.write(tmpPath, html)
|
||||||
|
|
|
||||||
|
|
@ -219,8 +219,7 @@ export async function hasNewCommits(worktreePath: string): Promise<boolean> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get the full unified diff of a branch vs main as a string. */
|
/** Get the full unified diff of a branch vs main as a string. */
|
||||||
export async function branchDiff(branch: string, cwd: string): Promise<string> {
|
export async function branchDiff(branch: string, main: string, cwd: string): Promise<string> {
|
||||||
const main = await mainBranch(cwd)
|
|
||||||
const result = await $`git diff ${main}...${branch}`.cwd(cwd).nothrow().quiet()
|
const result = await $`git diff ${main}...${branch}`.cwd(cwd).nothrow().quiet()
|
||||||
if (result.exitCode !== 0) return ""
|
if (result.exitCode !== 0) return ""
|
||||||
return result.text()
|
return result.text()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user