Extract duplicated diff rendering logic into shared/diff-page.js Implement theme switcher component across all templates
473 lines
16 KiB
HTML
473 lines
16 KiB
HTML
<!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 - macOS Glass</title>
|
|
|
|
<!-- Highlight.js 主题 (替换为 Xcode 风格) -->
|
|
<link
|
|
rel="stylesheet"
|
|
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/xcode.min.css"
|
|
/>
|
|
<!-- Diff2Html 核心样式 -->
|
|
<link
|
|
rel="stylesheet"
|
|
href="https://cdn.jsdelivr.net/npm/diff2html@3.4.47/bundles/css/diff2html.min.css"
|
|
/>
|
|
|
|
<!-- 引入 Tailwind CSS -->
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
|
|
<!-- 配置 Tailwind -->
|
|
<script>
|
|
tailwind.config = {
|
|
theme: {
|
|
extend: {
|
|
fontFamily: {
|
|
sans: [
|
|
"-apple-system",
|
|
"BlinkMacSystemFont",
|
|
'"SF Pro Text"',
|
|
'"Segoe UI"',
|
|
"Roboto",
|
|
"sans-serif",
|
|
],
|
|
mono: [
|
|
'"SF Mono"',
|
|
'"Menlo"',
|
|
'"Monaco"',
|
|
'"Courier New"',
|
|
"monospace",
|
|
],
|
|
},
|
|
animation: {
|
|
blob: "blob 10s infinite",
|
|
},
|
|
keyframes: {
|
|
blob: {
|
|
"0%": { transform: "translate(0px, 0px) scale(1)" },
|
|
"33%": { transform: "translate(30px, -50px) scale(1.1)" },
|
|
"66%": { transform: "translate(-20px, 20px) scale(0.9)" },
|
|
"100%": { transform: "translate(0px, 0px) scale(1)" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style type="text/tailwindcss">
|
|
@layer components {
|
|
/* 毛玻璃面板基础类 */
|
|
.glass-panel {
|
|
@apply bg-white/40 backdrop-blur-xl border border-white/60 shadow-[0_8px_32px_0_rgba(31,38,135,0.07)];
|
|
}
|
|
/* 毛玻璃输入框 */
|
|
.glass-input {
|
|
@apply bg-white/50 backdrop-blur-md border border-white/50 focus:bg-white/80 focus:border-white/80 focus:shadow-[0_0_0_4px_rgba(255,255,255,0.3)] transition-all duration-300 outline-none;
|
|
}
|
|
/* 毛玻璃按钮 */
|
|
.glass-btn {
|
|
@apply bg-white/50 backdrop-blur-md border border-white/60 text-slate-700 font-medium hover:bg-white/80 hover:shadow-sm hover:-translate-y-[0.5px] active:translate-y-[0.5px] transition-all duration-200;
|
|
}
|
|
/* 视图切换按钮激活状态 */
|
|
.view-option {
|
|
@apply px-4 py-1.5 rounded-lg text-sm font-medium text-slate-500 hover:text-slate-800 transition-all;
|
|
}
|
|
.view-option.active {
|
|
@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;
|
|
}
|
|
}
|
|
|
|
/* ==========================================
|
|
覆盖 Diff2Html 样式,使其融入毛玻璃背景
|
|
========================================== */
|
|
.d2h-wrapper {
|
|
font-family: "SF Mono", Menlo, Monaco, monospace !important;
|
|
}
|
|
.d2h-file-header {
|
|
display: none !important;
|
|
}
|
|
.d2h-file-wrapper {
|
|
border: none !important;
|
|
background: transparent !important;
|
|
margin-bottom: 0 !important;
|
|
}
|
|
.d2h-code-line-ctn {
|
|
@apply font-mono text-[13px] !important;
|
|
color: #334155 !important;
|
|
}
|
|
|
|
/* 差异行背景色 (柔和的苹果风色彩) */
|
|
.d2h-ins {
|
|
background-color: rgba(52, 199, 89, 0.15) !important;
|
|
border-color: rgba(52, 199, 89, 0.3) !important;
|
|
}
|
|
.d2h-del {
|
|
background-color: rgba(255, 59, 48, 0.12) !important;
|
|
border-color: rgba(255, 59, 48, 0.3) !important;
|
|
}
|
|
|
|
/* 行号和空行区域透明化 */
|
|
.d2h-code-linenumber {
|
|
background-color: rgba(255, 255, 255, 0.3) !important;
|
|
border-color: rgba(255, 255, 255, 0.4) !important;
|
|
color: #94a3b8 !important;
|
|
}
|
|
.d2h-info {
|
|
background-color: rgba(255, 255, 255, 0.4) !important;
|
|
color: #64748b !important;
|
|
border-color: rgba(255, 255, 255, 0.5) !important;
|
|
}
|
|
.d2h-emptyplaceholder {
|
|
background-color: rgba(255, 255, 255, 0.2) !important;
|
|
border-color: rgba(255, 255, 255, 0.3) !important;
|
|
}
|
|
tbody {
|
|
border-color: rgba(255, 255, 255, 0.5) !important;
|
|
}
|
|
td {
|
|
border-color: rgba(255, 255, 255, 0.5) !important;
|
|
}
|
|
|
|
/* macOS 风格滚动条 */
|
|
::-webkit-scrollbar {
|
|
width: 10px;
|
|
height: 10px;
|
|
}
|
|
::-webkit-scrollbar-track {
|
|
background: transparent;
|
|
}
|
|
::-webkit-scrollbar-thumb {
|
|
background: rgba(0, 0, 0, 0.15);
|
|
border-radius: 10px;
|
|
border: 2px solid transparent;
|
|
background-clip: padding-box;
|
|
}
|
|
::-webkit-scrollbar-thumb:hover {
|
|
background: rgba(0, 0, 0, 0.25);
|
|
border: 2px solid transparent;
|
|
background-clip: padding-box;
|
|
}
|
|
|
|
/* 选中文本颜色 */
|
|
::selection {
|
|
background: rgba(10, 132, 255, 0.3);
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body
|
|
class="font-sans min-h-screen flex flex-col items-center p-4 md:p-8 text-slate-800 relative overflow-x-hidden selection:bg-blue-200"
|
|
>
|
|
<!-- 动态弥散渐变背景 (macOS Big Sur 风格) -->
|
|
<div class="fixed inset-0 z-[-1] bg-[#f4f7fb] overflow-hidden">
|
|
<div
|
|
class="absolute top-[-10%] left-[-10%] w-[40rem] h-[40rem] bg-purple-300/60 rounded-full mix-blend-multiply filter blur-[100px] animate-blob"
|
|
></div>
|
|
<div
|
|
class="absolute top-[20%] right-[-10%] w-[35rem] h-[35rem] bg-blue-300/60 rounded-full mix-blend-multiply filter blur-[100px] animate-blob"
|
|
style="animation-delay: 2s"
|
|
></div>
|
|
<div
|
|
class="absolute bottom-[-20%] left-[20%] w-[45rem] h-[45rem] bg-pink-300/60 rounded-full mix-blend-multiply filter blur-[100px] animate-blob"
|
|
style="animation-delay: 4s"
|
|
></div>
|
|
<div
|
|
class="absolute bottom-[10%] right-[20%] w-[30rem] h-[30rem] bg-teal-200/60 rounded-full mix-blend-multiply filter blur-[100px] animate-blob"
|
|
style="animation-delay: 6s"
|
|
></div>
|
|
</div>
|
|
|
|
<!-- 主窗口 -->
|
|
<div
|
|
class="max-w-[1600px] w-full glass-panel rounded-[2rem] p-2 flex flex-col"
|
|
>
|
|
<!-- macOS 标题栏 -->
|
|
<div
|
|
class="flex items-center justify-between px-4 py-3 border-b border-white/40"
|
|
>
|
|
<!-- 红黄绿按钮 -->
|
|
<div class="flex gap-2 w-20">
|
|
<div
|
|
class="w-3.5 h-3.5 rounded-full bg-[#FF5F56] border border-[#E0443E] shadow-inner"
|
|
></div>
|
|
<div
|
|
class="w-3.5 h-3.5 rounded-full bg-[#FFBD2E] border border-[#DEA123] shadow-inner"
|
|
></div>
|
|
<div
|
|
class="w-3.5 h-3.5 rounded-full bg-[#27C93F] border border-[#1AAB29] shadow-inner"
|
|
></div>
|
|
</div>
|
|
|
|
<!-- 标题 -->
|
|
<h1
|
|
class="font-semibold text-slate-700 text-sm tracking-wide flex-1 text-center flex items-center justify-center gap-2"
|
|
>
|
|
<svg
|
|
class="w-4 h-4 text-slate-500"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
|
|
></path>
|
|
</svg>
|
|
Diff Checker
|
|
</h1>
|
|
|
|
<div class="w-20"></div>
|
|
<!-- 占位,保持标题居中 -->
|
|
</div>
|
|
|
|
<div class="p-4 md:p-6 flex-1 flex flex-col gap-6">
|
|
<!-- 双栏输入区 -->
|
|
<div class="flex flex-col lg:flex-row gap-6">
|
|
<!-- 左侧面板 -->
|
|
<div class="flex-1 flex flex-col gap-2">
|
|
<div class="flex items-center justify-between px-2">
|
|
<label
|
|
class="text-sm font-semibold text-slate-600 flex items-center gap-2"
|
|
>
|
|
<span class="w-2 h-2 rounded-full bg-slate-400"></span>
|
|
Original File
|
|
</label>
|
|
<button
|
|
id="clearLeftBtn"
|
|
class="text-xs font-medium text-slate-400 hover:text-slate-700 transition-colors"
|
|
>
|
|
Clear
|
|
</button>
|
|
</div>
|
|
<textarea
|
|
id="leftTextarea"
|
|
class="glass-input w-full flex-1 min-h-[260px] h-[300px] p-5 rounded-2xl font-mono text-[13px] leading-relaxed resize-y text-slate-700 placeholder:text-slate-400"
|
|
spellcheck="false"
|
|
>
|
|
function hello() {
|
|
console.log("Hello World");
|
|
return "Hi";
|
|
}</textarea
|
|
>
|
|
</div>
|
|
|
|
<!-- 右侧面板 -->
|
|
<div class="flex-1 flex flex-col gap-2">
|
|
<div class="flex items-center justify-between px-2">
|
|
<label
|
|
class="text-sm font-semibold text-slate-600 flex items-center gap-2"
|
|
>
|
|
<span class="w-2 h-2 rounded-full bg-blue-400"></span>
|
|
Modified File
|
|
</label>
|
|
<button
|
|
id="clearRightBtn"
|
|
class="text-xs font-medium text-slate-400 hover:text-slate-700 transition-colors"
|
|
>
|
|
Clear
|
|
</button>
|
|
</div>
|
|
<textarea
|
|
id="rightTextarea"
|
|
class="glass-input w-full flex-1 min-h-[260px] h-[300px] p-5 rounded-2xl font-mono text-[13px] leading-relaxed resize-y text-slate-700 placeholder:text-slate-400"
|
|
spellcheck="false"
|
|
>
|
|
function hello() {
|
|
console.log("Hello, Diff Checker!");
|
|
return "Hey there";
|
|
}</textarea
|
|
>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 工具栏 -->
|
|
<div
|
|
class="glass-panel rounded-2xl p-3 flex flex-wrap items-center justify-between gap-4"
|
|
>
|
|
<div class="flex flex-wrap items-center gap-4">
|
|
<!-- 语言选择 -->
|
|
<div
|
|
class="flex items-center gap-2 bg-white/40 px-3 py-1.5 rounded-xl border border-white/50"
|
|
>
|
|
<svg
|
|
class="w-4 h-4 text-slate-500"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"
|
|
></path>
|
|
</svg>
|
|
<select
|
|
id="languageSelect"
|
|
class="bg-transparent text-sm font-medium text-slate-700 outline-none cursor-pointer appearance-none pr-4"
|
|
>
|
|
<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 bg-white/40 p-1 rounded-xl border border-white/50"
|
|
id="viewToggle"
|
|
>
|
|
<button class="view-option active" data-view="side-by-side">
|
|
Split
|
|
</button>
|
|
<button class="view-option" data-view="line-by-line">
|
|
Unified
|
|
</button>
|
|
</div>
|
|
|
|
<div data-style-switcher></div>
|
|
</div>
|
|
|
|
<!-- 操作按钮 -->
|
|
<div class="flex gap-3 flex-wrap">
|
|
<button
|
|
id="swapBtn"
|
|
class="glass-btn px-5 py-2 rounded-xl text-sm flex items-center gap-2"
|
|
>
|
|
<svg
|
|
class="w-4 h-4"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4"
|
|
></path>
|
|
</svg>
|
|
Swap
|
|
</button>
|
|
<button
|
|
id="exampleBtn"
|
|
class="glass-btn px-5 py-2 rounded-xl text-sm"
|
|
>
|
|
Demo
|
|
</button>
|
|
<button
|
|
id="compareBtn"
|
|
class="bg-blue-500 hover:bg-blue-600 text-white shadow-[0_4px_14px_0_rgba(59,130,246,0.39)] hover:shadow-[0_6px_20px_rgba(59,130,246,0.23)] px-6 py-2 rounded-xl text-sm font-medium transition-all duration-200 hover:-translate-y-[0.5px] active:translate-y-[0.5px] flex items-center gap-2 border border-blue-400/50"
|
|
>
|
|
<svg
|
|
class="w-4 h-4"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
></path>
|
|
</svg>
|
|
Compare
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 差异结果区域 -->
|
|
<div class="glass-input rounded-2xl flex flex-col overflow-hidden">
|
|
<div
|
|
class="px-5 py-3 border-b border-white/50 flex items-center justify-between bg-white/30"
|
|
>
|
|
<span class="text-sm font-semibold text-slate-700"
|
|
>Diff Output</span
|
|
>
|
|
<span
|
|
id="diffStats"
|
|
class="text-xs font-medium text-slate-500 bg-white/50 px-3 py-1 rounded-full border border-white/60"
|
|
></span>
|
|
</div>
|
|
<div id="diff-output" class="overflow-x-auto min-h-[150px]">
|
|
<div class="p-12 text-center text-slate-400 text-sm">
|
|
Ready to compare files.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</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 src="../shared/diff-page.js"></script>
|
|
<script>
|
|
window.initDiffPage({
|
|
themeId: "macos",
|
|
currentThemePath: "macos/index.html",
|
|
switcherLabel: "Theme",
|
|
switcherAriaLabel: "Switch macOS glass theme",
|
|
fileLabels: {
|
|
left: "Original",
|
|
right: "Modified",
|
|
},
|
|
example: {
|
|
left: `function hello() {\n console.log("Hello World");\n return "Hi";\n}`,
|
|
right: `function hello() {\n console.log("Hello, Diff Checker!");\n return "Hey there";\n}`,
|
|
language: "javascript",
|
|
},
|
|
messages: {
|
|
generateError: (error) =>
|
|
`<div class="p-12 text-center text-red-500 text-sm">Error: ${error.message}</div>`,
|
|
renderError: () =>
|
|
'<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>`;
|
|
},
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|