Skip to content

Commit 9c46a9a

Browse files
committed
Use a modal to compare diff
1 parent 25508c3 commit 9c46a9a

File tree

1 file changed

+203
-91
lines changed

1 file changed

+203
-91
lines changed

src/Plugins/ZKEACMS.AuditTrail/Views/AuditTrail/History.cshtml

Lines changed: 203 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
background: #fff;
1313
}
1414
15+
body .pagination {
16+
margin: 10px 0 0;
17+
}
18+
1519
.diff-added {
1620
background-color: #d4edda;
1721
color: #155724;
@@ -30,27 +34,73 @@
3034
.new-value-cell {
3135
overflow: hidden;
3236
text-overflow: ellipsis;
33-
max-width: 400px;
37+
max-width: 100%;
3438
white-space: nowrap;
3539
}
3640
37-
tr.open .old-value-cell,
38-
tr.open .new-value-cell {
39-
overflow: visible;
40-
white-space: normal;
41+
tr {
42+
cursor: pointer;
43+
}
44+
45+
.modal-overlay {
46+
position: fixed;
47+
top: 0;
48+
left: 0;
49+
width: 100%;
50+
height: 100%;
51+
background-color: #fff;
52+
display: none;
53+
z-index: 1000;
54+
opacity: 0;
55+
transition: opacity 0.3s ease;
56+
}
57+
58+
.modal-overlay.active {
59+
opacity: 1;
60+
}
61+
62+
.modal-head {
63+
margin-bottom: 5px;
64+
}
65+
66+
.modal-container {
67+
position: absolute;
68+
top: 0;
69+
left: 0;
70+
width: 100%;
71+
height: 100%;
72+
display: flex;
73+
flex-direction: column;
74+
padding: 10px;
75+
}
76+
77+
.two-columns {
78+
display: flex;
79+
flex: 1;
80+
overflow: hidden;
81+
gap: 10px;
82+
}
83+
84+
.column {
85+
flex: 1;
86+
overflow-y: auto;
87+
padding: 10px;
88+
background-color: #f5f5f5;
89+
border-radius: 12px;
90+
border: 1px solid #ccc;
4191
}
4292
</style>
4393
}
4494
<div style="background:#fff">
4595
<div class="table-responsive">
46-
<table class="table table-striped table-bordered table-hover" style="margin:0">
96+
<table class="table table-striped table-bordered table-hover" style="margin:0; table-layout: fixed;">
4797
<thead>
4898
<tr>
49-
<th>@L("ContentType@Fields")</th>
99+
<th style="width: 120px;">@L("ContentType@Fields")</th>
50100
<th>@L("Old Value")</th>
51101
<th>@L("New Value")</th>
52-
<th>@L("LastUpdateByName")</th>
53-
<th>@L("Last-Modified")</th>
102+
<th style="width: 140px;">@L("LastUpdateByName")</th>
103+
<th style="width: 155px;">@L("Last-Modified")</th>
54104
</tr>
55105
</thead>
56106
<tbody>
@@ -63,10 +113,7 @@
63113
<tr>
64114
<td>@change.Field</td>
65115
<td>
66-
@if (change.OldValue != null)
67-
{
68-
<div class="old-value-cell">@change.OldValue</div>
69-
}
116+
<div class="old-value-cell text-muted">@change.OldValue</div>
70117
</td>
71118
<td>
72119
@if (change.ChangeType == (int)Easy.AuditTrail.AuditChangeType.Added)
@@ -85,7 +132,7 @@
85132
}
86133
else
87134
{
88-
<div class="new-value-cell">@change.NewValue</div>
135+
<div class="new-value-cell text-success">@change.NewValue</div>
89136
}
90137
</td>
91138
@if (i == 0)
@@ -104,116 +151,181 @@
104151
await Html.Pagin((ZKEACMS.Pagin)ViewBag.Pagin);
105152
}
106153
</div>
154+
<div id="modalOverlay" class="modal-overlay">
155+
<div class="modal-container">
156+
<div class="modal-head">
157+
<code class="small">ESC</code>
158+
<button id="closeModalBtn" type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
159+
</div>
160+
<div class="two-columns">
161+
<div id="leftColumn" class="column"></div>
162+
<div id="rightColumn" class="column"></div>
163+
</div>
164+
</div>
165+
</div>
107166
@using (Script.AtFoot())
108167
{
109168
<script type="text/javascript">
110-
function diffText(oldText, newText) {
111-
const oldTokens = tokenizeText(oldText);
112-
const newTokens = tokenizeText(newText);
169+
$(function () {
170+
function diffText(oldText, newText) {
171+
const oldTokens = tokenizeText(oldText);
172+
const newTokens = tokenizeText(newText);
113173

114-
const m = oldTokens.length;
115-
const n = newTokens.length;
174+
const m = oldTokens.length;
175+
const n = newTokens.length;
116176

117-
const dp = Array(m + 1).fill().map(() => Array(n + 1).fill(0));
177+
const dp = Array(m + 1).fill().map(() => Array(n + 1).fill(0));
118178

119-
for (let i = 1; i <= m; i++) {
120-
for (let j = 1; j <= n; j++) {
121-
if (oldTokens[i - 1] === newTokens[j - 1]) {
122-
dp[i][j] = dp[i - 1][j - 1] + 1;
123-
} else {
124-
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
179+
for (let i = 1; i <= m; i++) {
180+
for (let j = 1; j <= n; j++) {
181+
if (oldTokens[i - 1] === newTokens[j - 1]) {
182+
dp[i][j] = dp[i - 1][j - 1] + 1;
183+
} else {
184+
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
185+
}
125186
}
126187
}
127-
}
128188

129-
let i = m, j = n;
130-
const oldResult = [];
131-
const newResult = [];
132-
133-
while (i > 0 || j > 0) {
134-
if (i > 0 && j > 0 && oldTokens[i - 1] === newTokens[j - 1]) {
135-
oldResult.unshift({ text: oldTokens[i - 1], type: 'same' });
136-
newResult.unshift({ text: newTokens[j - 1], type: 'same' });
137-
i--;
138-
j--;
139-
} else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
140-
newResult.unshift({ text: newTokens[j - 1], type: 'added' });
141-
j--;
142-
} else if (i > 0 && (j === 0 || dp[i][j - 1] < dp[i - 1][j])) {
143-
oldResult.unshift({ text: oldTokens[i - 1], type: 'removed' });
144-
i--;
189+
let i = m, j = n;
190+
const oldResult = [];
191+
const newResult = [];
192+
193+
while (i > 0 || j > 0) {
194+
if (i > 0 && j > 0 && oldTokens[i - 1] === newTokens[j - 1]) {
195+
oldResult.unshift({ text: oldTokens[i - 1], type: 'same' });
196+
newResult.unshift({ text: newTokens[j - 1], type: 'same' });
197+
i--;
198+
j--;
199+
} else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
200+
newResult.unshift({ text: newTokens[j - 1], type: 'added' });
201+
j--;
202+
} else if (i > 0 && (j === 0 || dp[i][j - 1] < dp[i - 1][j])) {
203+
oldResult.unshift({ text: oldTokens[i - 1], type: 'removed' });
204+
i--;
205+
}
145206
}
207+
208+
return {
209+
oldResult: mergeAdjacentTokens(oldResult),
210+
newResult: mergeAdjacentTokens(newResult)
211+
};
146212
}
147213

148-
return {
149-
oldResult: mergeAdjacentTokens(oldResult),
150-
newResult: mergeAdjacentTokens(newResult)
151-
};
152-
}
214+
function tokenizeText(text) {
215+
const tokenRegex = /(\s+|[^\w\s]|\w+)/g;
216+
const tokens = [];
217+
let match;
153218

154-
function tokenizeText(text) {
155-
const tokenRegex = /(\s+|[^\w\s]|\w+)/g;
156-
const tokens = [];
157-
let match;
219+
while ((match = tokenRegex.exec(text)) !== null) {
220+
tokens.push(match[0]);
221+
}
158222

159-
while ((match = tokenRegex.exec(text)) !== null) {
160-
tokens.push(match[0]);
223+
return tokens;
161224
}
162225

163-
return tokens;
164-
}
165-
166-
function mergeAdjacentTokens(tokens) {
167-
if (tokens.length === 0) return [];
226+
function mergeAdjacentTokens(tokens) {
227+
if (tokens.length === 0) return [];
168228

169-
const result = [];
170-
let currentToken = { ...tokens[0] };
229+
const result = [];
230+
let currentToken = { ...tokens[0] };
171231

172-
for (let i = 1; i < tokens.length; i++) {
173-
if (tokens[i].type === currentToken.type) {
174-
currentToken.text += tokens[i].text;
175-
} else {
176-
result.push(currentToken);
177-
currentToken = { ...tokens[i] };
232+
for (let i = 1; i < tokens.length; i++) {
233+
if (tokens[i].type === currentToken.type) {
234+
currentToken.text += tokens[i].text;
235+
} else {
236+
result.push(currentToken);
237+
currentToken = { ...tokens[i] };
238+
}
178239
}
240+
241+
result.push(currentToken);
242+
return result;
179243
}
180244

181-
result.push(currentToken);
182-
return result;
183-
}
245+
function tokensToHtml(tokens) {
246+
return tokens.map(token => {
247+
let className = '';
248+
switch (token.type) {
249+
case 'added':
250+
return `<span class="diff-added">${token.text}</span>`;
251+
case 'removed':
252+
return `<del class="diff-removed">${token.text}</del>`;
253+
case 'same':
254+
return `<span>${token.text}</span>`;
255+
default:
256+
return `<span>${token.text}</span>`;
257+
}
258+
}).join('');
259+
}
260+
261+
const openModalBtn = document.getElementById('openModalBtn');
262+
const closeModalBtn = document.getElementById('closeModalBtn');
263+
const modalOverlay = document.getElementById('modalOverlay');
264+
const leftColumn = document.getElementById('leftColumn');
265+
const rightColumn = document.getElementById('rightColumn');
266+
let isSyncingScroll = false;
267+
268+
const openModal = () => {
269+
modalOverlay.style.display = 'block';
270+
271+
requestAnimationFrame(() => {
272+
modalOverlay.classList.add('active');
273+
});
274+
leftColumn.scrollTop = 0;
275+
rightColumn.scrollTop = 0;
276+
};
184277

185-
function tokensToHtml(tokens) {
186-
return tokens.map(token => {
187-
let className = '';
188-
switch (token.type) {
189-
case 'added':
190-
return `<span class="diff-added">${token.text}</span>`;
191-
case 'removed':
192-
return `<span class="diff-removed">${token.text}</span>`;
193-
case 'same':
194-
return `<span>${token.text}</span>`;
195-
default:
196-
return `<span>${token.text}</span>`;
278+
const closeModal = () => {
279+
modalOverlay.classList.remove('active');
280+
setTimeout(() => {
281+
modalOverlay.style.display = 'none';
282+
}, 300);
283+
};
284+
285+
closeModalBtn.addEventListener('click', closeModal);
286+
modalOverlay.addEventListener('click', (e) => {
287+
if (e.target === modalOverlay) {
288+
closeModal();
197289
}
198-
}).join('');
199-
}
290+
});
200291

201-
$(function () {
202-
$("tr").click(function () {
203-
$(this).toggleClass("open");
292+
document.addEventListener('keydown', (e) => {
293+
if (e.key === 'Escape' && modalOverlay.style.display === 'block') {
294+
closeModal();
295+
}
204296
});
205-
$('tr').each(function () {
297+
const syncScroll = (source, target) => {
298+
if (isSyncingScroll) return;
299+
isSyncingScroll = true;
300+
const scrollRatio = source.scrollTop / (source.scrollHeight - source.clientHeight);
301+
target.scrollTop = scrollRatio * (target.scrollHeight - target.clientHeight);
302+
setTimeout(() => {
303+
isSyncingScroll = false;
304+
}, 10);
305+
};
306+
307+
leftColumn.addEventListener('scroll', () => {
308+
syncScroll(leftColumn, rightColumn);
309+
});
310+
311+
rightColumn.addEventListener('scroll', () => {
312+
syncScroll(rightColumn, leftColumn);
313+
});
314+
315+
$('tr').click(function () {
206316
const oldValueCell = $(this).find('.old-value-cell');
207317
const newValueCell = $(this).find('.new-value-cell');
208318

209319
const oldText = $.trim(oldValueCell.html());
210320
const newText = $.trim(newValueCell.html());
211-
if (oldText && newText) {
321+
if (oldText || newText) {
322+
openModal();
212323
const diffResult = diffText(oldText, newText);
213-
newValueCell.html(tokensToHtml(diffResult.newResult));
214-
oldValueCell.html(tokensToHtml(diffResult.oldResult));
324+
leftColumn.innerHTML = tokensToHtml(diffResult.oldResult);
325+
rightColumn.innerHTML = tokensToHtml(diffResult.newResult);
215326
}
216327
});
328+
217329
});
218330
</script>
219331
}

0 commit comments

Comments
 (0)