branch-commit-compare
Version:
Git branch comparison tool
730 lines (615 loc) • 22.2 kB
JavaScript
/**
* Modal Module 模态框模块
* 处理所有模态框相关的功能
*/
// IGNORE_REASONS 由HTML模板的数据注入脚本定义
let currentIgnoreHash = null;
let currentIgnoreElement = null;
let currentCommitHash = null;
let currentCommitElement = null;
/**
* 显示忽略原因模态框
* @param {string} hash - 提交哈希
* @param {HTMLElement} element - 触发元素
*/
function showIgnoreModal(hash, element) {
currentIgnoreHash = hash;
currentIgnoreElement = element;
const reasonListElement = document.getElementById("reasonList");
reasonListElement.innerHTML = "";
IGNORE_REASONS.forEach((reason, index) => {
const optionElement = document.createElement("div");
optionElement.className = "reason-option";
const radioInput = document.createElement("input");
radioInput.type = "radio";
radioInput.id = `reason-${index}`;
radioInput.name = "ignoreReason";
radioInput.value = reason;
if (index === 0) {
radioInput.checked = true;
optionElement.classList.add("selected");
}
const label = document.createElement("label");
label.htmlFor = `reason-${index}`;
label.textContent = reason;
optionElement.appendChild(radioInput);
optionElement.appendChild(label);
optionElement.addEventListener("click", function (e) {
if (e.target !== radioInput) {
radioInput.checked = true;
if (reason === "其他原因") {
document.getElementById("customReasonInput").classList.add("show");
} else {
document.getElementById("customReasonInput").classList.remove("show");
}
document.querySelectorAll(".reason-option").forEach((opt) => {
opt.classList.remove("selected");
});
this.classList.add("selected");
}
});
if (reason === "其他原因") {
radioInput.addEventListener("change", function () {
document.getElementById("customReasonInput").classList.add("show");
document.querySelectorAll(".reason-option").forEach((opt) => {
opt.classList.remove("selected");
});
optionElement.classList.add("selected");
});
} else {
radioInput.addEventListener("change", function () {
document.getElementById("customReasonInput").classList.remove("show");
document.querySelectorAll(".reason-option").forEach((opt) => {
opt.classList.remove("selected");
});
optionElement.classList.add("selected");
});
}
reasonListElement.appendChild(optionElement);
});
document.getElementById("customReasonText").value = "";
document.getElementById("customReasonInput").classList.remove("show");
const modal = document.getElementById("ignoreModal");
modal.style.display = "flex";
modal.classList.add("show");
}
/**
* 关闭忽略原因模态框
*/
function closeIgnoreModal() {
const modal = document.getElementById("ignoreModal");
modal.style.display = "none";
modal.classList.remove("show");
currentIgnoreHash = null;
currentIgnoreElement = null;
}
/**
* 确认忽略
*/
async function confirmIgnore() {
const selectedReason = document.querySelector(
'input[name="ignoreReason"]:checked'
).value;
const customReason = document.getElementById("customReasonText").value.trim();
const finalReason =
selectedReason === "其他原因" ? customReason : selectedReason;
if (selectedReason === "其他原因" && !customReason) {
alert("请输入自定义的忽略原因");
return;
}
showLoading();
saveCurrentFilterState();
try {
// 先加载最新的完整数据
const latestIgnoredCommits = await loadIgnoredCommits();
// 检查是否已存在
const index = latestIgnoredCommits.findIndex(
(item) => item.hash === currentIgnoreHash
);
// 如果不存在,添加新的忽略记录
if (index === -1) {
latestIgnoredCommits.push({
hash: currentIgnoreHash,
reason: finalReason,
timestamp: new Date().toISOString(),
});
} else {
// 如果已存在,更新原因和时间戳
latestIgnoredCommits[index].reason = finalReason;
latestIgnoredCommits[index].timestamp = new Date().toISOString();
}
// 保存更新后的完整数据
await saveIgnoredCommits(latestIgnoredCommits);
// 重新加载数据到应用状态
appState.ignoredCommits = await loadIgnoredCommits();
// 只更新改变的卡片
renderCommits(false, currentIgnoreHash);
restoreFilterState();
closeIgnoreModal();
showSuccessMessage("忽略提交成功");
} catch (error) {
console.error("保存忽略提交失败:", error);
alert("保存忽略提交失败: " + error.message);
} finally {
hideLoading();
}
}
/**
* 切换忽略状态
* @param {string} hash - 提交哈希
* @param {HTMLElement} element - 触发元素
*/
async function toggleIgnoreCommit(hash, element) {
try {
// 先加载最新的完整数据来检查状态
const latestIgnoredCommits = await loadIgnoredCommits();
const index = latestIgnoredCommits.findIndex((item) => item.hash === hash);
if (index === -1) {
// 不存在,显示模态框添加忽略
showIgnoreModal(hash, element);
} else {
// 已存在,直接删除(取消忽略)
showLoading();
saveCurrentFilterState();
const updatedCommits = latestIgnoredCommits.filter(
(item) => item.hash !== hash
);
await saveIgnoredCommits(updatedCommits);
// 重新加载数据到应用状态
appState.ignoredCommits = await loadIgnoredCommits();
// 只更新改变的卡片
renderCommits(false, hash);
restoreFilterState();
showSuccessMessage("取消忽略成功");
hideLoading();
}
} catch (error) {
console.error("切换忽略状态失败:", error);
alert("操作失败: " + error.message);
hideLoading();
}
}
/**
* 显示备注模态框
* @param {string} hash - 提交哈希
* @param {HTMLElement} element - 触发元素
*/
function showRemarkModal(hash, element) {
currentCommitHash = hash;
currentCommitElement = element;
const remarkText = document.getElementById("remarkText");
remarkText.value = getRemarkContent(hash);
const deleteButton = document.getElementById("deleteRemarkBtn");
if (hasRemark(hash)) {
deleteButton.style.display = "block";
} else {
deleteButton.style.display = "none";
}
const modal = document.getElementById("remarkModal");
modal.style.display = "flex";
modal.classList.add("show");
}
/**
* 关闭备注模态框
*/
function closeRemarkModal() {
const modal = document.getElementById("remarkModal");
modal.style.display = "none";
modal.classList.remove("show");
currentCommitHash = null;
currentCommitElement = null;
}
/**
* 确认备注
*/
async function confirmRemark() {
const remarkText = document.getElementById("remarkText").value.trim();
console.log("开始保存备注,哈希值:", currentCommitHash, "内容:", remarkText);
try {
showLoading();
saveCurrentFilterState();
// 先加载最新的完整数据
const latestRemarks = await loadCommitRemarks();
const existingIndex = latestRemarks.findIndex(
(r) => r.hash === currentCommitHash
);
if (remarkText) {
if (existingIndex >= 0) {
// 更新现有备注
latestRemarks[existingIndex].content = remarkText;
latestRemarks[existingIndex].timestamp = new Date().toISOString();
console.log("已更新现有备注");
} else {
// 添加新备注
latestRemarks.push({
hash: currentCommitHash,
content: remarkText,
timestamp: new Date().toISOString(),
});
console.log("已添加新备注");
}
} else if (existingIndex >= 0) {
// 删除备注
latestRemarks.splice(existingIndex, 1);
console.log("已删除备注");
}
// 保存更新后的完整数据
await saveCommitRemarks(latestRemarks);
console.log("服务器保存备注完成");
// 重新加载数据到应用状态
appState.commitRemarks = await loadCommitRemarks();
closeRemarkModal();
// 只更新改变的卡片
renderCommits(false, currentCommitHash);
restoreFilterState();
showSuccessMessage("备注已保存");
} catch (error) {
console.error("保存备注操作失败:", error);
alert("保存备注失败: " + error.message);
} finally {
hideLoading();
}
}
/**
* 删除备注
*/
async function deleteRemark() {
if (!currentCommitHash) return;
try {
showLoading();
saveCurrentFilterState();
// 先加载最新的完整数据
const latestRemarks = await loadCommitRemarks();
const existingIndex = latestRemarks.findIndex(
(r) => r.hash === currentCommitHash
);
if (existingIndex >= 0) {
// 从完整数据中移除该备注
latestRemarks.splice(existingIndex, 1);
console.log("已删除备注");
// 保存更新后的完整数据
await saveCommitRemarks(latestRemarks);
// 重新加载数据到应用状态
appState.commitRemarks = await loadCommitRemarks();
closeRemarkModal();
// 只更新改变的卡片
renderCommits(false, currentCommitHash);
restoreFilterState();
showSuccessMessage("备注已删除");
}
} catch (error) {
console.error("删除备注失败:", error);
alert("删除备注失败: " + error.message);
} finally {
hideLoading();
}
}
/**
* 显示加载动画
*/
function showLoading() {
document.getElementById("loadingOverlay").classList.add("show");
}
/**
* 隐藏加载动画
*/
function hideLoading() {
document.getElementById("loadingOverlay").classList.remove("show");
}
/**
* 显示成功消息
* @param {string} message - 消息内容
*/
function showSuccessMessage(message) {
const successToast = document.createElement("div");
successToast.className = "success-toast";
successToast.textContent = message;
document.body.appendChild(successToast);
setTimeout(() => {
successToast.classList.add("show");
}, 100);
setTimeout(() => {
successToast.classList.remove("show");
setTimeout(() => successToast.remove(), 500);
}, 2000);
}
/**
* 在VS Code中查看提交
* @param {string} commitHash - 提交哈希
*/
function openInVSCode(commitHash) {
const modal = document.createElement("div");
modal.style.cssText =
"position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;z-index:9999";
const content = document.createElement("div");
content.style.cssText =
"background:var(--color-bg);padding:var(--spacing-6);border-radius:var(--radius-container);max-width:500px;width:90%;box-shadow:var(--shadow-extruded-hover)";
const title = document.createElement("h3");
title.textContent = "查看提交的Git命令";
title.style.marginTop = "0";
title.style.marginBottom = "var(--spacing-4)";
content.appendChild(title);
const commands = [
{ name: "查看提交详情", command: `git show ${commitHash}` },
{
name: "查看提交更改的文件",
command: `git show --name-only ${commitHash}`,
},
{
name: "查看提交的完整差异",
command: `git show --color-words ${commitHash}`,
},
];
commands.forEach((cmd) => {
const section = document.createElement("div");
section.style.marginBottom = "var(--spacing-4)";
const label = document.createElement("div");
label.textContent = cmd.name;
label.style.fontWeight = "var(--font-weight-semibold)";
label.style.marginBottom = "var(--spacing-2)";
const commandArea = document.createElement("div");
commandArea.style.display = "flex";
commandArea.style.gap = "var(--spacing-2)";
const cmdText = document.createElement("code");
cmdText.textContent = cmd.command;
cmdText.style.cssText =
"padding:var(--spacing-2);background:rgba(163,177,198,0.1);border-radius:var(--radius-small);font-family:var(--font-mono);flex:1;overflow-x:auto";
const copyBtn = document.createElement("button");
copyBtn.textContent = "复制";
copyBtn.className = "btn btn-small";
copyBtn.onclick = () => {
copyToClipboard(cmd.command, copyBtn);
};
commandArea.appendChild(cmdText);
commandArea.appendChild(copyBtn);
section.appendChild(label);
section.appendChild(commandArea);
content.appendChild(section);
});
const closeButton = document.createElement("button");
closeButton.textContent = "关闭";
closeButton.className = "btn";
closeButton.style.width = "100%";
closeButton.style.marginTop = "var(--spacing-4)";
closeButton.onclick = () => document.body.removeChild(modal);
content.appendChild(closeButton);
modal.addEventListener("click", (e) => {
if (e.target === modal) {
document.body.removeChild(modal);
}
});
modal.appendChild(content);
document.body.appendChild(modal);
}
/**
* 在VS Code中比较两个提交
* @param {string} sourceHash - 源提交哈希
* @param {string} targetHash - 目标提交哈希
*/
function compareCommitsInVSCode(sourceHash, targetHash) {
const modal = document.createElement("div");
modal.style.cssText =
"position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;z-index:9999";
const content = document.createElement("div");
content.style.cssText =
"background:var(--color-bg);padding:var(--spacing-6);border-radius:var(--radius-container);max-width:500px;width:90%;box-shadow:var(--shadow-extruded-hover)";
const title = document.createElement("h3");
title.textContent = "比较两个提交的Git命令";
title.style.marginTop = "0";
title.style.marginBottom = "var(--spacing-4)";
content.appendChild(title);
const commands = [
{
name: "显示两个提交之间的差异",
command: `git diff ${sourceHash} ${targetHash}`,
},
{
name: "查看更改的文件列表",
command: `git diff --name-only ${sourceHash} ${targetHash}`,
},
{
name: "查看汇总统计信息",
command: `git diff --stat ${sourceHash} ${targetHash}`,
},
];
commands.forEach((cmd) => {
const section = document.createElement("div");
section.style.marginBottom = "var(--spacing-4)";
const label = document.createElement("div");
label.textContent = cmd.name;
label.style.fontWeight = "var(--font-weight-semibold)";
label.style.marginBottom = "var(--spacing-2)";
const commandArea = document.createElement("div");
commandArea.style.display = "flex";
commandArea.style.gap = "var(--spacing-2)";
const cmdText = document.createElement("code");
cmdText.textContent = cmd.command;
cmdText.style.cssText =
"padding:var(--spacing-2);background:rgba(163,177,198,0.1);border-radius:var(--radius-small);font-family:var(--font-mono);flex:1;overflow-x:auto;white-space:nowrap";
const copyBtn = document.createElement("button");
copyBtn.textContent = "复制";
copyBtn.className = "btn btn-small";
copyBtn.onclick = () => {
copyToClipboard(cmd.command, copyBtn);
};
commandArea.appendChild(cmdText);
commandArea.appendChild(copyBtn);
section.appendChild(label);
section.appendChild(commandArea);
content.appendChild(section);
});
const closeButton = document.createElement("button");
closeButton.textContent = "关闭";
closeButton.className = "btn";
closeButton.style.width = "100%";
closeButton.style.marginTop = "var(--spacing-4)";
closeButton.onclick = () => document.body.removeChild(modal);
content.appendChild(closeButton);
modal.addEventListener("click", (e) => {
if (e.target === modal) {
document.body.removeChild(modal);
}
});
modal.appendChild(content);
document.body.appendChild(modal);
}
/**
* 查看提交变更
* @param {string} commitHash - 提交哈希
* @param {string} commitMessage - 提交消息
*/
function viewCommitChanges(commitHash, commitMessage) {
console.log("查看提交变更:", commitHash, commitMessage);
const originalBodyStyle = document.body.style.cssText;
document.body.style.overflow = "hidden";
document.body.style.paddingRight = "15px";
const modal = document.createElement("div");
modal.style.cssText =
"position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;z-index:9999";
const content = document.createElement("div");
content.style.cssText =
"background:#ffffff;padding:var(--spacing-5);border-radius:var(--radius-container);max-width:90%;width:90%;max-height:90vh;display:flex;flex-direction:column;box-shadow:var(--shadow-extruded-hover)";
const header = document.createElement("div");
header.style.cssText =
"display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--spacing-3);border-bottom:1px solid rgba(163,177,198,0.2);padding-bottom:var(--spacing-3)";
const titleArea = document.createElement("div");
const title = document.createElement("h3");
title.textContent = "提交详情";
title.style.margin = "0 0 var(--spacing-2) 0";
const subtitle = document.createElement("p");
subtitle.innerHTML = `<code style="font-family:var(--font-mono);background:rgba(163,177,198,0.15);padding:2px var(--spacing-2);border-radius:var(--radius-small)">${commitHash}</code> - ${escapeHtml(
commitMessage
)}`;
subtitle.style.margin = "0";
subtitle.style.color = "var(--color-muted)";
subtitle.style.fontSize = "var(--font-size-sm)";
titleArea.appendChild(title);
titleArea.appendChild(subtitle);
const closeIcon = document.createElement("button");
closeIcon.innerHTML = "✕";
closeIcon.className = "modal-close-button";
closeIcon.style.cssText = `
width: 44px;
height: 44px;
min-width: 44px;
min-height: 44px;
border-radius: 50%;
border: none;
background: var(--color-bg);
color: var(--color-muted);
font-size: 24px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
box-shadow: var(--shadow-extruded-small);
transition: all var(--transition-base);
flex-shrink: 0;
`;
closeIcon.addEventListener("mouseenter", () => {
closeIcon.style.color = "var(--color-danger)";
closeIcon.style.transform = "scale(1.05)";
});
closeIcon.addEventListener("mouseleave", () => {
closeIcon.style.color = "var(--color-muted)";
closeIcon.style.transform = "scale(1)";
});
const closeModal = () => {
document.body.removeChild(modal);
document.body.style.cssText = originalBodyStyle;
document.removeEventListener("keydown", handleKeyDown);
};
closeIcon.addEventListener("click", closeModal);
header.appendChild(titleArea);
header.appendChild(closeIcon);
content.appendChild(header);
const diffContainer = document.createElement("div");
diffContainer.style.cssText =
"flex:1;overflow:auto;min-height:0;max-height:calc(90vh - 100px);padding-right:var(--spacing-2)";
const diffViewer = document.createElement("div");
diffViewer.className = "diff-viewer";
diffViewer.innerHTML =
'<div style="padding:var(--spacing-5);text-align:center;color:var(--color-muted)">正在加载变更数据...</div>';
diffContainer.appendChild(diffViewer);
content.appendChild(diffContainer);
modal.addEventListener("click", (e) => {
if (e.target === modal) {
closeModal();
}
});
const handleKeyDown = (e) => {
if (e.key === "Escape") {
closeModal();
}
};
document.addEventListener("keydown", handleKeyDown);
modal.appendChild(content);
document.body.appendChild(modal);
// 获取diff数据
fetchGitDiff(commitHash)
.then((data) => {
console.log("获取到diff数据:", data);
renderDiffData(data, diffViewer);
})
.catch((error) => {
console.error("获取提交变更数据失败:", error);
diffViewer.innerHTML = `
<div style="padding:var(--spacing-5);text-align:center;color:var(--color-danger);background-color:rgba(220,53,69,0.1);border-radius:var(--radius-base)">
<p style="margin:0 0 var(--spacing-2) 0;font-weight:var(--font-weight-semibold)">获取提交变更数据失败</p>
<p style="margin:0;font-size:var(--font-size-sm)">${escapeHtml(
error.message
)}</p>
</div>
`;
});
}
/**
* 使用Diff2Html渲染差异数据
* @param {Object} data - diff数据
* @param {HTMLElement} diffViewer - 差异查看器元素
*/
function renderDiffData(data, diffViewer) {
console.log("渲染差异数据:", data);
if (!data || !data.diff || data.diff.trim() === "") {
diffViewer.innerHTML = `
<div style="padding:var(--spacing-5);text-align:center;color:var(--color-muted);background-color:rgba(163,177,198,0.1);border-radius:var(--radius-base)">
此提交没有文件变更或数据格式不正确
</div>
`;
return;
}
const diffText = data.diff;
diffViewer.innerHTML = "";
try {
const ui = new Diff2HtmlUI(diffViewer, diffText, {
drawFileList: true,
matching: "lines",
outputFormat: "side-by-side",
highlight: true,
});
ui.draw();
ui.highlightCode();
// 只隐藏行号,不添加任何其他样式
const style = document.createElement("style");
style.textContent = `
/* 隐藏所有行号 */
.d2h-code-line-prefix,
.d2h-code-linenumber,
.d2h-code-side-linenumber,
.d2h-line-num1,
.d2h-line-num2,
.line-num1,
.line-num2 {
display: none !important;
}
`;
diffViewer.appendChild(style);
} catch (e) {
console.error("Diff2Html 渲染失败:", e);
diffViewer.innerHTML = `
<div style="padding:var(--spacing-5);text-align:center;color:var(--color-danger);background-color:rgba(220,53,69,0.1);border-radius:var(--radius-base)">
无法渲染差异视图:${escapeHtml(e.message)}
</div>
`;
}
}