@x-oasis/html-fragment-diff 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +15 -0
- package/CHANGELOG.md +13 -0
- package/README.md +193 -0
- package/dist/html-fragment-diff.cjs.development.js +159 -0
- package/dist/html-fragment-diff.cjs.development.js.map +1 -0
- package/dist/html-fragment-diff.cjs.production.min.js +2 -0
- package/dist/html-fragment-diff.cjs.production.min.js.map +1 -0
- package/dist/html-fragment-diff.esm.js +153 -0
- package/dist/html-fragment-diff.esm.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +8 -0
- package/examples/.turbo/turbo-build.log +12 -0
- package/examples/README.md +99 -0
- package/examples/index.html +12 -0
- package/examples/package.json +26 -0
- package/examples/src/App.tsx +252 -0
- package/examples/src/index.css +193 -0
- package/examples/src/main.tsx +10 -0
- package/examples/tsconfig.json +25 -0
- package/examples/tsconfig.node.json +10 -0
- package/examples/vite.config.ts +30 -0
- package/package.json +26 -0
- package/src/index.ts +186 -0
- package/test/index.test.ts +178 -0
- package/tsconfig.build.json +10 -0
- package/tsconfig.json +7 -0
- package/vitest.config.ts +21 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
|
|
2
|
+
> html-fragment-diff-example@0.1.0 build /home/runner/work/x-oasis/x-oasis/packages/diff/html-fragment-diff/examples
|
|
3
|
+
> vite build
|
|
4
|
+
|
|
5
|
+
[36mvite v4.1.4 [32mbuilding for production...[36m[39m
|
|
6
|
+
transforming...
|
|
7
|
+
[32m✓[39m 51 modules transformed.
|
|
8
|
+
rendering chunks...
|
|
9
|
+
computing gzip size...
|
|
10
|
+
[2mdist/[22m[32mindex.html [39m[1m[2m 0.41 kB[22m[1m[22m
|
|
11
|
+
[2mdist/[22m[2massets/[22m[35mindex-41af7777.css [39m[1m[2m 2.44 kB[22m[1m[22m[2m │ gzip: 0.89 kB[22m
|
|
12
|
+
[2mdist/[22m[2massets/[22m[36mindex-90f7e9a9.js [39m[1m[2m296.32 kB[22m[1m[22m[2m │ gzip: 95.11 kB[22m
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
这个目录包含 `@x-oasis/html-fragment-diff` 的交互式示例。
|
|
4
|
+
|
|
5
|
+
## 运行示例
|
|
6
|
+
|
|
7
|
+
这是一个基于 Vite + React 的项目。
|
|
8
|
+
|
|
9
|
+
### 安装依赖
|
|
10
|
+
|
|
11
|
+
在项目根目录运行:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm install
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### 启动开发服务器
|
|
18
|
+
|
|
19
|
+
在 `examples` 目录下运行:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
cd packages/diff/html-fragment-diff/examples
|
|
23
|
+
pnpm install
|
|
24
|
+
pnpm dev
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
或者从项目根目录运行:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pnpm install
|
|
31
|
+
cd packages/diff/html-fragment-diff/examples
|
|
32
|
+
pnpm dev
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
开发服务器将在 `http://localhost:3001` 启动,并自动在浏览器中打开。
|
|
36
|
+
|
|
37
|
+
### 构建生产版本
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pnpm build
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 预览生产构建
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pnpm preview
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## 功能说明
|
|
50
|
+
|
|
51
|
+
示例页面包含以下功能:
|
|
52
|
+
|
|
53
|
+
1. **HTML 片段输入**: 并排显示原始片段和最终片段输入框
|
|
54
|
+
2. **解析结果**: 显示每个片段的解析结果(标签名、class 列表、文本内容等)
|
|
55
|
+
3. **Class 变更**: 显示新增和删除的 class
|
|
56
|
+
4. **文本变更**: 显示文本内容的变更情况
|
|
57
|
+
5. **JSON 输出**: 显示完整的对比结果 JSON
|
|
58
|
+
|
|
59
|
+
## 示例场景
|
|
60
|
+
|
|
61
|
+
### 场景 1: Class 新增
|
|
62
|
+
|
|
63
|
+
- Original: `<h1 class="title">Hello</h1>`
|
|
64
|
+
- Final: `<h1 class="title active">Hello</h1>`
|
|
65
|
+
- 说明: 演示 class 的新增,从 "title" 变为 "title active"
|
|
66
|
+
|
|
67
|
+
### 场景 2: Class 删除
|
|
68
|
+
|
|
69
|
+
- Original: `<h1 class="title primary">Hello</h1>`
|
|
70
|
+
- Final: `<h1 class="title">Hello</h1>`
|
|
71
|
+
- 说明: 演示 class 的删除,从 "title primary" 变为 "title"
|
|
72
|
+
|
|
73
|
+
### 场景 3: 文本变更
|
|
74
|
+
|
|
75
|
+
- Original: `<h1>Hello</h1>`
|
|
76
|
+
- Final: `<h1>World</h1>`
|
|
77
|
+
- 说明: 演示文本内容的变更
|
|
78
|
+
|
|
79
|
+
## 技术实现
|
|
80
|
+
|
|
81
|
+
示例使用:
|
|
82
|
+
- **Vite**: 快速的前端构建工具
|
|
83
|
+
- **React**: UI 框架
|
|
84
|
+
- **TypeScript**: 类型安全
|
|
85
|
+
- **@x-oasis/html-fragment-diff**: 核心库,提供 HTML 片段解析和对比功能
|
|
86
|
+
- **parse5**: HTML 解析库
|
|
87
|
+
|
|
88
|
+
## GitHub Pages 部署
|
|
89
|
+
|
|
90
|
+
示例已配置支持 GitHub Pages 部署。在 `vite.config.ts` 中:
|
|
91
|
+
|
|
92
|
+
- 支持 `GITHUB_PAGES` 环境变量
|
|
93
|
+
- 自动配置 base 路径
|
|
94
|
+
- 支持子路径部署
|
|
95
|
+
|
|
96
|
+
部署时设置环境变量:
|
|
97
|
+
- `GITHUB_PAGES=true`
|
|
98
|
+
- `GITHUB_REPOSITORY=owner/repo-name`
|
|
99
|
+
- `GITHUB_PAGES_PATH=html-fragment-diff`
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>HTML Fragment Diff Example</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "html-fragment-diff-example",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"react": "^18.2.0",
|
|
13
|
+
"react-dom": "^18.2.0",
|
|
14
|
+
"@x-oasis/html-fragment-diff": "workspace:*"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/react": "^18.2.0",
|
|
18
|
+
"@types/react-dom": "^18.2.0",
|
|
19
|
+
"@vitejs/plugin-react": "^4.2.1",
|
|
20
|
+
"vite": "^4.1.4",
|
|
21
|
+
"typescript": "^4.8.3"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "private"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
compareHtmlFragments,
|
|
4
|
+
HtmlFragmentDiff,
|
|
5
|
+
} from '@x-oasis/html-fragment-diff';
|
|
6
|
+
import './index.css';
|
|
7
|
+
|
|
8
|
+
// 默认示例
|
|
9
|
+
const DEFAULT_ORIGINAL = '<h1 class="title primary">Hello World</h1>';
|
|
10
|
+
const DEFAULT_FINAL = '<h1 class="title secondary active">Hello React</h1>';
|
|
11
|
+
|
|
12
|
+
const App: React.FC = () => {
|
|
13
|
+
const [originalFragment, setOriginalFragment] = useState(DEFAULT_ORIGINAL);
|
|
14
|
+
const [finalFragment, setFinalFragment] = useState(DEFAULT_FINAL);
|
|
15
|
+
const [diffResult, setDiffResult] = useState<HtmlFragmentDiff | null>(null);
|
|
16
|
+
const [parseError, setParseError] = useState<string | null>(null);
|
|
17
|
+
|
|
18
|
+
const handleCompare = () => {
|
|
19
|
+
try {
|
|
20
|
+
setParseError(null);
|
|
21
|
+
const result = compareHtmlFragments(originalFragment, finalFragment);
|
|
22
|
+
setDiffResult(result);
|
|
23
|
+
} catch (error: any) {
|
|
24
|
+
setParseError(error.message || '解析失败');
|
|
25
|
+
setDiffResult(null);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const handleReset = () => {
|
|
30
|
+
setOriginalFragment(DEFAULT_ORIGINAL);
|
|
31
|
+
setFinalFragment(DEFAULT_FINAL);
|
|
32
|
+
setDiffResult(null);
|
|
33
|
+
setParseError(null);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className="container">
|
|
38
|
+
<h1>HTML Fragment Diff Example</h1>
|
|
39
|
+
<p className="subtitle">对比两个 HTML 片段,检测 class 增删和文本变更</p>
|
|
40
|
+
|
|
41
|
+
<div className="info-box">
|
|
42
|
+
<strong>使用说明:</strong>
|
|
43
|
+
<ul style={{ marginTop: '8px', marginLeft: '20px' }}>
|
|
44
|
+
<li>左侧输入原始 HTML 片段,右侧输入最终 HTML 片段</li>
|
|
45
|
+
<li>点击"对比"按钮查看 class 和文本的变更</li>
|
|
46
|
+
<li>支持解析第一个根元素的 tagName、classList、textContent 等</li>
|
|
47
|
+
</ul>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<div className="section">
|
|
51
|
+
<div className="section-title">HTML 片段输入</div>
|
|
52
|
+
<div className="file-comparison">
|
|
53
|
+
<div className="file-panel">
|
|
54
|
+
<div className="file-header">原始片段</div>
|
|
55
|
+
<textarea
|
|
56
|
+
className="file-content"
|
|
57
|
+
value={originalFragment}
|
|
58
|
+
onChange={(e) => setOriginalFragment(e.target.value)}
|
|
59
|
+
placeholder="输入原始 HTML 片段,如: <h1 class='title'>Hello</h1>"
|
|
60
|
+
spellCheck={false}
|
|
61
|
+
/>
|
|
62
|
+
</div>
|
|
63
|
+
<div className="file-panel">
|
|
64
|
+
<div className="file-header">最终片段</div>
|
|
65
|
+
<textarea
|
|
66
|
+
className="file-content"
|
|
67
|
+
value={finalFragment}
|
|
68
|
+
onChange={(e) => setFinalFragment(e.target.value)}
|
|
69
|
+
placeholder="输入最终 HTML 片段,如: <h1 class='title active'>World</h1>"
|
|
70
|
+
spellCheck={false}
|
|
71
|
+
/>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
<div style={{ display: 'flex', gap: '10px', marginTop: '10px' }}>
|
|
75
|
+
<button onClick={handleCompare}>对比</button>
|
|
76
|
+
<button
|
|
77
|
+
onClick={handleReset}
|
|
78
|
+
style={{ background: '#6c757d' }}
|
|
79
|
+
onMouseEnter={(e) => {
|
|
80
|
+
e.currentTarget.style.background = '#5a6268';
|
|
81
|
+
}}
|
|
82
|
+
onMouseLeave={(e) => {
|
|
83
|
+
e.currentTarget.style.background = '#6c757d';
|
|
84
|
+
}}
|
|
85
|
+
>
|
|
86
|
+
重置
|
|
87
|
+
</button>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
{parseError && (
|
|
92
|
+
<div
|
|
93
|
+
className="result-panel"
|
|
94
|
+
style={{ borderColor: '#dc3545', background: '#f8d7da' }}
|
|
95
|
+
>
|
|
96
|
+
<div className="result-title" style={{ color: '#721c24' }}>
|
|
97
|
+
解析错误
|
|
98
|
+
</div>
|
|
99
|
+
<div className="result-content" style={{ color: '#721c24' }}>
|
|
100
|
+
{parseError}
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
)}
|
|
104
|
+
|
|
105
|
+
{diffResult && (
|
|
106
|
+
<div className="section">
|
|
107
|
+
<div className="section-title">对比结果</div>
|
|
108
|
+
|
|
109
|
+
{/* 解析结果 */}
|
|
110
|
+
<div className="diff-item">
|
|
111
|
+
<div className="diff-item-title">原始片段解析</div>
|
|
112
|
+
{diffResult.original ? (
|
|
113
|
+
<div style={{ fontSize: '13px', lineHeight: '1.8' }}>
|
|
114
|
+
<div>
|
|
115
|
+
<strong>标签名:</strong> {diffResult.original.tagName}
|
|
116
|
+
</div>
|
|
117
|
+
<div>
|
|
118
|
+
<strong>Class 列表:</strong>{' '}
|
|
119
|
+
{diffResult.original.classList.length > 0 ? (
|
|
120
|
+
diffResult.original.classList.map((cls, idx) => (
|
|
121
|
+
<span key={idx} className="class-added">
|
|
122
|
+
{cls}
|
|
123
|
+
</span>
|
|
124
|
+
))
|
|
125
|
+
) : (
|
|
126
|
+
<span style={{ color: '#999' }}>(无)</span>
|
|
127
|
+
)}
|
|
128
|
+
</div>
|
|
129
|
+
<div>
|
|
130
|
+
<strong>文本内容:</strong>{' '}
|
|
131
|
+
{diffResult.original.textContent || '(空)'}
|
|
132
|
+
</div>
|
|
133
|
+
{Object.keys(diffResult.original.otherAttrs).length > 0 && (
|
|
134
|
+
<div>
|
|
135
|
+
<strong>其他属性:</strong>{' '}
|
|
136
|
+
{JSON.stringify(diffResult.original.otherAttrs)}
|
|
137
|
+
</div>
|
|
138
|
+
)}
|
|
139
|
+
</div>
|
|
140
|
+
) : (
|
|
141
|
+
<div style={{ color: '#999' }}>解析失败</div>
|
|
142
|
+
)}
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<div className="diff-item">
|
|
146
|
+
<div className="diff-item-title">最终片段解析</div>
|
|
147
|
+
{diffResult.final ? (
|
|
148
|
+
<div style={{ fontSize: '13px', lineHeight: '1.8' }}>
|
|
149
|
+
<div>
|
|
150
|
+
<strong>标签名:</strong> {diffResult.final.tagName}
|
|
151
|
+
</div>
|
|
152
|
+
<div>
|
|
153
|
+
<strong>Class 列表:</strong>{' '}
|
|
154
|
+
{diffResult.final.classList.length > 0 ? (
|
|
155
|
+
diffResult.final.classList.map((cls, idx) => (
|
|
156
|
+
<span key={idx} className="class-added">
|
|
157
|
+
{cls}
|
|
158
|
+
</span>
|
|
159
|
+
))
|
|
160
|
+
) : (
|
|
161
|
+
<span style={{ color: '#999' }}>(无)</span>
|
|
162
|
+
)}
|
|
163
|
+
</div>
|
|
164
|
+
<div>
|
|
165
|
+
<strong>文本内容:</strong>{' '}
|
|
166
|
+
{diffResult.final.textContent || '(空)'}
|
|
167
|
+
</div>
|
|
168
|
+
{Object.keys(diffResult.final.otherAttrs).length > 0 && (
|
|
169
|
+
<div>
|
|
170
|
+
<strong>其他属性:</strong>{' '}
|
|
171
|
+
{JSON.stringify(diffResult.final.otherAttrs)}
|
|
172
|
+
</div>
|
|
173
|
+
)}
|
|
174
|
+
</div>
|
|
175
|
+
) : (
|
|
176
|
+
<div style={{ color: '#999' }}>解析失败</div>
|
|
177
|
+
)}
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
{/* Class 变更 */}
|
|
181
|
+
<div className="diff-item">
|
|
182
|
+
<div className="diff-item-title">Class 变更</div>
|
|
183
|
+
<div style={{ fontSize: '13px', lineHeight: '1.8' }}>
|
|
184
|
+
{diffResult.classAdded.length > 0 && (
|
|
185
|
+
<div style={{ marginBottom: '8px' }}>
|
|
186
|
+
<strong style={{ color: '#155724' }}>新增的 Class:</strong>
|
|
187
|
+
<div style={{ marginTop: '4px' }}>
|
|
188
|
+
{diffResult.classAdded.map((cls, idx) => (
|
|
189
|
+
<span key={idx} className="class-added">
|
|
190
|
+
+{cls}
|
|
191
|
+
</span>
|
|
192
|
+
))}
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
)}
|
|
196
|
+
{diffResult.classRemoved.length > 0 && (
|
|
197
|
+
<div style={{ marginBottom: '8px' }}>
|
|
198
|
+
<strong style={{ color: '#721c24' }}>删除的 Class:</strong>
|
|
199
|
+
<div style={{ marginTop: '4px' }}>
|
|
200
|
+
{diffResult.classRemoved.map((cls, idx) => (
|
|
201
|
+
<span key={idx} className="class-removed">
|
|
202
|
+
-{cls}
|
|
203
|
+
</span>
|
|
204
|
+
))}
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
)}
|
|
208
|
+
{diffResult.classAdded.length === 0 &&
|
|
209
|
+
diffResult.classRemoved.length === 0 && (
|
|
210
|
+
<div style={{ color: '#666' }}>Class 无变更</div>
|
|
211
|
+
)}
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
{/* 文本变更 */}
|
|
216
|
+
<div className="diff-item">
|
|
217
|
+
<div className="diff-item-title">文本变更</div>
|
|
218
|
+
<div style={{ fontSize: '13px', lineHeight: '1.8' }}>
|
|
219
|
+
{diffResult.textChanged ? (
|
|
220
|
+
<div>
|
|
221
|
+
<div className="text-changed">{diffResult.textSummary}</div>
|
|
222
|
+
<div style={{ marginTop: '8px' }}>
|
|
223
|
+
<div>
|
|
224
|
+
<strong>原始文本:</strong>{' '}
|
|
225
|
+
{diffResult.textOriginal || '(空)'}
|
|
226
|
+
</div>
|
|
227
|
+
<div>
|
|
228
|
+
<strong>最终文本:</strong>{' '}
|
|
229
|
+
{diffResult.textFinal || '(空)'}
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
) : (
|
|
234
|
+
<div className="text-unchanged">文本无变更</div>
|
|
235
|
+
)}
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
|
|
239
|
+
{/* JSON 输出 */}
|
|
240
|
+
<div className="result-panel">
|
|
241
|
+
<div className="result-title">完整 JSON 结果</div>
|
|
242
|
+
<div className="result-content">
|
|
243
|
+
<pre>{JSON.stringify(diffResult, null, 2)}</pre>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
</div>
|
|
247
|
+
)}
|
|
248
|
+
</div>
|
|
249
|
+
);
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
export default App;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
* {
|
|
2
|
+
margin: 0;
|
|
3
|
+
padding: 0;
|
|
4
|
+
box-sizing: border-box;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
body {
|
|
8
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
9
|
+
background: #f5f5f5;
|
|
10
|
+
padding: 20px;
|
|
11
|
+
line-height: 1.6;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.container {
|
|
15
|
+
max-width: 1400px;
|
|
16
|
+
margin: 0 auto;
|
|
17
|
+
background: white;
|
|
18
|
+
border-radius: 8px;
|
|
19
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
20
|
+
padding: 30px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
h1 {
|
|
24
|
+
color: #333;
|
|
25
|
+
margin-bottom: 10px;
|
|
26
|
+
font-size: 28px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.subtitle {
|
|
30
|
+
color: #666;
|
|
31
|
+
margin-bottom: 30px;
|
|
32
|
+
font-size: 14px;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.section {
|
|
36
|
+
margin-bottom: 30px;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.section-title {
|
|
40
|
+
font-size: 18px;
|
|
41
|
+
font-weight: 600;
|
|
42
|
+
color: #333;
|
|
43
|
+
margin-bottom: 15px;
|
|
44
|
+
padding-bottom: 10px;
|
|
45
|
+
border-bottom: 2px solid #e0e0e0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.file-comparison {
|
|
49
|
+
display: grid;
|
|
50
|
+
grid-template-columns: 1fr 1fr;
|
|
51
|
+
gap: 20px;
|
|
52
|
+
margin-bottom: 20px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.file-panel {
|
|
56
|
+
border: 1px solid #ddd;
|
|
57
|
+
border-radius: 4px;
|
|
58
|
+
overflow: hidden;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.file-header {
|
|
62
|
+
background: #f8f8f8;
|
|
63
|
+
padding: 10px 15px;
|
|
64
|
+
font-weight: 600;
|
|
65
|
+
color: #555;
|
|
66
|
+
border-bottom: 1px solid #ddd;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.file-content {
|
|
70
|
+
padding: 15px;
|
|
71
|
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
72
|
+
font-size: 13px;
|
|
73
|
+
line-height: 1.8;
|
|
74
|
+
white-space: pre-wrap;
|
|
75
|
+
word-wrap: break-word;
|
|
76
|
+
max-height: 400px;
|
|
77
|
+
overflow-y: auto;
|
|
78
|
+
background: #fff;
|
|
79
|
+
resize: vertical;
|
|
80
|
+
min-height: 200px;
|
|
81
|
+
width: 100%;
|
|
82
|
+
border: none;
|
|
83
|
+
outline: none;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.file-content:focus {
|
|
87
|
+
outline: none;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
button {
|
|
91
|
+
padding: 10px 20px;
|
|
92
|
+
background: #4a90e2;
|
|
93
|
+
color: white;
|
|
94
|
+
border: none;
|
|
95
|
+
border-radius: 4px;
|
|
96
|
+
font-size: 14px;
|
|
97
|
+
font-weight: 600;
|
|
98
|
+
cursor: pointer;
|
|
99
|
+
transition: background 0.2s;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
button:hover {
|
|
103
|
+
background: #357abd;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
button:active {
|
|
107
|
+
transform: translateY(1px);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.result-panel {
|
|
111
|
+
border: 2px solid #4a90e2;
|
|
112
|
+
border-radius: 4px;
|
|
113
|
+
padding: 15px;
|
|
114
|
+
background: #f0f7ff;
|
|
115
|
+
margin-top: 20px;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.result-title {
|
|
119
|
+
font-weight: 600;
|
|
120
|
+
color: #4a90e2;
|
|
121
|
+
margin-bottom: 10px;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.result-content {
|
|
125
|
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
126
|
+
font-size: 13px;
|
|
127
|
+
line-height: 1.8;
|
|
128
|
+
white-space: pre-wrap;
|
|
129
|
+
word-wrap: break-word;
|
|
130
|
+
background: white;
|
|
131
|
+
padding: 15px;
|
|
132
|
+
border-radius: 4px;
|
|
133
|
+
border: 1px solid #ddd;
|
|
134
|
+
max-height: 300px;
|
|
135
|
+
overflow-y: auto;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.info-box {
|
|
139
|
+
background: #e7f3ff;
|
|
140
|
+
border-left: 4px solid #4a90e2;
|
|
141
|
+
padding: 12px;
|
|
142
|
+
margin-bottom: 20px;
|
|
143
|
+
border-radius: 4px;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.info-box strong {
|
|
147
|
+
color: #4a90e2;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.diff-item {
|
|
151
|
+
margin-bottom: 15px;
|
|
152
|
+
padding: 12px;
|
|
153
|
+
background: #f6f8fa;
|
|
154
|
+
border-radius: 4px;
|
|
155
|
+
border: 1px solid #d1d9e0;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.diff-item-title {
|
|
159
|
+
font-weight: 600;
|
|
160
|
+
color: #0969da;
|
|
161
|
+
margin-bottom: 8px;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.class-added {
|
|
165
|
+
color: #155724;
|
|
166
|
+
background: #d4edda;
|
|
167
|
+
padding: 2px 6px;
|
|
168
|
+
border-radius: 3px;
|
|
169
|
+
margin-right: 5px;
|
|
170
|
+
font-size: 12px;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.class-removed {
|
|
174
|
+
color: #721c24;
|
|
175
|
+
background: #f8d7da;
|
|
176
|
+
padding: 2px 6px;
|
|
177
|
+
border-radius: 3px;
|
|
178
|
+
margin-right: 5px;
|
|
179
|
+
font-size: 12px;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.text-changed {
|
|
183
|
+
color: #856404;
|
|
184
|
+
background: #fff3cd;
|
|
185
|
+
padding: 2px 6px;
|
|
186
|
+
border-radius: 3px;
|
|
187
|
+
font-size: 12px;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.text-unchanged {
|
|
191
|
+
color: #666;
|
|
192
|
+
font-size: 12px;
|
|
193
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"allowImportingTsExtensions": true,
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"jsx": "react-jsx",
|
|
14
|
+
"strict": true,
|
|
15
|
+
"noUnusedLocals": true,
|
|
16
|
+
"noUnusedParameters": true,
|
|
17
|
+
"noFallthroughCasesInSwitch": true,
|
|
18
|
+
"baseUrl": ".",
|
|
19
|
+
"paths": {
|
|
20
|
+
"@x-oasis/html-fragment-diff": ["../src/index.ts"]
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"include": ["src"],
|
|
24
|
+
"references": [{ "path": "./tsconfig.node.json" }]
|
|
25
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import react from '@vitejs/plugin-react';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
// 如果设置了 GITHUB_PAGES 环境变量,使用仓库名和子路径作为 base 路径
|
|
6
|
+
// 格式: owner/repo-name,我们只需要 repo-name
|
|
7
|
+
// 子路径从 GITHUB_PAGES_PATH 环境变量获取,例如: html-fragment-diff
|
|
8
|
+
const getBasePath = () => {
|
|
9
|
+
if (process.env.GITHUB_PAGES === 'true') {
|
|
10
|
+
const repo = process.env.GITHUB_REPOSITORY || 'red-armor/x-oasis';
|
|
11
|
+
const repoName = repo.split('/')[1];
|
|
12
|
+
const subPath = process.env.GITHUB_PAGES_PATH || 'html-fragment-diff';
|
|
13
|
+
return `/${repoName}/${subPath}/`;
|
|
14
|
+
}
|
|
15
|
+
return '/';
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default defineConfig({
|
|
19
|
+
base: getBasePath(),
|
|
20
|
+
plugins: [react()],
|
|
21
|
+
resolve: {
|
|
22
|
+
alias: {
|
|
23
|
+
'@x-oasis/html-fragment-diff': path.resolve(__dirname, '../src/index.ts'),
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
server: {
|
|
27
|
+
port: 3001,
|
|
28
|
+
open: true,
|
|
29
|
+
},
|
|
30
|
+
});
|