feat(theme): add style switcher and centralize diff logic
Extract duplicated diff rendering logic into shared/diff-page.js Implement theme switcher component across all templates
This commit is contained in:
@@ -83,6 +83,27 @@
|
|||||||
.view-option.active {
|
.view-option.active {
|
||||||
@apply text-cyber-cyan border-cyber-cyan bg-cyber-cyan/10 shadow-[inset_0_0_10px_rgba(0,240,255,0.2)];
|
@apply text-cyber-cyan border-cyber-cyan bg-cyber-cyan/10 shadow-[inset_0_0_10px_rgba(0,240,255,0.2)];
|
||||||
}
|
}
|
||||||
|
.style-switcher {
|
||||||
|
@apply flex items-center gap-3;
|
||||||
|
}
|
||||||
|
.style-switcher-label {
|
||||||
|
@apply text-xs text-gray-500 tracking-widest;
|
||||||
|
}
|
||||||
|
.style-switcher-select {
|
||||||
|
background: #050505;
|
||||||
|
border: 1px solid #333;
|
||||||
|
color: #fcee0a;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
font-family: "Fira Code", monospace;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.style-switcher-select:hover,
|
||||||
|
.style-switcher-select:focus {
|
||||||
|
border-color: #00f0ff;
|
||||||
|
box-shadow: 0 0 12px rgba(0, 240, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
/* 面板边框发光效果 */
|
/* 面板边框发光效果 */
|
||||||
.cyber-panel {
|
.cyber-panel {
|
||||||
@@ -313,6 +334,8 @@ function initCyberware() {
|
|||||||
UNIFIED
|
UNIFIED
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div data-style-switcher></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
<!-- 操作按钮 -->
|
||||||
@@ -351,184 +374,40 @@ function initCyberware() {
|
|||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/diff2html@3.4.47/bundles/js/diff2html.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/diff2html@3.4.47/bundles/js/diff2html.min.js"></script>
|
||||||
|
|
||||||
<!-- 核心逻辑 -->
|
<script src="../shared/diff-page.js"></script>
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
window.initDiffPage({
|
||||||
"use strict";
|
themeId: "cyberpunk",
|
||||||
|
currentThemePath: "cyberpunk/index.html",
|
||||||
const leftTextarea = document.getElementById("leftTextarea");
|
switcherLabel: "SKIN_",
|
||||||
const rightTextarea = document.getElementById("rightTextarea");
|
switcherAriaLabel: "Switch cyberpunk skin",
|
||||||
const compareBtn = document.getElementById("compareBtn");
|
fileLabels: {
|
||||||
const swapBtn = document.getElementById("swapBtn");
|
left: "ORIGINAL",
|
||||||
const exampleBtn = document.getElementById("exampleBtn");
|
right: "MODIFIED",
|
||||||
const clearLeftBtn = document.getElementById("clearLeftBtn");
|
},
|
||||||
const clearRightBtn = document.getElementById("clearRightBtn");
|
example: {
|
||||||
const languageSelect = document.getElementById("languageSelect");
|
left: `function initCyberware() {\n console.log("Booting optics...");\n return "Ready";\n}`,
|
||||||
const diffOutput = document.getElementById("diff-output");
|
right: `function initCyberware() {\n console.log("Booting Kiroshi optics v3.0...");\n connectToNetwork();\n return "Online";\n}`,
|
||||||
const diffStats = document.getElementById("diffStats");
|
language: "javascript",
|
||||||
const viewOptions = document.querySelectorAll(".view-option");
|
},
|
||||||
|
messages: {
|
||||||
let currentView = "side-by-side";
|
generateError: (error) =>
|
||||||
|
`<div class="p-10 text-center font-mono text-cyber-pink tracking-widest">SYS_ERR: ${error.message}</div>`,
|
||||||
function getSelectedLanguage() {
|
renderError: () =>
|
||||||
return languageSelect.value;
|
'<div class="p-10 text-center font-mono text-cyber-pink tracking-widest">RENDER_FAILED</div>',
|
||||||
|
blankResult:
|
||||||
|
'<div class="p-10 text-center font-mono text-gray-500 tracking-widest">DATA_MATCH // NO_DIFF_FOUND</div>',
|
||||||
|
blankStats: "<span class='text-gray-500'>SYNCED</span>",
|
||||||
|
generateErrorStats: "ERR",
|
||||||
|
renderErrorStats: "ERR",
|
||||||
|
},
|
||||||
|
formatStats: ({ added, deleted, identical }) => {
|
||||||
|
if (identical) {
|
||||||
|
return "<span class='text-gray-500'>SYNCED // NO_CHANGES</span>";
|
||||||
}
|
}
|
||||||
|
return `<span class='text-cyber-cyan'>+${added} INS</span> <span class='text-gray-600 mx-2'>|</span> <span class='text-cyber-pink'>-${deleted} DEL</span>`;
|
||||||
function getHighlightConfig() {
|
},
|
||||||
const lang = getSelectedLanguage();
|
|
||||||
if (lang === "plaintext") return false;
|
|
||||||
return { enabled: true, language: lang };
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateUnifiedDiff(original, modified) {
|
|
||||||
return Diff.createTwoFilesPatch(
|
|
||||||
"ORIGINAL",
|
|
||||||
"MODIFIED",
|
|
||||||
original || "",
|
|
||||||
modified || "",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
{ context: 4 },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderDiff() {
|
|
||||||
const leftText = leftTextarea.value;
|
|
||||||
const rightText = rightTextarea.value;
|
|
||||||
|
|
||||||
let diffString;
|
|
||||||
try {
|
|
||||||
diffString = generateUnifiedDiff(leftText, rightText);
|
|
||||||
} catch (e) {
|
|
||||||
diffOutput.innerHTML = `<div class="p-10 text-center font-mono text-cyber-pink tracking-widest">SYS_ERR: ${e.message}</div>`;
|
|
||||||
diffStats.textContent = "ERR";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const configuration = {
|
|
||||||
drawFileList: false,
|
|
||||||
matching: "lines",
|
|
||||||
outputFormat: currentView,
|
|
||||||
highlight: getHighlightConfig(),
|
|
||||||
renderNothingWhenEmpty: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let diffHtml = "";
|
|
||||||
try {
|
|
||||||
diffHtml = Diff2Html.html(diffString, configuration);
|
|
||||||
} catch (e) {
|
|
||||||
diffOutput.innerHTML = `<div class="p-10 text-center font-mono text-cyber-pink tracking-widest">RENDER_FAILED</div>`;
|
|
||||||
diffStats.textContent = "ERR";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
diffOutput.innerHTML = diffHtml;
|
|
||||||
|
|
||||||
const selectedLang = getSelectedLanguage();
|
|
||||||
if (selectedLang !== "plaintext") {
|
|
||||||
const codeBlocks = diffOutput.querySelectorAll("code");
|
|
||||||
if (codeBlocks.length > 0 && window.hljs) {
|
|
||||||
codeBlocks.forEach((block) => {
|
|
||||||
block.classList.forEach((cls) => {
|
|
||||||
if (cls.startsWith("language-")) block.classList.remove(cls);
|
|
||||||
});
|
});
|
||||||
block.classList.add(`language-${selectedLang}`);
|
|
||||||
if (block.dataset.highlighted) {
|
|
||||||
delete block.dataset.highlighted;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
hljs.highlightElement(block);
|
|
||||||
} catch (e) {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const addedLines = diffOutput.querySelectorAll(".d2h-ins").length;
|
|
||||||
const deletedLines = diffOutput.querySelectorAll(".d2h-del").length;
|
|
||||||
if (addedLines === 0 && deletedLines === 0) {
|
|
||||||
diffStats.innerHTML =
|
|
||||||
"<span class='text-gray-500'>SYNCED // NO_CHANGES</span>";
|
|
||||||
} else {
|
|
||||||
diffStats.innerHTML = `<span class='text-cyber-cyan'>+${addedLines} INS</span> <span class='text-gray-600 mx-2'>|</span> <span class='text-cyber-pink'>-${deletedLines} DEL</span>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!diffHtml.trim() || diffOutput.innerText.trim() === "") {
|
|
||||||
diffOutput.innerHTML =
|
|
||||||
'<div class="p-10 text-center font-mono text-gray-500 tracking-widest">DATA_MATCH // NO_DIFF_FOUND</div>';
|
|
||||||
diffStats.innerHTML = "<span class='text-gray-500'>SYNCED</span>";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setActiveView(view) {
|
|
||||||
currentView = view;
|
|
||||||
viewOptions.forEach((btn) => {
|
|
||||||
const val = btn.getAttribute("data-view");
|
|
||||||
if (val === view) {
|
|
||||||
btn.classList.add("active");
|
|
||||||
} else {
|
|
||||||
btn.classList.remove("active");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (
|
|
||||||
diffOutput.querySelector(".d2h-wrapper") ||
|
|
||||||
diffOutput.children.length > 0
|
|
||||||
) {
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadExample() {
|
|
||||||
leftTextarea.value = `function initCyberware() {\n console.log("Booting optics...");\n return "Ready";\n}`;
|
|
||||||
rightTextarea.value = `function initCyberware() {\n console.log("Booting Kiroshi optics v3.0...");\n connectToNetwork();\n return "Online";\n}`;
|
|
||||||
languageSelect.value = "javascript";
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
|
|
||||||
function swapTexts() {
|
|
||||||
const temp = leftTextarea.value;
|
|
||||||
leftTextarea.value = rightTextarea.value;
|
|
||||||
rightTextarea.value = temp;
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearLeft() {
|
|
||||||
leftTextarea.value = "";
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
function clearRight() {
|
|
||||||
rightTextarea.value = "";
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
|
|
||||||
compareBtn.addEventListener("click", renderDiff);
|
|
||||||
swapBtn.addEventListener("click", swapTexts);
|
|
||||||
exampleBtn.addEventListener("click", loadExample);
|
|
||||||
clearLeftBtn.addEventListener("click", clearLeft);
|
|
||||||
clearRightBtn.addEventListener("click", clearRight);
|
|
||||||
|
|
||||||
languageSelect.addEventListener("change", () => {
|
|
||||||
if (
|
|
||||||
diffOutput.querySelector(".d2h-wrapper") ||
|
|
||||||
diffOutput.children.length > 0
|
|
||||||
)
|
|
||||||
renderDiff();
|
|
||||||
});
|
|
||||||
|
|
||||||
viewOptions.forEach((btn) => {
|
|
||||||
btn.addEventListener("click", (e) => {
|
|
||||||
setActiveView(e.currentTarget.getAttribute("data-view"));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener("keydown", (e) => {
|
|
||||||
if ((e.ctrlKey || e.metaKey) && e.key === "Enter") {
|
|
||||||
e.preventDefault();
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener("DOMContentLoaded", renderDiff);
|
|
||||||
})();
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
238
exe/index.html
238
exe/index.html
@@ -72,6 +72,26 @@
|
|||||||
.view-option.active {
|
.view-option.active {
|
||||||
@apply border-t-win-borderDark border-l-win-borderDark border-b-win-borderLight border-r-win-borderLight pt-[5px] pb-[3px] pl-[17px] pr-[15px] bg-[url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABZJREFUeNpi2rVrdz8DAwMDMwIGAAgwAEYQAWtO1y8gAAAAAElFTkSuQmCC')] /* 经典棋盘格背景 */;
|
@apply border-t-win-borderDark border-l-win-borderDark border-b-win-borderLight border-r-win-borderLight pt-[5px] pb-[3px] pl-[17px] pr-[15px] bg-[url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABZJREFUeNpi2rVrdz8DAwMDMwIGAAgwAEYQAWtO1y8gAAAAAElFTkSuQmCC')] /* 经典棋盘格背景 */;
|
||||||
}
|
}
|
||||||
|
.style-switcher {
|
||||||
|
@apply flex items-center gap-2;
|
||||||
|
}
|
||||||
|
.style-switcher-label {
|
||||||
|
@apply text-lg;
|
||||||
|
}
|
||||||
|
.style-switcher-select {
|
||||||
|
background: #ffffff;
|
||||||
|
border: 2px solid;
|
||||||
|
border-top-color: #000000;
|
||||||
|
border-left-color: #000000;
|
||||||
|
border-bottom-color: #ffffff;
|
||||||
|
border-right-color: #ffffff;
|
||||||
|
color: #000000;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: "VT323", monospace;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
outline: none;
|
||||||
|
padding: 0.125rem 0.5rem;
|
||||||
|
}
|
||||||
/* JS 动态插入的空状态提示 */
|
/* JS 动态插入的空状态提示 */
|
||||||
.empty-message {
|
.empty-message {
|
||||||
@apply p-8 text-center font-retro text-xl text-gray-600 bg-white;
|
@apply p-8 text-center font-retro text-xl text-gray-600 bg-white;
|
||||||
@@ -269,6 +289,8 @@ function hello() {
|
|||||||
UNIFIED
|
UNIFIED
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div data-style-switcher></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
<!-- 操作按钮 -->
|
||||||
@@ -312,196 +334,38 @@ function hello() {
|
|||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/diff2html@3.4.47/bundles/js/diff2html.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/diff2html@3.4.47/bundles/js/diff2html.min.js"></script>
|
||||||
|
|
||||||
<!-- 核心逻辑 (保持不变,仅修改了统计信息的输出格式以符合复古风格) -->
|
<script src="../shared/diff-page.js"></script>
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
window.initDiffPage({
|
||||||
"use strict";
|
themeId: "exe",
|
||||||
|
currentThemePath: "exe/index.html",
|
||||||
const leftTextarea = document.getElementById("leftTextarea");
|
switcherLabel: "SKIN:",
|
||||||
const rightTextarea = document.getElementById("rightTextarea");
|
switcherAriaLabel: "Switch retro EXE skin",
|
||||||
const compareBtn = document.getElementById("compareBtn");
|
fileLabels: {
|
||||||
const swapBtn = document.getElementById("swapBtn");
|
left: "ORIGINAL",
|
||||||
const exampleBtn = document.getElementById("exampleBtn");
|
right: "MODIFIED",
|
||||||
const clearLeftBtn = document.getElementById("clearLeftBtn");
|
},
|
||||||
const clearRightBtn = document.getElementById("clearRightBtn");
|
example: {
|
||||||
const languageSelect = document.getElementById("languageSelect");
|
left: `function hello() {\n console.log("Hello World");\n return "Hi";\n}`,
|
||||||
const diffOutput = document.getElementById("diff-output");
|
right: `function hello() {\n console.log("Hello, Diff Checker!");\n return "Hey there";\n}`,
|
||||||
const diffStats = document.getElementById("diffStats");
|
language: "javascript",
|
||||||
const viewOptions = document.querySelectorAll(".view-option");
|
},
|
||||||
|
messages: {
|
||||||
let currentView = "side-by-side";
|
generateError: (error) =>
|
||||||
|
`<div class="empty-message" style="color:red;">ERROR: ${error.message}</div>`,
|
||||||
function getSelectedLanguage() {
|
renderError: () => '<div class="empty-message">RENDER FAILED.</div>',
|
||||||
return languageSelect.value;
|
blankResult: '<div class="empty-message">FILES ARE IDENTICAL.</div>',
|
||||||
|
blankStats: "NO_CHANGES",
|
||||||
|
generateErrorStats: "ERR",
|
||||||
|
renderErrorStats: "ERR",
|
||||||
|
},
|
||||||
|
formatStats: ({ added, deleted, identical }) => {
|
||||||
|
if (identical) {
|
||||||
|
return "NO_CHANGES_DETECTED";
|
||||||
}
|
}
|
||||||
|
return `[ +${added} INS | -${deleted} DEL ]`;
|
||||||
function getHighlightConfig() {
|
},
|
||||||
const lang = getSelectedLanguage();
|
|
||||||
if (lang === "plaintext") return false;
|
|
||||||
return { enabled: true, language: lang };
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateUnifiedDiff(original, modified) {
|
|
||||||
return Diff.createTwoFilesPatch(
|
|
||||||
"ORIGINAL",
|
|
||||||
"MODIFIED",
|
|
||||||
original || "",
|
|
||||||
modified || "",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
{ context: 4 },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderDiff() {
|
|
||||||
const leftText = leftTextarea.value;
|
|
||||||
const rightText = rightTextarea.value;
|
|
||||||
|
|
||||||
let diffString;
|
|
||||||
try {
|
|
||||||
diffString = generateUnifiedDiff(leftText, rightText);
|
|
||||||
} catch (e) {
|
|
||||||
diffOutput.innerHTML = `<div class="empty-message" style="color:red;">ERROR: ${e.message}</div>`;
|
|
||||||
diffStats.textContent = "ERR";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const configuration = {
|
|
||||||
drawFileList: false,
|
|
||||||
matching: "lines",
|
|
||||||
outputFormat: currentView,
|
|
||||||
highlight: getHighlightConfig(),
|
|
||||||
renderNothingWhenEmpty: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let diffHtml = "";
|
|
||||||
try {
|
|
||||||
diffHtml = Diff2Html.html(diffString, configuration);
|
|
||||||
} catch (e) {
|
|
||||||
console.warn(e);
|
|
||||||
diffOutput.innerHTML = `<div class="empty-message">RENDER FAILED.</div>`;
|
|
||||||
diffStats.textContent = "ERR";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
diffOutput.innerHTML = diffHtml;
|
|
||||||
|
|
||||||
const selectedLang = getSelectedLanguage();
|
|
||||||
if (selectedLang !== "plaintext") {
|
|
||||||
const codeBlocks = diffOutput.querySelectorAll("code");
|
|
||||||
if (codeBlocks.length > 0 && window.hljs) {
|
|
||||||
codeBlocks.forEach((block) => {
|
|
||||||
block.classList.forEach((cls) => {
|
|
||||||
if (cls.startsWith("language-")) block.classList.remove(cls);
|
|
||||||
});
|
});
|
||||||
block.classList.add(`language-${selectedLang}`);
|
|
||||||
if (block.dataset.highlighted) {
|
|
||||||
delete block.dataset.highlighted;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
hljs.highlightElement(block);
|
|
||||||
} catch (e) {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const addedLines = diffOutput.querySelectorAll(".d2h-ins").length;
|
|
||||||
const deletedLines = diffOutput.querySelectorAll(".d2h-del").length;
|
|
||||||
if (addedLines === 0 && deletedLines === 0) {
|
|
||||||
diffStats.innerHTML = "NO_CHANGES_DETECTED";
|
|
||||||
} else {
|
|
||||||
// 复古终端风格的统计输出
|
|
||||||
diffStats.innerHTML = `[ +${addedLines} INS | -${deletedLines} DEL ]`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!diffHtml.trim() || diffOutput.innerText.trim() === "") {
|
|
||||||
diffOutput.innerHTML =
|
|
||||||
'<div class="empty-message">FILES ARE IDENTICAL.</div>';
|
|
||||||
diffStats.innerHTML = "NO_CHANGES";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setActiveView(view) {
|
|
||||||
currentView = view;
|
|
||||||
viewOptions.forEach((btn) => {
|
|
||||||
const val = btn.getAttribute("data-view");
|
|
||||||
if (val === view) {
|
|
||||||
btn.classList.add("active");
|
|
||||||
} else {
|
|
||||||
btn.classList.remove("active");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (
|
|
||||||
diffOutput.querySelector(".d2h-wrapper") ||
|
|
||||||
diffOutput.children.length > 0
|
|
||||||
) {
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadExample() {
|
|
||||||
leftTextarea.value = `function hello() {\n console.log("Hello World");\n return "Hi";\n}`;
|
|
||||||
rightTextarea.value = `function hello() {\n console.log("Hello, Diff Checker!");\n return "Hey there";\n}`;
|
|
||||||
languageSelect.value = "javascript";
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
|
|
||||||
function swapTexts() {
|
|
||||||
const temp = leftTextarea.value;
|
|
||||||
leftTextarea.value = rightTextarea.value;
|
|
||||||
rightTextarea.value = temp;
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearLeft() {
|
|
||||||
leftTextarea.value = "";
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
function clearRight() {
|
|
||||||
rightTextarea.value = "";
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
|
|
||||||
compareBtn.addEventListener("click", renderDiff);
|
|
||||||
swapBtn.addEventListener("click", swapTexts);
|
|
||||||
exampleBtn.addEventListener("click", loadExample);
|
|
||||||
clearLeftBtn.addEventListener("click", clearLeft);
|
|
||||||
clearRightBtn.addEventListener("click", clearRight);
|
|
||||||
|
|
||||||
languageSelect.addEventListener("change", () => {
|
|
||||||
if (
|
|
||||||
diffOutput.querySelector(".d2h-wrapper") ||
|
|
||||||
diffOutput.children.length > 0
|
|
||||||
) {
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
viewOptions.forEach((btn) => {
|
|
||||||
btn.addEventListener("click", (e) => {
|
|
||||||
const view = e.currentTarget.getAttribute("data-view");
|
|
||||||
setActiveView(view);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener("keydown", (e) => {
|
|
||||||
if ((e.ctrlKey || e.metaKey) && e.key === "Enter") {
|
|
||||||
e.preventDefault();
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener("DOMContentLoaded", () => {
|
|
||||||
renderDiff();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
|
||||||
document.readyState === "complete" ||
|
|
||||||
document.readyState === "interactive"
|
|
||||||
) {
|
|
||||||
setTimeout(renderDiff, 10);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
228
index.html
228
index.html
@@ -43,6 +43,15 @@
|
|||||||
.view-option.active {
|
.view-option.active {
|
||||||
@apply bg-white text-blue-700 shadow-sm font-semibold;
|
@apply bg-white text-blue-700 shadow-sm font-semibold;
|
||||||
}
|
}
|
||||||
|
.style-switcher {
|
||||||
|
@apply flex items-center gap-2 bg-slate-100 px-4 py-1.5 rounded-full;
|
||||||
|
}
|
||||||
|
.style-switcher-label {
|
||||||
|
@apply text-sm font-medium text-slate-700;
|
||||||
|
}
|
||||||
|
.style-switcher-select {
|
||||||
|
@apply bg-white border border-slate-300 rounded-full px-3 py-1 text-sm font-medium text-slate-800 outline-none cursor-pointer shadow-sm hover:border-blue-500 transition-colors;
|
||||||
|
}
|
||||||
/* JS 动态插入的空状态提示 */
|
/* JS 动态插入的空状态提示 */
|
||||||
.empty-message {
|
.empty-message {
|
||||||
@apply p-10 text-center text-slate-500 bg-slate-50/50;
|
@apply p-10 text-center text-slate-500 bg-slate-50/50;
|
||||||
@@ -197,6 +206,8 @@ function hello() {
|
|||||||
📋 行内
|
📋 行内
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div data-style-switcher></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
<!-- 操作按钮 -->
|
||||||
@@ -254,197 +265,38 @@ function hello() {
|
|||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/diff2html@3.4.47/bundles/js/diff2html.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/diff2html@3.4.47/bundles/js/diff2html.min.js"></script>
|
||||||
|
|
||||||
<!-- 核心逻辑 (保持不变) -->
|
<script src="./shared/diff-page.js"></script>
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
window.initDiffPage({
|
||||||
"use strict";
|
themeId: "light",
|
||||||
|
currentThemePath: "index.html",
|
||||||
const leftTextarea = document.getElementById("leftTextarea");
|
switcherLabel: "🎨 风格",
|
||||||
const rightTextarea = document.getElementById("rightTextarea");
|
switcherAriaLabel: "切换 Diff Checker 风格",
|
||||||
const compareBtn = document.getElementById("compareBtn");
|
fileLabels: {
|
||||||
const swapBtn = document.getElementById("swapBtn");
|
left: "原始",
|
||||||
const exampleBtn = document.getElementById("exampleBtn");
|
right: "修改",
|
||||||
const clearLeftBtn = document.getElementById("clearLeftBtn");
|
},
|
||||||
const clearRightBtn = document.getElementById("clearRightBtn");
|
example: {
|
||||||
const languageSelect = document.getElementById("languageSelect");
|
left: `function hello() {\n console.log("Hello World");\n return "Hi";\n}`,
|
||||||
const diffOutput = document.getElementById("diff-output");
|
right: `function hello() {\n console.log("Hello, Diff Checker!");\n return "Hey there";\n}`,
|
||||||
const diffStats = document.getElementById("diffStats");
|
language: "javascript",
|
||||||
const viewOptions = document.querySelectorAll(".view-option");
|
},
|
||||||
|
messages: {
|
||||||
let currentView = "side-by-side";
|
generateError: (error) =>
|
||||||
|
`<div class="empty-message" style="color:#ef4444;">生成差异时出错: ${error.message}</div>`,
|
||||||
function getSelectedLanguage() {
|
renderError: () =>
|
||||||
return languageSelect.value;
|
'<div class="empty-message">渲染差异失败,请检查输入</div>',
|
||||||
|
blankResult:
|
||||||
|
'<div class="empty-message">两个文本完全相同,没有差异</div>',
|
||||||
|
blankStats: "<span class='text-slate-500'>⚖️ 无差异</span>",
|
||||||
|
},
|
||||||
|
formatStats: ({ added, deleted, identical }) => {
|
||||||
|
if (identical) {
|
||||||
|
return "<span class='text-emerald-600 font-medium'>✅ 内容相同</span>";
|
||||||
}
|
}
|
||||||
|
return `<span class='text-emerald-600 font-medium'>+${added} 行添加</span> | <span class='text-rose-500 font-medium'>-${deleted} 行删除</span>`;
|
||||||
function getHighlightConfig() {
|
},
|
||||||
const lang = getSelectedLanguage();
|
|
||||||
if (lang === "plaintext") return false;
|
|
||||||
return { enabled: true, language: lang };
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateUnifiedDiff(original, modified) {
|
|
||||||
return Diff.createTwoFilesPatch(
|
|
||||||
"原始",
|
|
||||||
"修改",
|
|
||||||
original || "",
|
|
||||||
modified || "",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
{ context: 4 },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderDiff() {
|
|
||||||
const leftText = leftTextarea.value;
|
|
||||||
const rightText = rightTextarea.value;
|
|
||||||
|
|
||||||
let diffString;
|
|
||||||
try {
|
|
||||||
diffString = generateUnifiedDiff(leftText, rightText);
|
|
||||||
} catch (e) {
|
|
||||||
diffOutput.innerHTML = `<div class="empty-message" style="color:#ef4444;">生成差异时出错: ${e.message}</div>`;
|
|
||||||
diffStats.textContent = "";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const configuration = {
|
|
||||||
drawFileList: false,
|
|
||||||
matching: "lines",
|
|
||||||
outputFormat: currentView,
|
|
||||||
highlight: getHighlightConfig(),
|
|
||||||
renderNothingWhenEmpty: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let diffHtml = "";
|
|
||||||
try {
|
|
||||||
diffHtml = Diff2Html.html(diffString, configuration);
|
|
||||||
} catch (e) {
|
|
||||||
console.warn(e);
|
|
||||||
diffOutput.innerHTML = `<div class="empty-message">渲染差异失败,请检查输入</div>`;
|
|
||||||
diffStats.textContent = "";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
diffOutput.innerHTML = diffHtml;
|
|
||||||
|
|
||||||
const selectedLang = getSelectedLanguage();
|
|
||||||
if (selectedLang !== "plaintext") {
|
|
||||||
const codeBlocks = diffOutput.querySelectorAll("code");
|
|
||||||
if (codeBlocks.length > 0 && window.hljs) {
|
|
||||||
codeBlocks.forEach((block) => {
|
|
||||||
block.classList.forEach((cls) => {
|
|
||||||
if (cls.startsWith("language-")) block.classList.remove(cls);
|
|
||||||
});
|
});
|
||||||
block.classList.add(`language-${selectedLang}`);
|
|
||||||
if (block.dataset.highlighted) {
|
|
||||||
delete block.dataset.highlighted;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
hljs.highlightElement(block);
|
|
||||||
} catch (e) {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const addedLines = diffOutput.querySelectorAll(".d2h-ins").length;
|
|
||||||
const deletedLines = diffOutput.querySelectorAll(".d2h-del").length;
|
|
||||||
if (addedLines === 0 && deletedLines === 0) {
|
|
||||||
diffStats.innerHTML =
|
|
||||||
"<span class='text-emerald-600 font-medium'>✅ 内容相同</span>";
|
|
||||||
} else {
|
|
||||||
diffStats.innerHTML = `<span class='text-emerald-600 font-medium'>+${addedLines} 行添加</span> | <span class='text-rose-500 font-medium'>-${deletedLines} 行删除</span>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!diffHtml.trim() || diffOutput.innerText.trim() === "") {
|
|
||||||
diffOutput.innerHTML =
|
|
||||||
'<div class="empty-message">两个文本完全相同,没有差异</div>';
|
|
||||||
diffStats.innerHTML =
|
|
||||||
"<span class='text-slate-500'>⚖️ 无差异</span>";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setActiveView(view) {
|
|
||||||
currentView = view;
|
|
||||||
viewOptions.forEach((btn) => {
|
|
||||||
const val = btn.getAttribute("data-view");
|
|
||||||
if (val === view) {
|
|
||||||
btn.classList.add("active");
|
|
||||||
} else {
|
|
||||||
btn.classList.remove("active");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (
|
|
||||||
diffOutput.querySelector(".d2h-wrapper") ||
|
|
||||||
diffOutput.children.length > 0
|
|
||||||
) {
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadExample() {
|
|
||||||
leftTextarea.value = `function hello() {\n console.log("Hello World");\n return "Hi";\n}`;
|
|
||||||
rightTextarea.value = `function hello() {\n console.log("Hello, Diff Checker!");\n return "Hey there";\n}`;
|
|
||||||
languageSelect.value = "javascript";
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
|
|
||||||
function swapTexts() {
|
|
||||||
const temp = leftTextarea.value;
|
|
||||||
leftTextarea.value = rightTextarea.value;
|
|
||||||
rightTextarea.value = temp;
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearLeft() {
|
|
||||||
leftTextarea.value = "";
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
function clearRight() {
|
|
||||||
rightTextarea.value = "";
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
|
|
||||||
compareBtn.addEventListener("click", renderDiff);
|
|
||||||
swapBtn.addEventListener("click", swapTexts);
|
|
||||||
exampleBtn.addEventListener("click", loadExample);
|
|
||||||
clearLeftBtn.addEventListener("click", clearLeft);
|
|
||||||
clearRightBtn.addEventListener("click", clearRight);
|
|
||||||
|
|
||||||
languageSelect.addEventListener("change", () => {
|
|
||||||
if (
|
|
||||||
diffOutput.querySelector(".d2h-wrapper") ||
|
|
||||||
diffOutput.children.length > 0
|
|
||||||
) {
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
viewOptions.forEach((btn) => {
|
|
||||||
btn.addEventListener("click", (e) => {
|
|
||||||
const view = e.currentTarget.getAttribute("data-view");
|
|
||||||
setActiveView(view);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener("keydown", (e) => {
|
|
||||||
if ((e.ctrlKey || e.metaKey) && e.key === "Enter") {
|
|
||||||
e.preventDefault();
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener("DOMContentLoaded", () => {
|
|
||||||
renderDiff();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
|
||||||
document.readyState === "complete" ||
|
|
||||||
document.readyState === "interactive"
|
|
||||||
) {
|
|
||||||
setTimeout(renderDiff, 10);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
222
macos/index.html
222
macos/index.html
@@ -81,6 +81,21 @@
|
|||||||
.view-option.active {
|
.view-option.active {
|
||||||
@apply bg-white/80 text-blue-600 shadow-sm border border-white/80;
|
@apply bg-white/80 text-blue-600 shadow-sm border border-white/80;
|
||||||
}
|
}
|
||||||
|
.style-switcher {
|
||||||
|
@apply flex items-center gap-2 bg-white/40 p-1.5 rounded-xl border border-white/50;
|
||||||
|
}
|
||||||
|
.style-switcher-label {
|
||||||
|
@apply text-sm font-medium text-slate-500 pl-2;
|
||||||
|
}
|
||||||
|
.style-switcher-select {
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
color: #334155;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
outline: none;
|
||||||
|
padding: 0.25rem 0.75rem 0.25rem 0.25rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================
|
/* ==========================================
|
||||||
@@ -338,6 +353,8 @@ function hello() {
|
|||||||
Unified
|
Unified
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div data-style-switcher></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
<!-- 操作按钮 -->
|
||||||
@@ -416,183 +433,40 @@ function hello() {
|
|||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/diff2html@3.4.47/bundles/js/diff2html.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/diff2html@3.4.47/bundles/js/diff2html.min.js"></script>
|
||||||
|
|
||||||
<!-- 核心逻辑 -->
|
<script src="../shared/diff-page.js"></script>
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
window.initDiffPage({
|
||||||
"use strict";
|
themeId: "macos",
|
||||||
|
currentThemePath: "macos/index.html",
|
||||||
const leftTextarea = document.getElementById("leftTextarea");
|
switcherLabel: "Theme",
|
||||||
const rightTextarea = document.getElementById("rightTextarea");
|
switcherAriaLabel: "Switch macOS glass theme",
|
||||||
const compareBtn = document.getElementById("compareBtn");
|
fileLabels: {
|
||||||
const swapBtn = document.getElementById("swapBtn");
|
left: "Original",
|
||||||
const exampleBtn = document.getElementById("exampleBtn");
|
right: "Modified",
|
||||||
const clearLeftBtn = document.getElementById("clearLeftBtn");
|
},
|
||||||
const clearRightBtn = document.getElementById("clearRightBtn");
|
example: {
|
||||||
const languageSelect = document.getElementById("languageSelect");
|
left: `function hello() {\n console.log("Hello World");\n return "Hi";\n}`,
|
||||||
const diffOutput = document.getElementById("diff-output");
|
right: `function hello() {\n console.log("Hello, Diff Checker!");\n return "Hey there";\n}`,
|
||||||
const diffStats = document.getElementById("diffStats");
|
language: "javascript",
|
||||||
const viewOptions = document.querySelectorAll(".view-option");
|
},
|
||||||
|
messages: {
|
||||||
let currentView = "side-by-side";
|
generateError: (error) =>
|
||||||
|
`<div class="p-12 text-center text-red-500 text-sm">Error: ${error.message}</div>`,
|
||||||
function getSelectedLanguage() {
|
renderError: () =>
|
||||||
return languageSelect.value;
|
'<div class="p-12 text-center text-red-500 text-sm">Render Failed</div>',
|
||||||
|
blankResult:
|
||||||
|
'<div class="p-12 text-center text-slate-400 text-sm">Files are identical.</div>',
|
||||||
|
blankStats: "Identical",
|
||||||
|
generateErrorStats: "Error",
|
||||||
|
renderErrorStats: "Error",
|
||||||
|
},
|
||||||
|
formatStats: ({ added, deleted, identical }) => {
|
||||||
|
if (identical) {
|
||||||
|
return "No Changes";
|
||||||
}
|
}
|
||||||
|
return `<span class='text-green-600'>+${added}</span> | <span class='text-red-500'>-${deleted}</span>`;
|
||||||
function getHighlightConfig() {
|
},
|
||||||
const lang = getSelectedLanguage();
|
|
||||||
if (lang === "plaintext") return false;
|
|
||||||
return { enabled: true, language: lang };
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateUnifiedDiff(original, modified) {
|
|
||||||
return Diff.createTwoFilesPatch(
|
|
||||||
"Original",
|
|
||||||
"Modified",
|
|
||||||
original || "",
|
|
||||||
modified || "",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
{ context: 4 },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderDiff() {
|
|
||||||
const leftText = leftTextarea.value;
|
|
||||||
const rightText = rightTextarea.value;
|
|
||||||
|
|
||||||
let diffString;
|
|
||||||
try {
|
|
||||||
diffString = generateUnifiedDiff(leftText, rightText);
|
|
||||||
} catch (e) {
|
|
||||||
diffOutput.innerHTML = `<div class="p-12 text-center text-red-500 text-sm">Error: ${e.message}</div>`;
|
|
||||||
diffStats.textContent = "Error";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const configuration = {
|
|
||||||
drawFileList: false,
|
|
||||||
matching: "lines",
|
|
||||||
outputFormat: currentView,
|
|
||||||
highlight: getHighlightConfig(),
|
|
||||||
renderNothingWhenEmpty: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let diffHtml = "";
|
|
||||||
try {
|
|
||||||
diffHtml = Diff2Html.html(diffString, configuration);
|
|
||||||
} catch (e) {
|
|
||||||
diffOutput.innerHTML = `<div class="p-12 text-center text-red-500 text-sm">Render Failed</div>`;
|
|
||||||
diffStats.textContent = "Error";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
diffOutput.innerHTML = diffHtml;
|
|
||||||
|
|
||||||
const selectedLang = getSelectedLanguage();
|
|
||||||
if (selectedLang !== "plaintext") {
|
|
||||||
const codeBlocks = diffOutput.querySelectorAll("code");
|
|
||||||
if (codeBlocks.length > 0 && window.hljs) {
|
|
||||||
codeBlocks.forEach((block) => {
|
|
||||||
block.classList.forEach((cls) => {
|
|
||||||
if (cls.startsWith("language-")) block.classList.remove(cls);
|
|
||||||
});
|
});
|
||||||
block.classList.add(`language-${selectedLang}`);
|
|
||||||
if (block.dataset.highlighted) {
|
|
||||||
delete block.dataset.highlighted;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
hljs.highlightElement(block);
|
|
||||||
} catch (e) {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const addedLines = diffOutput.querySelectorAll(".d2h-ins").length;
|
|
||||||
const deletedLines = diffOutput.querySelectorAll(".d2h-del").length;
|
|
||||||
if (addedLines === 0 && deletedLines === 0) {
|
|
||||||
diffStats.innerHTML = "No Changes";
|
|
||||||
} else {
|
|
||||||
diffStats.innerHTML = `<span class='text-green-600'>+${addedLines}</span> | <span class='text-red-500'>-${deletedLines}</span>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!diffHtml.trim() || diffOutput.innerText.trim() === "") {
|
|
||||||
diffOutput.innerHTML =
|
|
||||||
'<div class="p-12 text-center text-slate-400 text-sm">Files are identical.</div>';
|
|
||||||
diffStats.innerHTML = "Identical";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setActiveView(view) {
|
|
||||||
currentView = view;
|
|
||||||
viewOptions.forEach((btn) => {
|
|
||||||
const val = btn.getAttribute("data-view");
|
|
||||||
if (val === view) {
|
|
||||||
btn.classList.add("active");
|
|
||||||
} else {
|
|
||||||
btn.classList.remove("active");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (
|
|
||||||
diffOutput.querySelector(".d2h-wrapper") ||
|
|
||||||
diffOutput.children.length > 0
|
|
||||||
) {
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadExample() {
|
|
||||||
leftTextarea.value = `function hello() {\n console.log("Hello World");\n return "Hi";\n}`;
|
|
||||||
rightTextarea.value = `function hello() {\n console.log("Hello, Diff Checker!");\n return "Hey there";\n}`;
|
|
||||||
languageSelect.value = "javascript";
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
|
|
||||||
function swapTexts() {
|
|
||||||
const temp = leftTextarea.value;
|
|
||||||
leftTextarea.value = rightTextarea.value;
|
|
||||||
rightTextarea.value = temp;
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearLeft() {
|
|
||||||
leftTextarea.value = "";
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
function clearRight() {
|
|
||||||
rightTextarea.value = "";
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
|
|
||||||
compareBtn.addEventListener("click", renderDiff);
|
|
||||||
swapBtn.addEventListener("click", swapTexts);
|
|
||||||
exampleBtn.addEventListener("click", loadExample);
|
|
||||||
clearLeftBtn.addEventListener("click", clearLeft);
|
|
||||||
clearRightBtn.addEventListener("click", clearRight);
|
|
||||||
|
|
||||||
languageSelect.addEventListener("change", () => {
|
|
||||||
if (
|
|
||||||
diffOutput.querySelector(".d2h-wrapper") ||
|
|
||||||
diffOutput.children.length > 0
|
|
||||||
)
|
|
||||||
renderDiff();
|
|
||||||
});
|
|
||||||
|
|
||||||
viewOptions.forEach((btn) => {
|
|
||||||
btn.addEventListener("click", (e) => {
|
|
||||||
setActiveView(e.currentTarget.getAttribute("data-view"));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener("keydown", (e) => {
|
|
||||||
if ((e.ctrlKey || e.metaKey) && e.key === "Enter") {
|
|
||||||
e.preventDefault();
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener("DOMContentLoaded", renderDiff);
|
|
||||||
})();
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -75,6 +75,26 @@
|
|||||||
.view-option.active {
|
.view-option.active {
|
||||||
@apply text-black bg-matrix-green shadow-[0_0_10px_#00FF41];
|
@apply text-black bg-matrix-green shadow-[0_0_10px_#00FF41];
|
||||||
}
|
}
|
||||||
|
.style-switcher {
|
||||||
|
@apply flex items-center gap-2;
|
||||||
|
}
|
||||||
|
.style-switcher-label {
|
||||||
|
@apply text-sm tracking-widest text-matrix-darkGreen;
|
||||||
|
}
|
||||||
|
.style-switcher-select {
|
||||||
|
background: #000;
|
||||||
|
border: 1px solid #008f11;
|
||||||
|
color: #00ff41;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
font-family: "Share Tech Mono", monospace;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.style-switcher-select:hover,
|
||||||
|
.style-switcher-select:focus {
|
||||||
|
border-color: #00ff41;
|
||||||
|
box-shadow: 0 0 10px #00ff41;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================
|
/* ==========================================
|
||||||
@@ -325,6 +345,8 @@ function enterMatrix() {
|
|||||||
UNIFIED
|
UNIFIED
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div data-style-switcher></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
<!-- 操作按钮 -->
|
||||||
@@ -418,185 +440,40 @@ function enterMatrix() {
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- 核心对比逻辑 -->
|
<script src="../shared/diff-page.js"></script>
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
window.initDiffPage({
|
||||||
"use strict";
|
themeId: "matrix",
|
||||||
|
currentThemePath: "matrix/index.html",
|
||||||
const leftTextarea = document.getElementById("leftTextarea");
|
switcherLabel: "MODE:",
|
||||||
const rightTextarea = document.getElementById("rightTextarea");
|
switcherAriaLabel: "Switch Matrix skin",
|
||||||
const compareBtn = document.getElementById("compareBtn");
|
fileLabels: {
|
||||||
const swapBtn = document.getElementById("swapBtn");
|
left: "SOURCE",
|
||||||
const exampleBtn = document.getElementById("exampleBtn");
|
right: "TARGET",
|
||||||
const clearLeftBtn = document.getElementById("clearLeftBtn");
|
},
|
||||||
const clearRightBtn = document.getElementById("clearRightBtn");
|
example: {
|
||||||
const languageSelect = document.getElementById("languageSelect");
|
left: `function enterMatrix() {\n console.log("Follow the white rabbit.");\n return "Ignorance is bliss.";\n}`,
|
||||||
const diffOutput = document.getElementById("diff-output");
|
right: `function enterMatrix() {\n console.log("There is no spoon.");\n decodeConstruct();\n return "I know Kung Fu.";\n}`,
|
||||||
const diffStats = document.getElementById("diffStats");
|
language: "javascript",
|
||||||
const viewOptions = document.querySelectorAll(".view-option");
|
},
|
||||||
|
messages: {
|
||||||
let currentView = "side-by-side";
|
generateError: (error) =>
|
||||||
|
`<div class="p-12 text-center text-matrix-red tracking-widest">FATAL_ERROR: ${error.message}</div>`,
|
||||||
function getSelectedLanguage() {
|
renderError: () =>
|
||||||
return languageSelect.value;
|
'<div class="p-12 text-center text-matrix-red tracking-widest">RENDER_FAILURE</div>',
|
||||||
|
blankResult:
|
||||||
|
'<div class="p-12 text-center text-matrix-darkGreen tracking-widest">DATA_STREAMS_ARE_IDENTICAL.</div>',
|
||||||
|
blankStats: "<span class='text-matrix-darkGreen'>SYNCED</span>",
|
||||||
|
generateErrorStats: "ERR",
|
||||||
|
renderErrorStats: "ERR",
|
||||||
|
},
|
||||||
|
formatStats: ({ added, deleted, identical }) => {
|
||||||
|
if (identical) {
|
||||||
|
return "<span class='text-matrix-darkGreen'>MATCH_FOUND // NO_ANOMALIES</span>";
|
||||||
}
|
}
|
||||||
|
return `<span class='text-matrix-green'>+${added} INJECTED</span> <span class='text-matrix-darkGreen mx-2'>|</span> <span class='text-matrix-red'>-${deleted} PURGED</span>`;
|
||||||
function getHighlightConfig() {
|
},
|
||||||
const lang = getSelectedLanguage();
|
|
||||||
if (lang === "plaintext") return false;
|
|
||||||
return { enabled: true, language: lang };
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateUnifiedDiff(original, modified) {
|
|
||||||
return Diff.createTwoFilesPatch(
|
|
||||||
"SOURCE",
|
|
||||||
"TARGET",
|
|
||||||
original || "",
|
|
||||||
modified || "",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
{ context: 4 },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderDiff() {
|
|
||||||
const leftText = leftTextarea.value;
|
|
||||||
const rightText = rightTextarea.value;
|
|
||||||
|
|
||||||
let diffString;
|
|
||||||
try {
|
|
||||||
diffString = generateUnifiedDiff(leftText, rightText);
|
|
||||||
} catch (e) {
|
|
||||||
diffOutput.innerHTML = `<div class="p-12 text-center text-matrix-red tracking-widest">FATAL_ERROR: ${e.message}</div>`;
|
|
||||||
diffStats.textContent = "ERR";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const configuration = {
|
|
||||||
drawFileList: false,
|
|
||||||
matching: "lines",
|
|
||||||
outputFormat: currentView,
|
|
||||||
highlight: getHighlightConfig(),
|
|
||||||
renderNothingWhenEmpty: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let diffHtml = "";
|
|
||||||
try {
|
|
||||||
diffHtml = Diff2Html.html(diffString, configuration);
|
|
||||||
} catch (e) {
|
|
||||||
diffOutput.innerHTML = `<div class="p-12 text-center text-matrix-red tracking-widest">RENDER_FAILURE</div>`;
|
|
||||||
diffStats.textContent = "ERR";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
diffOutput.innerHTML = diffHtml;
|
|
||||||
|
|
||||||
const selectedLang = getSelectedLanguage();
|
|
||||||
if (selectedLang !== "plaintext") {
|
|
||||||
const codeBlocks = diffOutput.querySelectorAll("code");
|
|
||||||
if (codeBlocks.length > 0 && window.hljs) {
|
|
||||||
codeBlocks.forEach((block) => {
|
|
||||||
block.classList.forEach((cls) => {
|
|
||||||
if (cls.startsWith("language-")) block.classList.remove(cls);
|
|
||||||
});
|
});
|
||||||
block.classList.add(`language-${selectedLang}`);
|
|
||||||
if (block.dataset.highlighted) {
|
|
||||||
delete block.dataset.highlighted;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
hljs.highlightElement(block);
|
|
||||||
} catch (e) {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const addedLines = diffOutput.querySelectorAll(".d2h-ins").length;
|
|
||||||
const deletedLines = diffOutput.querySelectorAll(".d2h-del").length;
|
|
||||||
if (addedLines === 0 && deletedLines === 0) {
|
|
||||||
diffStats.innerHTML =
|
|
||||||
"<span class='text-matrix-darkGreen'>MATCH_FOUND // NO_ANOMALIES</span>";
|
|
||||||
} else {
|
|
||||||
diffStats.innerHTML = `<span class='text-matrix-green'>+${addedLines} INJECTED</span> <span class='text-matrix-darkGreen mx-2'>|</span> <span class='text-matrix-red'>-${deletedLines} PURGED</span>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!diffHtml.trim() || diffOutput.innerText.trim() === "") {
|
|
||||||
diffOutput.innerHTML =
|
|
||||||
'<div class="p-12 text-center text-matrix-darkGreen tracking-widest">DATA_STREAMS_ARE_IDENTICAL.</div>';
|
|
||||||
diffStats.innerHTML =
|
|
||||||
"<span class='text-matrix-darkGreen'>SYNCED</span>";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setActiveView(view) {
|
|
||||||
currentView = view;
|
|
||||||
viewOptions.forEach((btn) => {
|
|
||||||
const val = btn.getAttribute("data-view");
|
|
||||||
if (val === view) {
|
|
||||||
btn.classList.add("active");
|
|
||||||
} else {
|
|
||||||
btn.classList.remove("active");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (
|
|
||||||
diffOutput.querySelector(".d2h-wrapper") ||
|
|
||||||
diffOutput.children.length > 0
|
|
||||||
) {
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadExample() {
|
|
||||||
leftTextarea.value = `function enterMatrix() {\n console.log("Follow the white rabbit.");\n return "Ignorance is bliss.";\n}`;
|
|
||||||
rightTextarea.value = `function enterMatrix() {\n console.log("There is no spoon.");\n decodeConstruct();\n return "I know Kung Fu.";\n}`;
|
|
||||||
languageSelect.value = "javascript";
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
|
|
||||||
function swapTexts() {
|
|
||||||
const temp = leftTextarea.value;
|
|
||||||
leftTextarea.value = rightTextarea.value;
|
|
||||||
rightTextarea.value = temp;
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearLeft() {
|
|
||||||
leftTextarea.value = "";
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
function clearRight() {
|
|
||||||
rightTextarea.value = "";
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
|
|
||||||
compareBtn.addEventListener("click", renderDiff);
|
|
||||||
swapBtn.addEventListener("click", swapTexts);
|
|
||||||
exampleBtn.addEventListener("click", loadExample);
|
|
||||||
clearLeftBtn.addEventListener("click", clearLeft);
|
|
||||||
clearRightBtn.addEventListener("click", clearRight);
|
|
||||||
|
|
||||||
languageSelect.addEventListener("change", () => {
|
|
||||||
if (
|
|
||||||
diffOutput.querySelector(".d2h-wrapper") ||
|
|
||||||
diffOutput.children.length > 0
|
|
||||||
)
|
|
||||||
renderDiff();
|
|
||||||
});
|
|
||||||
|
|
||||||
viewOptions.forEach((btn) => {
|
|
||||||
btn.addEventListener("click", (e) => {
|
|
||||||
setActiveView(e.currentTarget.getAttribute("data-view"));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener("keydown", (e) => {
|
|
||||||
if ((e.ctrlKey || e.metaKey) && e.key === "Enter") {
|
|
||||||
e.preventDefault();
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener("DOMContentLoaded", renderDiff);
|
|
||||||
})();
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -92,6 +92,15 @@
|
|||||||
.view-option.active {
|
.view-option.active {
|
||||||
@apply text-notion-text font-medium bg-notion-hover;
|
@apply text-notion-text font-medium bg-notion-hover;
|
||||||
}
|
}
|
||||||
|
.style-switcher {
|
||||||
|
@apply flex items-center gap-2;
|
||||||
|
}
|
||||||
|
.style-switcher-label {
|
||||||
|
@apply text-sm text-notion-gray;
|
||||||
|
}
|
||||||
|
.style-switcher-select {
|
||||||
|
@apply bg-transparent hover:bg-notion-hover px-2 py-1 rounded-[4px] text-sm text-notion-text outline-none cursor-pointer transition-colors appearance-none;
|
||||||
|
}
|
||||||
|
|
||||||
/* Notion 代码块输入框 */
|
/* Notion 代码块输入框 */
|
||||||
.notion-input-wrapper {
|
.notion-input-wrapper {
|
||||||
@@ -279,6 +288,10 @@ function hello() {
|
|||||||
Unified view
|
Unified view
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="w-[1px] h-4 bg-notion-border hidden md:block"></div>
|
||||||
|
|
||||||
|
<div data-style-switcher></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
<!-- 操作按钮 -->
|
||||||
@@ -330,184 +343,38 @@ function hello() {
|
|||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/diff2html@3.4.47/bundles/js/diff2html.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/diff2html@3.4.47/bundles/js/diff2html.min.js"></script>
|
||||||
|
|
||||||
<!-- 核心逻辑 -->
|
<script src="../shared/diff-page.js"></script>
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
window.initDiffPage({
|
||||||
"use strict";
|
themeId: "notion",
|
||||||
|
currentThemePath: "notion/index.html",
|
||||||
const leftTextarea = document.getElementById("leftTextarea");
|
switcherLabel: "Style",
|
||||||
const rightTextarea = document.getElementById("rightTextarea");
|
switcherAriaLabel: "Switch Notion style",
|
||||||
const compareBtn = document.getElementById("compareBtn");
|
fileLabels: {
|
||||||
const swapBtn = document.getElementById("swapBtn");
|
left: "Original",
|
||||||
const exampleBtn = document.getElementById("exampleBtn");
|
right: "Modified",
|
||||||
const clearLeftBtn = document.getElementById("clearLeftBtn");
|
},
|
||||||
const clearRightBtn = document.getElementById("clearRightBtn");
|
example: {
|
||||||
const languageSelect = document.getElementById("languageSelect");
|
left: `function hello() {\n console.log("Hello World");\n return "Hi";\n}`,
|
||||||
const diffOutput = document.getElementById("diff-output");
|
right: `function hello() {\n console.log("Hello, Diff Checker!");\n return "Hey there";\n}`,
|
||||||
const diffStats = document.getElementById("diffStats");
|
language: "javascript",
|
||||||
const viewOptions = document.querySelectorAll(".view-option");
|
},
|
||||||
|
messages: {
|
||||||
let currentView = "side-by-side";
|
generateError: (error) =>
|
||||||
|
`<div class="p-8 text-center text-[#eb5757] text-sm">Error: ${error.message}</div>`,
|
||||||
function getSelectedLanguage() {
|
renderError: () =>
|
||||||
return languageSelect.value;
|
'<div class="p-8 text-center text-[#eb5757] text-sm">Render Failed</div>',
|
||||||
|
blankResult:
|
||||||
|
'<div class="p-8 text-center text-notion-lightGray text-sm">Files are identical.</div>',
|
||||||
|
blankStats: "Identical",
|
||||||
|
},
|
||||||
|
formatStats: ({ added, deleted, identical }) => {
|
||||||
|
if (identical) {
|
||||||
|
return "No changes";
|
||||||
}
|
}
|
||||||
|
return `<span style="color:#448361">${added} additions</span>, <span style="color:#e03e3e">${deleted} deletions</span>`;
|
||||||
function getHighlightConfig() {
|
},
|
||||||
const lang = getSelectedLanguage();
|
|
||||||
if (lang === "plaintext") return false;
|
|
||||||
return { enabled: true, language: lang };
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateUnifiedDiff(original, modified) {
|
|
||||||
return Diff.createTwoFilesPatch(
|
|
||||||
"Original",
|
|
||||||
"Modified",
|
|
||||||
original || "",
|
|
||||||
modified || "",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
{ context: 4 },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderDiff() {
|
|
||||||
const leftText = leftTextarea.value;
|
|
||||||
const rightText = rightTextarea.value;
|
|
||||||
|
|
||||||
let diffString;
|
|
||||||
try {
|
|
||||||
diffString = generateUnifiedDiff(leftText, rightText);
|
|
||||||
} catch (e) {
|
|
||||||
diffOutput.innerHTML = `<div class="p-8 text-center text-[#eb5757] text-sm">Error: ${e.message}</div>`;
|
|
||||||
diffStats.textContent = "";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const configuration = {
|
|
||||||
drawFileList: false,
|
|
||||||
matching: "lines",
|
|
||||||
outputFormat: currentView,
|
|
||||||
highlight: getHighlightConfig(),
|
|
||||||
renderNothingWhenEmpty: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let diffHtml = "";
|
|
||||||
try {
|
|
||||||
diffHtml = Diff2Html.html(diffString, configuration);
|
|
||||||
} catch (e) {
|
|
||||||
diffOutput.innerHTML = `<div class="p-8 text-center text-[#eb5757] text-sm">Render Failed</div>`;
|
|
||||||
diffStats.textContent = "";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
diffOutput.innerHTML = diffHtml;
|
|
||||||
|
|
||||||
const selectedLang = getSelectedLanguage();
|
|
||||||
if (selectedLang !== "plaintext") {
|
|
||||||
const codeBlocks = diffOutput.querySelectorAll("code");
|
|
||||||
if (codeBlocks.length > 0 && window.hljs) {
|
|
||||||
codeBlocks.forEach((block) => {
|
|
||||||
block.classList.forEach((cls) => {
|
|
||||||
if (cls.startsWith("language-")) block.classList.remove(cls);
|
|
||||||
});
|
});
|
||||||
block.classList.add(`language-${selectedLang}`);
|
|
||||||
if (block.dataset.highlighted) {
|
|
||||||
delete block.dataset.highlighted;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
hljs.highlightElement(block);
|
|
||||||
} catch (e) {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const addedLines = diffOutput.querySelectorAll(".d2h-ins").length;
|
|
||||||
const deletedLines = diffOutput.querySelectorAll(".d2h-del").length;
|
|
||||||
if (addedLines === 0 && deletedLines === 0) {
|
|
||||||
diffStats.innerHTML = "No changes";
|
|
||||||
} else {
|
|
||||||
// Notion 风格的极简统计文本
|
|
||||||
diffStats.innerHTML = `<span style="color:#448361">${addedLines} additions</span>, <span style="color:#e03e3e">${deletedLines} deletions</span>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!diffHtml.trim() || diffOutput.innerText.trim() === "") {
|
|
||||||
diffOutput.innerHTML =
|
|
||||||
'<div class="p-8 text-center text-notion-lightGray text-sm">Files are identical.</div>';
|
|
||||||
diffStats.innerHTML = "Identical";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setActiveView(view) {
|
|
||||||
currentView = view;
|
|
||||||
viewOptions.forEach((btn) => {
|
|
||||||
const val = btn.getAttribute("data-view");
|
|
||||||
if (val === view) {
|
|
||||||
btn.classList.add("active");
|
|
||||||
} else {
|
|
||||||
btn.classList.remove("active");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (
|
|
||||||
diffOutput.querySelector(".d2h-wrapper") ||
|
|
||||||
diffOutput.children.length > 0
|
|
||||||
) {
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadExample() {
|
|
||||||
leftTextarea.value = `function hello() {\n console.log("Hello World");\n return "Hi";\n}`;
|
|
||||||
rightTextarea.value = `function hello() {\n console.log("Hello, Diff Checker!");\n return "Hey there";\n}`;
|
|
||||||
languageSelect.value = "javascript";
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
|
|
||||||
function swapTexts() {
|
|
||||||
const temp = leftTextarea.value;
|
|
||||||
leftTextarea.value = rightTextarea.value;
|
|
||||||
rightTextarea.value = temp;
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearLeft() {
|
|
||||||
leftTextarea.value = "";
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
function clearRight() {
|
|
||||||
rightTextarea.value = "";
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
|
|
||||||
compareBtn.addEventListener("click", renderDiff);
|
|
||||||
swapBtn.addEventListener("click", swapTexts);
|
|
||||||
exampleBtn.addEventListener("click", loadExample);
|
|
||||||
clearLeftBtn.addEventListener("click", clearLeft);
|
|
||||||
clearRightBtn.addEventListener("click", clearRight);
|
|
||||||
|
|
||||||
languageSelect.addEventListener("change", () => {
|
|
||||||
if (
|
|
||||||
diffOutput.querySelector(".d2h-wrapper") ||
|
|
||||||
diffOutput.children.length > 0
|
|
||||||
)
|
|
||||||
renderDiff();
|
|
||||||
});
|
|
||||||
|
|
||||||
viewOptions.forEach((btn) => {
|
|
||||||
btn.addEventListener("click", (e) => {
|
|
||||||
setActiveView(e.currentTarget.getAttribute("data-view"));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener("keydown", (e) => {
|
|
||||||
if ((e.ctrlKey || e.metaKey) && e.key === "Enter") {
|
|
||||||
e.preventDefault();
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener("DOMContentLoaded", renderDiff);
|
|
||||||
})();
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
293
shared/diff-page.js
Normal file
293
shared/diff-page.js
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
(function (global) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const DEFAULT_THEMES = [
|
||||||
|
{ id: "light", label: "轻量", path: "index.html" },
|
||||||
|
{ id: "cyberpunk", label: "赛博", path: "cyberpunk/index.html" },
|
||||||
|
{ id: "macos", label: "macOS", path: "macos/index.html" },
|
||||||
|
{ id: "matrix", label: "Matrix", path: "matrix/index.html" },
|
||||||
|
{ id: "notion", label: "Notion", path: "notion/index.html" },
|
||||||
|
{ id: "synthwave", label: "蒸汽波", path: "synthwave/index.html" },
|
||||||
|
{ id: "exe", label: "EXE", path: "exe/index.html" },
|
||||||
|
];
|
||||||
|
|
||||||
|
function resolveThemeUrl(targetPath, currentPath) {
|
||||||
|
try {
|
||||||
|
const url = new URL(global.location.href);
|
||||||
|
if (url.pathname.endsWith(currentPath)) {
|
||||||
|
url.pathname =
|
||||||
|
url.pathname.slice(0, -currentPath.length) + targetPath;
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
return new URL(targetPath, global.location.href).toString();
|
||||||
|
} catch (error) {
|
||||||
|
return targetPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initStyleSwitcher(config) {
|
||||||
|
const host = document.querySelector(config.hostSelector || "[data-style-switcher]");
|
||||||
|
if (!host) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const themes = config.themes || DEFAULT_THEMES;
|
||||||
|
const wrapper = document.createElement("div");
|
||||||
|
wrapper.className = "style-switcher";
|
||||||
|
|
||||||
|
const label = document.createElement("label");
|
||||||
|
label.className = "style-switcher-label";
|
||||||
|
label.textContent = config.switcherLabel || "风格";
|
||||||
|
|
||||||
|
const select = document.createElement("select");
|
||||||
|
select.className = "style-switcher-select";
|
||||||
|
select.setAttribute(
|
||||||
|
"aria-label",
|
||||||
|
config.switcherAriaLabel || "切换界面风格",
|
||||||
|
);
|
||||||
|
|
||||||
|
themes.forEach((theme) => {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = resolveThemeUrl(theme.path, config.currentThemePath);
|
||||||
|
option.textContent = theme.label;
|
||||||
|
option.selected = theme.id === config.themeId;
|
||||||
|
select.appendChild(option);
|
||||||
|
});
|
||||||
|
|
||||||
|
select.addEventListener("change", () => {
|
||||||
|
global.location.href = select.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.appendChild(label);
|
||||||
|
wrapper.appendChild(select);
|
||||||
|
host.replaceChildren(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getElement(id) {
|
||||||
|
return document.getElementById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function initDiffPage(config) {
|
||||||
|
const leftTextarea = getElement(config.ids?.leftTextarea || "leftTextarea");
|
||||||
|
const rightTextarea = getElement(
|
||||||
|
config.ids?.rightTextarea || "rightTextarea",
|
||||||
|
);
|
||||||
|
const compareBtn = getElement(config.ids?.compareBtn || "compareBtn");
|
||||||
|
const swapBtn = getElement(config.ids?.swapBtn || "swapBtn");
|
||||||
|
const exampleBtn = getElement(config.ids?.exampleBtn || "exampleBtn");
|
||||||
|
const clearLeftBtn = getElement(config.ids?.clearLeftBtn || "clearLeftBtn");
|
||||||
|
const clearRightBtn = getElement(
|
||||||
|
config.ids?.clearRightBtn || "clearRightBtn",
|
||||||
|
);
|
||||||
|
const languageSelect = getElement(
|
||||||
|
config.ids?.languageSelect || "languageSelect",
|
||||||
|
);
|
||||||
|
const diffOutput = getElement(config.ids?.diffOutput || "diff-output");
|
||||||
|
const diffStats = getElement(config.ids?.diffStats || "diffStats");
|
||||||
|
const viewOptions = document.querySelectorAll(
|
||||||
|
config.selectors?.viewOptions || ".view-option",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!leftTextarea ||
|
||||||
|
!rightTextarea ||
|
||||||
|
!compareBtn ||
|
||||||
|
!swapBtn ||
|
||||||
|
!exampleBtn ||
|
||||||
|
!clearLeftBtn ||
|
||||||
|
!clearRightBtn ||
|
||||||
|
!languageSelect ||
|
||||||
|
!diffOutput ||
|
||||||
|
!diffStats ||
|
||||||
|
viewOptions.length === 0
|
||||||
|
) {
|
||||||
|
throw new Error("Diff page initialization failed: missing required DOM nodes.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const messages = config.messages || {};
|
||||||
|
const example = config.example || {};
|
||||||
|
const normalizeLanguage =
|
||||||
|
config.normalizeLanguage || ((language) => language);
|
||||||
|
const formatStats =
|
||||||
|
config.formatStats ||
|
||||||
|
((result) => {
|
||||||
|
if (result.identical) {
|
||||||
|
return "No changes";
|
||||||
|
}
|
||||||
|
return `+${result.added} / -${result.deleted}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
let currentView =
|
||||||
|
config.initialView ||
|
||||||
|
Array.from(viewOptions).find((button) => button.classList.contains("active"))
|
||||||
|
?.getAttribute("data-view") ||
|
||||||
|
"side-by-side";
|
||||||
|
|
||||||
|
function setOutput(html, statsHtml) {
|
||||||
|
diffOutput.innerHTML = html;
|
||||||
|
diffStats.innerHTML = statsHtml || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelectedLanguage() {
|
||||||
|
return normalizeLanguage(languageSelect.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHighlightConfig() {
|
||||||
|
const language = getSelectedLanguage();
|
||||||
|
if (language === "plaintext") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return { enabled: true, language: language };
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateUnifiedDiff(original, modified) {
|
||||||
|
return Diff.createTwoFilesPatch(
|
||||||
|
config.fileLabels?.left || "Original",
|
||||||
|
config.fileLabels?.right || "Modified",
|
||||||
|
original || "",
|
||||||
|
modified || "",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
{ context: config.contextLines || 4 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyHighlighting() {
|
||||||
|
const selectedLanguage = getSelectedLanguage();
|
||||||
|
if (selectedLanguage === "plaintext" || !global.hljs) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const codeBlocks = diffOutput.querySelectorAll("code");
|
||||||
|
codeBlocks.forEach((block) => {
|
||||||
|
block.classList.forEach((className) => {
|
||||||
|
if (className.startsWith("language-")) {
|
||||||
|
block.classList.remove(className);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
block.classList.add("language-" + selectedLanguage);
|
||||||
|
delete block.dataset.highlighted;
|
||||||
|
try {
|
||||||
|
global.hljs.highlightElement(block);
|
||||||
|
} catch (error) {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderDiff() {
|
||||||
|
let diffString = "";
|
||||||
|
try {
|
||||||
|
diffString = generateUnifiedDiff(leftTextarea.value, rightTextarea.value);
|
||||||
|
} catch (error) {
|
||||||
|
setOutput(
|
||||||
|
typeof messages.generateError === "function"
|
||||||
|
? messages.generateError(error)
|
||||||
|
: "<div>Diff generation failed.</div>",
|
||||||
|
messages.generateErrorStats || "",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let diffHtml = "";
|
||||||
|
try {
|
||||||
|
diffHtml = Diff2Html.html(diffString, {
|
||||||
|
drawFileList: false,
|
||||||
|
matching: "lines",
|
||||||
|
outputFormat: currentView,
|
||||||
|
highlight: getHighlightConfig(),
|
||||||
|
renderNothingWhenEmpty: false,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
setOutput(
|
||||||
|
typeof messages.renderError === "function"
|
||||||
|
? messages.renderError(error)
|
||||||
|
: "<div>Diff rendering failed.</div>",
|
||||||
|
messages.renderErrorStats || "",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
diffOutput.innerHTML = diffHtml;
|
||||||
|
applyHighlighting();
|
||||||
|
|
||||||
|
const added = diffOutput.querySelectorAll(".d2h-ins").length;
|
||||||
|
const deleted = diffOutput.querySelectorAll(".d2h-del").length;
|
||||||
|
const identical = added === 0 && deleted === 0;
|
||||||
|
|
||||||
|
diffStats.innerHTML = formatStats({
|
||||||
|
added: added,
|
||||||
|
deleted: deleted,
|
||||||
|
identical: identical,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!diffHtml.trim() || diffOutput.innerText.trim() === "") {
|
||||||
|
setOutput(messages.blankResult || "<div>Files are identical.</div>", messages.blankStats || "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setActiveView(view, shouldRender) {
|
||||||
|
currentView = view;
|
||||||
|
viewOptions.forEach((button) => {
|
||||||
|
button.classList.toggle(
|
||||||
|
"active",
|
||||||
|
button.getAttribute("data-view") === view,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (shouldRender !== false) {
|
||||||
|
renderDiff();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadExample() {
|
||||||
|
leftTextarea.value = example.left || "";
|
||||||
|
rightTextarea.value = example.right || "";
|
||||||
|
if (example.language) {
|
||||||
|
languageSelect.value = example.language;
|
||||||
|
}
|
||||||
|
renderDiff();
|
||||||
|
}
|
||||||
|
|
||||||
|
function swapTexts() {
|
||||||
|
const currentLeft = leftTextarea.value;
|
||||||
|
leftTextarea.value = rightTextarea.value;
|
||||||
|
rightTextarea.value = currentLeft;
|
||||||
|
renderDiff();
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearLeft() {
|
||||||
|
leftTextarea.value = "";
|
||||||
|
renderDiff();
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearRight() {
|
||||||
|
rightTextarea.value = "";
|
||||||
|
renderDiff();
|
||||||
|
}
|
||||||
|
|
||||||
|
compareBtn.addEventListener("click", renderDiff);
|
||||||
|
swapBtn.addEventListener("click", swapTexts);
|
||||||
|
exampleBtn.addEventListener("click", loadExample);
|
||||||
|
clearLeftBtn.addEventListener("click", clearLeft);
|
||||||
|
clearRightBtn.addEventListener("click", clearRight);
|
||||||
|
languageSelect.addEventListener("change", renderDiff);
|
||||||
|
|
||||||
|
viewOptions.forEach((button) => {
|
||||||
|
button.addEventListener("click", (event) => {
|
||||||
|
setActiveView(event.currentTarget.getAttribute("data-view"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("keydown", (event) => {
|
||||||
|
if ((event.ctrlKey || event.metaKey) && event.key === "Enter") {
|
||||||
|
event.preventDefault();
|
||||||
|
renderDiff();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
initStyleSwitcher(config);
|
||||||
|
setActiveView(currentView, false);
|
||||||
|
renderDiff();
|
||||||
|
}
|
||||||
|
|
||||||
|
global.initDiffPage = initDiffPage;
|
||||||
|
})(window);
|
||||||
@@ -23,11 +23,7 @@
|
|||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
<link
|
<link
|
||||||
href="https://fonts.googleapis.com/css2?family=Righteous&family=
|
href="https://fonts.googleapis.com/css2?family=Righteous&family=Space+Mono:ital,wght@0,400;0,700;1,400&display=swap"
|
||||||
|
|
||||||
@message_part::reasoning-1_2
|
|
||||||
|
|
||||||
Space+Mono:ital,wght@0,400;0,700;1,400&display=swap"
|
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -90,6 +86,26 @@ Space+Mono:ital,wght@0,400;0,700;1,400&display=swap"
|
|||||||
.view-option.active {
|
.view-option.active {
|
||||||
@apply text-vapor-bg bg-vapor-purple shadow-[0_0_15px_#b967ff];
|
@apply text-vapor-bg bg-vapor-purple shadow-[0_0_15px_#b967ff];
|
||||||
}
|
}
|
||||||
|
.style-switcher {
|
||||||
|
@apply flex items-center gap-3;
|
||||||
|
}
|
||||||
|
.style-switcher-label {
|
||||||
|
@apply font-vapor text-sm tracking-widest text-vapor-yellow drop-shadow-[0_0_5px_#fffb96];
|
||||||
|
}
|
||||||
|
.style-switcher-select {
|
||||||
|
background: #120458;
|
||||||
|
border: 2px solid #b967ff;
|
||||||
|
color: #01cdfe;
|
||||||
|
padding: 0.375rem 0.75rem;
|
||||||
|
font-family: "Righteous", cursive;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.style-switcher-select:hover,
|
||||||
|
.style-switcher-select:focus {
|
||||||
|
border-color: #01cdfe;
|
||||||
|
box-shadow: 0 0 10px #01cdfe;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================
|
/* ==========================================
|
||||||
@@ -377,6 +393,8 @@ function playSynthwave() {
|
|||||||
MONO
|
MONO
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div data-style-switcher></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
<!-- 操作按钮 -->
|
||||||
@@ -425,193 +443,40 @@ function playSynthwave() {
|
|||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/diff2html@3.4.47/bundles/js/diff2html.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/diff2html@3.4.47/bundles/js/diff2html.min.js"></script>
|
||||||
|
|
||||||
<!-- 核心对比逻辑 -->
|
<script src="../shared/diff-page.js"></script>
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
window.initDiffPage({
|
||||||
"use strict";
|
themeId: "synthwave",
|
||||||
|
currentThemePath: "synthwave/index.html",
|
||||||
const leftTextarea = document.getElementById("leftTextarea");
|
switcherLabel: "SKIN:",
|
||||||
const rightTextarea = document.getElementById("rightTextarea");
|
switcherAriaLabel: "Switch synthwave skin",
|
||||||
const compareBtn = document.getElementById("compareBtn");
|
fileLabels: {
|
||||||
const swapBtn = document.getElementById("swapBtn");
|
left: "CASSETTE_A",
|
||||||
const exampleBtn = document.getElementById("exampleBtn");
|
right: "CASSETTE_B",
|
||||||
const clearLeftBtn = document.getElementById("clearLeftBtn");
|
},
|
||||||
const clearRightBtn = document.getElementById("clearRightBtn");
|
example: {
|
||||||
const languageSelect = document.getElementById("languageSelect");
|
left: `function playSynthwave() {\n console.log("Loading VHS tape...");\n return "Nostalgia";\n}`,
|
||||||
const diffOutput = document.getElementById("diff-output");
|
right: `function playSynthwave() {\n console.log("Loading LaserDisc...");\n enableNeonGlow();\n return "A E S T H E T I C S";\n}`,
|
||||||
const diffStats = document.getElementById("diffStats");
|
language: "javascript",
|
||||||
const viewOptions = document.querySelectorAll(".view-option");
|
},
|
||||||
|
messages: {
|
||||||
let currentView = "side-by-side";
|
generateError: (error) =>
|
||||||
|
`<div class="p-12 text-center font-vapor text-vapor-pink tracking-widest text-xl">GLITCH_ERROR: ${error.message}</div>`,
|
||||||
function getSelectedLanguage() {
|
renderError: () =>
|
||||||
let val = languageSelect.value;
|
'<div class="p-12 text-center font-vapor text-vapor-pink tracking-widest text-xl">TAPE_JAMMED</div>',
|
||||||
// 映射回真实的语言名称
|
blankResult:
|
||||||
if (val === "javascript") return "javascript";
|
'<div class="p-12 text-center font-vapor text-vapor-cyan tracking-widest text-xl">TRACKS_ARE_IDENTICAL</div>',
|
||||||
if (val === "typescript") return "typescript";
|
blankStats: "<span class='text-vapor-cyan'>SYNCED</span>",
|
||||||
if (val === "html") return "html";
|
generateErrorStats: "ERR",
|
||||||
if (val === "css") return "css";
|
renderErrorStats: "ERR",
|
||||||
if (val === "json") return "json";
|
},
|
||||||
if (val === "python") return "python";
|
formatStats: ({ added, deleted, identical }) => {
|
||||||
return "plaintext";
|
if (identical) {
|
||||||
|
return "<span class='text-vapor-cyan drop-shadow-[0_0_5px_#01cdfe]'>VIBES_SYNCED</span>";
|
||||||
}
|
}
|
||||||
|
return `<span class='text-vapor-cyan drop-shadow-[0_0_5px_#01cdfe]'>+${added} UPLOADED</span> <span class='text-vapor-purple mx-2'>|</span> <span class='text-vapor-pink drop-shadow-[0_0_5px_#ff71ce]'>-${deleted} ERASED</span>`;
|
||||||
function getHighlightConfig() {
|
},
|
||||||
const lang = getSelectedLanguage();
|
|
||||||
if (lang === "plaintext") return false;
|
|
||||||
return { enabled: true, language: lang };
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateUnifiedDiff(original, modified) {
|
|
||||||
return Diff.createTwoFilesPatch(
|
|
||||||
"CASSETTE_A",
|
|
||||||
"CASSETTE_B",
|
|
||||||
original || "",
|
|
||||||
modified || "",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
{ context: 4 },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderDiff() {
|
|
||||||
const leftText = leftTextarea.value;
|
|
||||||
const rightText = rightTextarea.value;
|
|
||||||
|
|
||||||
let diffString;
|
|
||||||
try {
|
|
||||||
diffString = generateUnifiedDiff(leftText, rightText);
|
|
||||||
} catch (e) {
|
|
||||||
diffOutput.innerHTML = `<div class="p-12 text-center font-vapor text-vapor-pink tracking-widest text-xl">GLITCH_ERROR: ${e.message}</div>`;
|
|
||||||
diffStats.textContent = "ERR";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const configuration = {
|
|
||||||
drawFileList: false,
|
|
||||||
matching: "lines",
|
|
||||||
outputFormat: currentView,
|
|
||||||
highlight: getHighlightConfig(),
|
|
||||||
renderNothingWhenEmpty: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let diffHtml = "";
|
|
||||||
try {
|
|
||||||
diffHtml = Diff2Html.html(diffString, configuration);
|
|
||||||
} catch (e) {
|
|
||||||
diffOutput.innerHTML = `<div class="p-12 text-center font-vapor text-vapor-pink tracking-widest text-xl">TAPE_JAMMED</div>`;
|
|
||||||
diffStats.textContent = "ERR";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
diffOutput.innerHTML = diffHtml;
|
|
||||||
|
|
||||||
const selectedLang = getSelectedLanguage();
|
|
||||||
if (selectedLang !== "plaintext") {
|
|
||||||
const codeBlocks = diffOutput.querySelectorAll("code");
|
|
||||||
if (codeBlocks.length > 0 && window.hljs) {
|
|
||||||
codeBlocks.forEach((block) => {
|
|
||||||
block.classList.forEach((cls) => {
|
|
||||||
if (cls.startsWith("language-")) block.classList.remove(cls);
|
|
||||||
});
|
});
|
||||||
block.classList.add(`language-${selectedLang}`);
|
|
||||||
if (block.dataset.highlighted) {
|
|
||||||
delete block.dataset.highlighted;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
hljs.highlightElement(block);
|
|
||||||
} catch (e) {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const addedLines = diffOutput.querySelectorAll(".d2h-ins").length;
|
|
||||||
const deletedLines = diffOutput.querySelectorAll(".d2h-del").length;
|
|
||||||
if (addedLines === 0 && deletedLines === 0) {
|
|
||||||
diffStats.innerHTML =
|
|
||||||
"<span class='text-vapor-cyan drop-shadow-[0_0_5px_#01cdfe]'>VIBES_SYNCED</span>";
|
|
||||||
} else {
|
|
||||||
diffStats.innerHTML = `<span class='text-vapor-cyan drop-shadow-[0_0_5px_#01cdfe]'>+${addedLines} UPLOADED</span> <span class='text-vapor-purple mx-2'>|</span> <span class='text-vapor-pink drop-shadow-[0_0_5px_#ff71ce]'>-${deletedLines} ERASED</span>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!diffHtml.trim() || diffOutput.innerText.trim() === "") {
|
|
||||||
diffOutput.innerHTML =
|
|
||||||
'<div class="p-12 text-center font-vapor text-vapor-cyan tracking-widest text-xl">TRACKS_ARE_IDENTICAL</div>';
|
|
||||||
diffStats.innerHTML = "<span class='text-vapor-cyan'>SYNCED</span>";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setActiveView(view) {
|
|
||||||
currentView = view;
|
|
||||||
viewOptions.forEach((btn) => {
|
|
||||||
const val = btn.getAttribute("data-view");
|
|
||||||
if (val === view) {
|
|
||||||
btn.classList.add("active");
|
|
||||||
} else {
|
|
||||||
btn.classList.remove("active");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (
|
|
||||||
diffOutput.querySelector(".d2h-wrapper") ||
|
|
||||||
diffOutput.children.length > 0
|
|
||||||
) {
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadExample() {
|
|
||||||
leftTextarea.value = `function playSynthwave() {\n console.log("Loading VHS tape...");\n return "Nostalgia";\n}`;
|
|
||||||
rightTextarea.value = `function playSynthwave() {\n console.log("Loading LaserDisc...");\n enableNeonGlow();\n return "A E S T H E T I C S";\n}`;
|
|
||||||
// 恢复下拉框的显示值
|
|
||||||
languageSelect.selectedIndex = 0;
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
|
|
||||||
function swapTexts() {
|
|
||||||
const temp = leftTextarea.value;
|
|
||||||
leftTextarea.value = rightTextarea.value;
|
|
||||||
rightTextarea.value = temp;
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearLeft() {
|
|
||||||
leftTextarea.value = "";
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
function clearRight() {
|
|
||||||
rightTextarea.value = "";
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
|
|
||||||
compareBtn.addEventListener("click", renderDiff);
|
|
||||||
swapBtn.addEventListener("click", swapTexts);
|
|
||||||
exampleBtn.addEventListener("click", loadExample);
|
|
||||||
clearLeftBtn.addEventListener("click", clearLeft);
|
|
||||||
clearRightBtn.addEventListener("click", clearRight);
|
|
||||||
|
|
||||||
languageSelect.addEventListener("change", () => {
|
|
||||||
if (
|
|
||||||
diffOutput.querySelector(".d2h-wrapper") ||
|
|
||||||
diffOutput.children.length > 0
|
|
||||||
)
|
|
||||||
renderDiff();
|
|
||||||
});
|
|
||||||
|
|
||||||
viewOptions.forEach((btn) => {
|
|
||||||
btn.addEventListener("click", (e) => {
|
|
||||||
setActiveView(e.currentTarget.getAttribute("data-view"));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener("keydown", (e) => {
|
|
||||||
if ((e.ctrlKey || e.metaKey) && e.key === "Enter") {
|
|
||||||
e.preventDefault();
|
|
||||||
renderDiff();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener("DOMContentLoaded", renderDiff);
|
|
||||||
})();
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user