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:
@@ -75,6 +75,26 @@
|
||||
.view-option.active {
|
||||
@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
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div data-style-switcher></div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
@@ -418,185 +440,40 @@ function enterMatrix() {
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- 核心对比逻辑 -->
|
||||
<script src="../shared/diff-page.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
const leftTextarea = document.getElementById("leftTextarea");
|
||||
const rightTextarea = document.getElementById("rightTextarea");
|
||||
const compareBtn = document.getElementById("compareBtn");
|
||||
const swapBtn = document.getElementById("swapBtn");
|
||||
const exampleBtn = document.getElementById("exampleBtn");
|
||||
const clearLeftBtn = document.getElementById("clearLeftBtn");
|
||||
const clearRightBtn = document.getElementById("clearRightBtn");
|
||||
const languageSelect = document.getElementById("languageSelect");
|
||||
const diffOutput = document.getElementById("diff-output");
|
||||
const diffStats = document.getElementById("diffStats");
|
||||
const viewOptions = document.querySelectorAll(".view-option");
|
||||
|
||||
let currentView = "side-by-side";
|
||||
|
||||
function getSelectedLanguage() {
|
||||
return languageSelect.value;
|
||||
}
|
||||
|
||||
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;
|
||||
window.initDiffPage({
|
||||
themeId: "matrix",
|
||||
currentThemePath: "matrix/index.html",
|
||||
switcherLabel: "MODE:",
|
||||
switcherAriaLabel: "Switch Matrix skin",
|
||||
fileLabels: {
|
||||
left: "SOURCE",
|
||||
right: "TARGET",
|
||||
},
|
||||
example: {
|
||||
left: `function enterMatrix() {\n console.log("Follow the white rabbit.");\n return "Ignorance is bliss.";\n}`,
|
||||
right: `function enterMatrix() {\n console.log("There is no spoon.");\n decodeConstruct();\n return "I know Kung Fu.";\n}`,
|
||||
language: "javascript",
|
||||
},
|
||||
messages: {
|
||||
generateError: (error) =>
|
||||
`<div class="p-12 text-center text-matrix-red tracking-widest">FATAL_ERROR: ${error.message}</div>`,
|
||||
renderError: () =>
|
||||
'<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>";
|
||||
}
|
||||
|
||||
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);
|
||||
})();
|
||||
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>`;
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user