initial commit
This commit is contained in:
507
exe/index.html
Normal file
507
exe/index.html
Normal file
@@ -0,0 +1,507 @@
|
||||
<!doctype html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.5, user-scalable=yes"
|
||||
/>
|
||||
<title>DIFF_CHECKER.EXE</title>
|
||||
|
||||
<!-- Highlight.js 主题 (复古终端风格) -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/vs.min.css"
|
||||
/>
|
||||
<!-- Diff2Html 核心样式 -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/diff2html@3.4.47/bundles/css/diff2html.min.css"
|
||||
/>
|
||||
|
||||
<!-- 引入复古像素字体 -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=VT323&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<!-- 引入 Tailwind CSS -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
|
||||
<!-- 配置 Tailwind -->
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
win: {
|
||||
bg: "#c0c0c0",
|
||||
title: "#000080",
|
||||
desktop: "#008080",
|
||||
borderLight: "#ffffff",
|
||||
borderDark: "#000000",
|
||||
borderGray: "#808080",
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
retro: ['"VT323"', "monospace"],
|
||||
mono: ['"Courier New"', "Courier", "monospace"],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style type="text/tailwindcss">
|
||||
@layer components {
|
||||
/* 经典 Win95 凸起边框 (外边框) */
|
||||
.win-outset {
|
||||
@apply bg-win-bg border-[2px] border-t-win-borderLight border-l-win-borderLight border-b-win-borderDark border-r-win-borderDark;
|
||||
}
|
||||
/* 经典 Win95 凹陷边框 (输入框/内容区) */
|
||||
.win-inset {
|
||||
@apply bg-white border-[2px] border-t-win-borderDark border-l-win-borderDark border-b-win-borderLight border-r-win-borderLight;
|
||||
}
|
||||
/* 经典按钮 */
|
||||
.win-btn {
|
||||
@apply win-outset px-4 py-1 font-retro text-lg text-black cursor-pointer active:border-t-win-borderDark active:border-l-win-borderDark active:border-b-win-borderLight active:border-r-win-borderLight active:pt-[5px] active:pb-[3px] active:pl-[17px] active:pr-[15px] focus:outline-none focus:ring-1 focus:ring-black focus:ring-offset-1 focus:ring-offset-win-bg;
|
||||
}
|
||||
/* 视图切换按钮的激活状态 (凹陷) */
|
||||
.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')] /* 经典棋盘格背景 */;
|
||||
}
|
||||
/* JS 动态插入的空状态提示 */
|
||||
.empty-message {
|
||||
@apply p-8 text-center font-retro text-xl text-gray-600 bg-white;
|
||||
}
|
||||
}
|
||||
|
||||
/* 覆盖 diff2html 默认样式,使其复古化 */
|
||||
.d2h-wrapper {
|
||||
font-family: "Courier New", Courier, monospace !important;
|
||||
}
|
||||
.d2h-file-header {
|
||||
display: none !important;
|
||||
}
|
||||
.d2h-code-line-ctn {
|
||||
@apply font-mono text-[14px] !important;
|
||||
}
|
||||
.d2h-file-wrapper {
|
||||
border: none !important;
|
||||
border-radius: 0 !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
.d2h-code-line {
|
||||
padding: 0 0.5em !important;
|
||||
}
|
||||
/* 强化差异颜色,去掉现代的柔和感 */
|
||||
.d2h-ins {
|
||||
background-color: #a6ffb2 !important;
|
||||
border-color: #008000 !important;
|
||||
}
|
||||
.d2h-del {
|
||||
background-color: #ffb2b2 !important;
|
||||
border-color: #800000 !important;
|
||||
}
|
||||
|
||||
/* 复古滚动条 */
|
||||
::-webkit-scrollbar {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: #dfdfdf;
|
||||
border-left: 1px solid #fff;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #c0c0c0;
|
||||
border: 2px solid;
|
||||
border-top-color: #fff;
|
||||
border-left-color: #fff;
|
||||
border-bottom-color: #000;
|
||||
border-right-color: #000;
|
||||
}
|
||||
::-webkit-scrollbar-button {
|
||||
background: #c0c0c0;
|
||||
border: 2px solid;
|
||||
border-top-color: #fff;
|
||||
border-left-color: #fff;
|
||||
border-bottom-color: #000;
|
||||
border-right-color: #000;
|
||||
display: block;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body
|
||||
class="bg-win-desktop font-retro p-2 md:p-8 min-h-screen flex flex-col items-center text-black selection:bg-win-title selection:text-white"
|
||||
>
|
||||
<!-- 主窗口 -->
|
||||
<div
|
||||
class="max-w-[1600px] w-full win-outset p-[2px] flex flex-col shadow-2xl"
|
||||
>
|
||||
<!-- 标题栏 -->
|
||||
<div
|
||||
class="bg-win-title text-white px-2 py-1 flex items-center justify-between mb-2"
|
||||
>
|
||||
<h1 class="font-retro text-xl tracking-wider flex items-center gap-2">
|
||||
💾 DIFF_CHECKER.EXE
|
||||
</h1>
|
||||
<div class="flex gap-1">
|
||||
<button
|
||||
class="win-outset w-6 h-6 flex items-center justify-center text-black font-bold text-sm leading-none active:border-t-win-borderDark active:border-l-win-borderDark active:border-b-win-borderLight active:border-r-win-borderLight"
|
||||
>
|
||||
_
|
||||
</button>
|
||||
<button
|
||||
class="win-outset w-6 h-6 flex items-center justify-center text-black font-bold text-sm leading-none active:border-t-win-borderDark active:border-l-win-borderDark active:border-b-win-borderLight active:border-r-win-borderLight"
|
||||
>
|
||||
□
|
||||
</button>
|
||||
<button
|
||||
class="win-outset w-6 h-6 flex items-center justify-center text-black font-bold text-sm leading-none active:border-t-win-borderDark active:border-l-win-borderDark active:border-b-win-borderLight active:border-r-win-borderLight"
|
||||
>
|
||||
X
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 菜单栏 (装饰) -->
|
||||
<div
|
||||
class="flex gap-4 px-2 pb-2 text-lg border-b border-win-borderGray mb-3"
|
||||
>
|
||||
<span class="cursor-pointer hover:bg-win-title hover:text-white px-1"
|
||||
><u>F</u>ile</span
|
||||
>
|
||||
<span class="cursor-pointer hover:bg-win-title hover:text-white px-1"
|
||||
><u>E</u>dit</span
|
||||
>
|
||||
<span class="cursor-pointer hover:bg-win-title hover:text-white px-1"
|
||||
><u>V</u>iew</span
|
||||
>
|
||||
<span class="cursor-pointer hover:bg-win-title hover:text-white px-1"
|
||||
><u>H</u>elp</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="px-3 pb-3 flex-1 flex flex-col">
|
||||
<!-- 双栏输入区 -->
|
||||
<div class="flex flex-col lg:flex-row gap-4 mb-4">
|
||||
<!-- 左侧面板 -->
|
||||
<div class="flex-1 flex flex-col gap-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="text-lg tracking-wide">A:\ORIGINAL.TXT</label>
|
||||
<button id="clearLeftBtn" class="win-btn text-sm py-0 px-2">
|
||||
CLEAR
|
||||
</button>
|
||||
</div>
|
||||
<textarea
|
||||
id="leftTextarea"
|
||||
class="win-inset w-full flex-1 min-h-[260px] h-[300px] p-2 font-mono text-[15px] leading-tight resize-y focus:outline-none"
|
||||
spellcheck="false"
|
||||
>
|
||||
function hello() {
|
||||
console.log("Hello World");
|
||||
return "Hi";
|
||||
}</textarea
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- 右侧面板 -->
|
||||
<div class="flex-1 flex flex-col gap-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="text-lg tracking-wide">B:\MODIFIED.TXT</label>
|
||||
<button id="clearRightBtn" class="win-btn text-sm py-0 px-2">
|
||||
CLEAR
|
||||
</button>
|
||||
</div>
|
||||
<textarea
|
||||
id="rightTextarea"
|
||||
class="win-inset w-full flex-1 min-h-[260px] h-[300px] p-2 font-mono text-[15px] leading-tight resize-y focus:outline-none"
|
||||
spellcheck="false"
|
||||
>
|
||||
function hello() {
|
||||
console.log("Hello, Diff Checker!");
|
||||
return "Hey there";
|
||||
}</textarea
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 工具栏 -->
|
||||
<div
|
||||
class="win-outset p-2 mb-4 flex flex-wrap items-center justify-between gap-4"
|
||||
>
|
||||
<div class="flex flex-wrap items-center gap-6">
|
||||
<!-- 语言选择 -->
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-lg">LANG:</label>
|
||||
<select
|
||||
id="languageSelect"
|
||||
class="win-inset px-2 py-1 text-lg font-retro outline-none cursor-pointer"
|
||||
>
|
||||
<option value="javascript" selected>JAVASCRIPT</option>
|
||||
<option value="typescript">TYPESCRIPT</option>
|
||||
<option value="html">HTML</option>
|
||||
<option value="css">CSS</option>
|
||||
<option value="json">JSON</option>
|
||||
<option value="python">PYTHON</option>
|
||||
<option value="java">JAVA</option>
|
||||
<option value="cpp">C++</option>
|
||||
<option value="plaintext">PLAIN_TEXT</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 视图切换 -->
|
||||
<div class="flex items-center gap-2" id="viewToggle">
|
||||
<label class="text-lg">VIEW:</label>
|
||||
<button
|
||||
class="win-btn view-option active"
|
||||
data-view="side-by-side"
|
||||
>
|
||||
SPLIT
|
||||
</button>
|
||||
<button class="win-btn view-option" data-view="line-by-line">
|
||||
UNIFIED
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="flex gap-2 flex-wrap">
|
||||
<button id="swapBtn" class="win-btn">SWAP</button>
|
||||
<button id="exampleBtn" class="win-btn">DEMO</button>
|
||||
<button id="compareBtn" class="win-btn font-bold border-[3px]">
|
||||
EXECUTE_DIFF
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 差异结果区域 -->
|
||||
<div class="flex flex-col flex-1">
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<span class="text-lg">C:\OUTPUT.LOG</span>
|
||||
<span
|
||||
id="diffStats"
|
||||
class="text-lg bg-black text-[#00ff00] px-2 font-mono"
|
||||
></span>
|
||||
</div>
|
||||
<div class="win-inset flex-1 overflow-hidden flex flex-col bg-white">
|
||||
<div id="diff-output" class="overflow-x-auto flex-1">
|
||||
<div class="empty-message">PRESS [EXECUTE_DIFF] TO START...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 状态栏 -->
|
||||
<div
|
||||
class="win-inset border-t-win-borderDark border-l-win-borderDark border-b-win-borderLight border-r-win-borderLight bg-win-bg px-2 py-1 mt-1 flex justify-between text-sm font-retro"
|
||||
>
|
||||
<span>STATUS: READY</span>
|
||||
<span>POWERED BY DIFF2HTML & HIGHLIGHT.JS</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 依赖库 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/diff@5.1.0/dist/diff.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>
|
||||
(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(
|
||||
"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>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user