initial commit
This commit is contained in:
513
notion/index.html
Normal file
513
notion/index.html
Normal file
@@ -0,0 +1,513 @@
|
||||
<!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 - Notion Style</title>
|
||||
|
||||
<!-- Highlight.js 主题 (GitHub 风格最接近 Notion 的代码块) -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css"
|
||||
/>
|
||||
<!-- Diff2Html 核心样式 -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/diff2html@3.4.47/bundles/css/diff2html.min.css"
|
||||
/>
|
||||
|
||||
<!-- 引入 Inter 字体 (Notion 默认英文字体) -->
|
||||
<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=Inter:wght@400;500;600&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<!-- 引入 Tailwind CSS -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
|
||||
<!-- 配置 Tailwind -->
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
notion: {
|
||||
text: "#37352f" /* Notion 默认文字黑 */,
|
||||
gray: "#787774" /* Notion 默认次要文字灰 */,
|
||||
lightGray: "#9a9a97" /* 更浅的灰 */,
|
||||
bg: "#ffffff" /* 纯白背景 */,
|
||||
hover: "#efefed" /* 悬浮浅灰背景 */,
|
||||
codeBg: "#f7f7f5" /* 代码块背景灰 */,
|
||||
border: "#e9e9e7" /* 极细边框灰 */,
|
||||
blue: "#2383e2" /* Notion 链接/按钮蓝 */,
|
||||
diffAdd: "#edf3ec" /* 低饱和度绿 */,
|
||||
diffDel: "#fbe4e4" /* 低饱和度红 */,
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
sans: [
|
||||
'"Inter"',
|
||||
"-apple-system",
|
||||
"BlinkMacSystemFont",
|
||||
'"Segoe UI"',
|
||||
"sans-serif",
|
||||
],
|
||||
mono: [
|
||||
'"SFMono-Regular"',
|
||||
"Menlo",
|
||||
"Monaco",
|
||||
"Consolas",
|
||||
'"Liberation Mono"',
|
||||
'"Courier New"',
|
||||
"monospace",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style type="text/tailwindcss">
|
||||
@layer components {
|
||||
/* Notion 风格按钮 */
|
||||
.notion-btn {
|
||||
@apply px-3 py-1.5 rounded-[4px] text-sm font-medium transition-colors duration-200 flex items-center gap-1.5 cursor-pointer select-none;
|
||||
}
|
||||
.notion-btn-ghost {
|
||||
@apply text-notion-text bg-transparent hover:bg-notion-hover active:bg-[#e1e1df];
|
||||
}
|
||||
.notion-btn-primary {
|
||||
@apply text-white bg-[#2f2f2f] hover:bg-[#454545] active:bg-[#1a1a1a];
|
||||
}
|
||||
|
||||
/* 视图切换按钮 */
|
||||
.view-option {
|
||||
@apply px-2.5 py-1 rounded-[4px] text-sm text-notion-gray hover:bg-notion-hover transition-colors cursor-pointer select-none;
|
||||
}
|
||||
.view-option.active {
|
||||
@apply text-notion-text font-medium bg-notion-hover;
|
||||
}
|
||||
|
||||
/* Notion 代码块输入框 */
|
||||
.notion-input-wrapper {
|
||||
@apply bg-notion-codeBg rounded-[4px] border border-transparent focus-within:border-notion-border transition-colors flex flex-col;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================
|
||||
深度定制 Diff2Html 样式 (Notion 极简风)
|
||||
========================================== */
|
||||
.d2h-wrapper {
|
||||
font-family:
|
||||
"SFMono-Regular", Menlo, Monaco, Consolas, monospace !important;
|
||||
color: #37352f !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: #37352f !important;
|
||||
}
|
||||
|
||||
/* 移除所有表格边框 */
|
||||
tbody,
|
||||
td,
|
||||
.d2h-code-linenumber,
|
||||
.d2h-info,
|
||||
.d2h-emptyplaceholder {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
/* 差异行背景色 (Notion 低饱和度色彩) */
|
||||
.d2h-ins {
|
||||
background-color: #edf3ec !important;
|
||||
}
|
||||
.d2h-del {
|
||||
background-color: #fbe4e4 !important;
|
||||
}
|
||||
|
||||
/* 行号区域极简处理 */
|
||||
.d2h-code-linenumber {
|
||||
background-color: #f7f7f5 !important;
|
||||
color: rgba(55, 53, 47, 0.4) !important;
|
||||
padding-right: 12px !important;
|
||||
}
|
||||
.d2h-info {
|
||||
background-color: #ffffff !important;
|
||||
color: rgba(55, 53, 47, 0.4) !important;
|
||||
}
|
||||
.d2h-emptyplaceholder {
|
||||
background-color: #f7f7f5 !important;
|
||||
}
|
||||
|
||||
/* 滚动条极简处理 */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #d3d1cb;
|
||||
border-radius: 4px;
|
||||
border: 2px solid #fff;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #aeaca6;
|
||||
}
|
||||
|
||||
/* 选中文本颜色 */
|
||||
::selection {
|
||||
background: rgba(35, 131, 226, 0.28);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body
|
||||
class="bg-notion-bg text-notion-text font-sans min-h-screen flex flex-col items-center selection:bg-[#cce2ff]"
|
||||
>
|
||||
<!-- 页面容器 (限制最大宽度,类似 Notion 页面) -->
|
||||
<div class="max-w-[1100px] w-full px-6 py-12 md:py-20 flex flex-col gap-8">
|
||||
<!-- 标题区 (大字号,带 Emoji) -->
|
||||
<header class="flex flex-col gap-2">
|
||||
<h1 class="text-4xl font-bold tracking-tight flex items-center gap-3">
|
||||
<span class="text-4xl">⚖️</span> Diff Checker
|
||||
</h1>
|
||||
<p class="text-notion-gray text-base mt-2">
|
||||
Paste your code below to compare changes. Supports syntax
|
||||
highlighting.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<!-- 双栏输入区 -->
|
||||
<div class="flex flex-col lg:flex-row gap-6">
|
||||
<!-- 左侧面板 -->
|
||||
<div class="flex-1 notion-input-wrapper">
|
||||
<div
|
||||
class="flex items-center justify-between px-4 py-2 border-b border-notion-border/50"
|
||||
>
|
||||
<label class="text-sm text-notion-gray font-medium">Original</label>
|
||||
<button
|
||||
id="clearLeftBtn"
|
||||
class="text-xs text-notion-lightGray hover:text-notion-text transition-colors"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
<textarea
|
||||
id="leftTextarea"
|
||||
class="w-full flex-1 min-h-[260px] h-[300px] p-4 bg-transparent font-mono text-[13px] leading-relaxed resize-y outline-none placeholder:text-notion-lightGray"
|
||||
spellcheck="false"
|
||||
>
|
||||
function hello() {
|
||||
console.log("Hello World");
|
||||
return "Hi";
|
||||
}</textarea
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- 右侧面板 -->
|
||||
<div class="flex-1 notion-input-wrapper">
|
||||
<div
|
||||
class="flex items-center justify-between px-4 py-2 border-b border-notion-border/50"
|
||||
>
|
||||
<label class="text-sm text-notion-gray font-medium">Modified</label>
|
||||
<button
|
||||
id="clearRightBtn"
|
||||
class="text-xs text-notion-lightGray hover:text-notion-text transition-colors"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
<textarea
|
||||
id="rightTextarea"
|
||||
class="w-full flex-1 min-h-[260px] h-[300px] p-4 bg-transparent font-mono text-[13px] leading-relaxed resize-y outline-none placeholder:text-notion-lightGray"
|
||||
spellcheck="false"
|
||||
>
|
||||
function hello() {
|
||||
console.log("Hello, Diff Checker!");
|
||||
return "Hey there";
|
||||
}</textarea
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 工具栏 (极简分割线上下) -->
|
||||
<div
|
||||
class="flex flex-wrap items-center justify-between gap-4 py-2 border-y border-notion-border"
|
||||
>
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<!-- 语言选择 -->
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm text-notion-gray">Language</span>
|
||||
<select
|
||||
id="languageSelect"
|
||||
class="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"
|
||||
>
|
||||
<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="w-[1px] h-4 bg-notion-border hidden md:block"></div>
|
||||
|
||||
<!-- 视图切换 -->
|
||||
<div class="flex items-center gap-1" id="viewToggle">
|
||||
<button class="view-option active" data-view="side-by-side">
|
||||
Split view
|
||||
</button>
|
||||
<button class="view-option" data-view="line-by-line">
|
||||
Unified view
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="flex gap-2 flex-wrap">
|
||||
<button id="swapBtn" class="notion-btn notion-btn-ghost">
|
||||
<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="notion-btn notion-btn-ghost">
|
||||
Demo
|
||||
</button>
|
||||
<button id="compareBtn" class="notion-btn notion-btn-primary ml-2">
|
||||
Compare
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 差异结果区域 -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center justify-between py-1">
|
||||
<h2 class="text-lg font-semibold">Result</h2>
|
||||
<span id="diffStats" class="text-sm text-notion-gray"></span>
|
||||
</div>
|
||||
|
||||
<div class="notion-input-wrapper overflow-hidden">
|
||||
<div id="diff-output" class="overflow-x-auto min-h-[100px]">
|
||||
<div class="p-8 text-center text-notion-lightGray text-sm">
|
||||
Click "Compare" to see the differences.
|
||||
</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>
|
||||
(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="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>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user