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 ;
3034 .new-value-cell {
3135 overflow : hidden ;
3236 text-overflow : ellipsis ;
33- max-width : 400 px ;
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 : 120 px ; " >@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 : 140 px ; " >@L( "LastUpdateByName")</th >
103+ <th style = " width : 155 px ; " >@L( "Last-Modified")</th >
54104 </tr >
55105 </thead >
56106 <tbody >
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 )
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 )
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