Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | Enhance the various diff views (excluding diff -b output) to be able to dynamically load more context lines per mouse click. |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA3-256: |
51c1efd403db412f5aed8ffa99ce0775 |
User & Date: | stephan 2021-09-11 17:09:55 |
Context
2021-09-11
| ||
17:28 | Improved alignment by giving a score of 62 to an inserted or deleted line. ... (check-in: 2bded9fb user: drh tags: trunk) | |
17:09 | Enhance the various diff views (excluding diff -b output) to be able to dynamically load more context lines per mouse click. ... (check-in: 51c1efd4 user: stephan tags: trunk) | |
15:53 | Merge the diff alignment scoring tweak from trunk. ... (Closed-Leaf check-in: 916094f8 user: drh tags: diff-js-refactoring) | |
15:52 | Tweak the diff-alignment scoring algorithm to give extra affinity to lines that share a common prefix. ... (check-in: 2921ec25 user: drh tags: trunk) | |
Changes
Changes to skins/bootstrap/css.txt.
︙ | ︙ | |||
4398 4399 4400 4401 4402 4403 4404 | height: 4px; background: #000; } body.branch .submenu > a.timeline-link { color: black; } | > > > > > > > > > > > > > > > > > > > > > > > > | 4398 4399 4400 4401 4402 4403 4404 4405 4406 4407 4408 4409 4410 4411 4412 4413 4414 4415 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 | height: 4px; background: #000; } body.branch .submenu > a.timeline-link { color: black; } tr.diffskip > td.chunkctrl .jcbutton { min-width: 3.5ex; max-width: revert; } /* Bootstrap installs a 'table' class on tables which causes its styles to be more specific matches than our diff tables, so we have to fight that fire with more fire... */ table.diff.table>thead>tr>th, table.diff.table>tbody>tr>th, table.diff.table>tfoot>tr>th, table.diff.table>thead>tr>td, table.diff.table>tbody>tr>td, table.diff.table>tfoot>tr>td { padding: 0; line-height: revert; vertical-align: top; border-top: none; } table.diff tr.diffskip.jchunk > td { padding: 0.25em 0.5em; } table.diff pre { border: none; word-wrap: initial; } |
Changes to skins/darkmode/css.txt.
︙ | ︙ | |||
477 478 479 480 481 482 483 | text-decoration: none; } td.difftxt ins > ins { background-color: #559855; color: #000; text-decoration: none; } | > > | > > > | 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 | text-decoration: none; } td.difftxt ins > ins { background-color: #559855; color: #000; text-decoration: none; } tr.diffskip.jchunk { background-color: black; } tr.diffskip > td.chunkctrl .jcbutton { background-color: #303536; } /************************************************************************ ************************************************************************/ body.wikiedit #fossil-status-bar, body.fileedit #fossil-status-bar{ border-radius: 0.25em 0.25em 0 0; } |
︙ | ︙ |
Changes to skins/eagle/css.txt.
︙ | ︙ | |||
261 262 263 264 265 266 267 | /* Side-by-side diff */ table.splitdiff { background-color: #485D7B; font-family: fixed, Dejavu Sans Mono, Monaco, Lucida Console, monospace; font-size: 8pt; border-collapse:collapse; white-space: pre; | < | 261 262 263 264 265 266 267 268 269 270 271 272 273 274 | /* Side-by-side diff */ table.splitdiff { background-color: #485D7B; font-family: fixed, Dejavu Sans Mono, Monaco, Lucida Console, monospace; font-size: 8pt; border-collapse:collapse; white-space: pre; border: 1px #000 dashed; margin-left: auto; margin-right: auto; } /* format for the layout table, used for the captcha display */ table.captcha { |
︙ | ︙ | |||
345 346 347 348 349 350 351 | /* deleted in a diff */ td.difftxt del > del, td.diffln del { background-color: rgb(230, 110, 110); } td.difftxt del { background-color: inherit; } | > > | > > > > > | | 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 | /* deleted in a diff */ td.difftxt del > del, td.diffln del { background-color: rgb(230, 110, 110); } td.difftxt del { background-color: inherit; } tr.diffskip.jchunk { background-color: #7EA2D9; } tr.diffskip > td.chunkctrl .jcbutton{ color: white; background-color: #485D7B; } .fileage tr:hover { background-color: #7EA2D9; } span.modpending { color: #c0c0c0; font-style: italic; } span.forum_author { |
︙ | ︙ |
Changes to skins/khaki/css.txt.
︙ | ︙ | |||
168 169 170 171 172 173 174 | div.forumPostBody blockquote { border-width: 1pt; border-radius: 0.25em; border-style: solid; padding: 0 0.5em; } | > > > > > > > > > | 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | div.forumPostBody blockquote { border-width: 1pt; border-radius: 0.25em; border-style: solid; padding: 0 0.5em; } tr.diffskip > td.chunkctrl .jcbutton { color: white; background-color: #a09048; } tr.diffskip.jchunk { background-color: #c0af58; } |
Changes to skins/xekri/css.txt.
︙ | ︙ | |||
261 262 263 264 265 266 267 268 269 270 271 272 273 274 | padding: 0.1rem 1rem; } /************************************** * Diffs */ /* Code Added */ td.diffln ins, td.difftxt ins > ins { background-color: #7f7; color: #000; } | > > > > > > > | 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 | padding: 0.1rem 1rem; } /************************************** * Diffs */ tr.diffskip.jchunk { background-color: black; } tr.diffskip > td.chunkctrl .jcbutton { background-color: #303536; } /* Code Added */ td.diffln ins, td.difftxt ins > ins { background-color: #7f7; color: #000; } |
︙ | ︙ | |||
291 292 293 294 295 296 297 | * Diffs : Side-By-Side */ /* display (column-based) */ table.splitdiff { border-spacing: 0; font-size: 0.85rem; | < | 298 299 300 301 302 303 304 305 306 307 308 309 310 311 | * Diffs : Side-By-Side */ /* display (column-based) */ table.splitdiff { border-spacing: 0; font-size: 0.85rem; } table.splitdiff pre { border: 0; margin: 0 0.5em; padding: 0; } |
︙ | ︙ |
Changes to src/builtin.c.
︙ | ︙ | |||
621 622 623 624 625 626 627 628 629 630 631 632 633 634 | fossil_free(zName); zName = db_get("project-code", ""); CX("projectCode: %!j,\n", zName); fossil_free(zName); CX("/* Length of UUID hashes for display purposes. */"); CX("hashDigits: %d, hashDigitsUrl: %d,\n", hash_digits(0), hash_digits(1)); CX("editStateMarkers: {" "/*Symbolic markers to denote certain edit states.*/" "isNew:'[+]', isModified:'[*]', isDeleted:'[-]'},\n"); CX("confirmerButtonTicks: 3 " "/*default fossil.confirmer tick count.*/,\n"); /* Inject certain info about the current skin... */ CX("skin:{"); | > > | 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 | fossil_free(zName); zName = db_get("project-code", ""); CX("projectCode: %!j,\n", zName); fossil_free(zName); CX("/* Length of UUID hashes for display purposes. */"); CX("hashDigits: %d, hashDigitsUrl: %d,\n", hash_digits(0), hash_digits(1)); CX("diffContextLines: %d,\n", diff_context_lines(0)); CX("editStateMarkers: {" "/*Symbolic markers to denote certain edit states.*/" "isNew:'[+]', isModified:'[*]', isDeleted:'[-]'},\n"); CX("confirmerButtonTicks: 3 " "/*default fossil.confirmer tick count.*/,\n"); /* Inject certain info about the current skin... */ CX("skin:{"); |
︙ | ︙ | |||
700 701 702 703 704 705 706 707 708 709 710 711 712 713 | ** entries: all known deps of this one. Each ** REQUIRES an EXPLICIT trailing \0, including ** the final one! */ } fjs[] = { /* This list ordering isn't strictly important. */ {"confirmer", 0, 0}, {"copybutton", 0, "dom\0"}, {"dom", 0, 0}, {"fetch", 0, 0}, {"info-diff", 0, "dom\0"}, {"numbered-lines", 0, "popupwidget\0copybutton\0"}, {"pikchr", 0, "dom\0"}, {"popupwidget", 0, "dom\0"}, {"storage", 0, 0}, | > | 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 | ** entries: all known deps of this one. Each ** REQUIRES an EXPLICIT trailing \0, including ** the final one! */ } fjs[] = { /* This list ordering isn't strictly important. */ {"confirmer", 0, 0}, {"copybutton", 0, "dom\0"}, {"diff", 0, "dom\0fetch\0popupwidget\0"}, {"dom", 0, 0}, {"fetch", 0, 0}, {"info-diff", 0, "dom\0"}, {"numbered-lines", 0, "popupwidget\0copybutton\0"}, {"pikchr", 0, "dom\0"}, {"popupwidget", 0, "dom\0"}, {"storage", 0, 0}, |
︙ | ︙ |
Changes to src/default.css.
︙ | ︙ | |||
530 531 532 533 534 535 536 | } ul.filelist li { padding-top: 1px; } /* Rules governing diff layout and colors */ table.diff { | | > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 | } ul.filelist li { padding-top: 1px; } /* Rules governing diff layout and colors */ table.diff { width: 100%; border-spacing: 0; border: 1px solid black; } table.diff td.diffln{ padding: 0; } table.diff td.diffln > pre{ padding: 0 0.25em 0 0.5em; margin: 0; } table.diff td { vertical-align: top; padding: 0; overflow: hidden /*work around inner PRE slight overflow/overlap*/; } table.diff pre { margin: 0 0 0 0; padding: 0 0.5em; } table.diff td.diffln > pre { padding: 0 0.35em 0 0.5em; } table.diff td.difftxt > pre { min-width: 100%; max-width: 100%; } tr.diffskip.jchunk { /* jchunk gets added from JS to diffskip rows when they are plugged into the /jchunk route. */ background-color: aliceblue; padding: 0; } tr.diffskip.jchunk > td { padding: 0.25em 0.5em; margin: 0; } tr.diffskip.jchunk:hover { /*background-color: rgba(127,127,127,0.5); cursor: pointer;*/ } tr.diffskip > td.chunkctrl { text-align: left; font-family: monospace; } tr.diffskip > td.chunkctrl > div { /* Exists solely for layout purposes. */ } tr.diffskip > td.chunkctrl .jcbutton /* class name .button breaks w/ some skins! */ { min-width: 3.5ex; max-width: 3.5ex; text-align: center; display: inline-block; padding: 0.1em 1em; margin: 0 1em 0 0; background-color: rgba(127,127,127,0.2); border-style: outset; border-width: 0; border-radius: 0.5em; opacity: 0.7; } tr.diffskip > td.chunkctrl .jcbutton.up:not(.down){ /* Simulate an arrow pointing up */ border-radius: 3em 3em 0.25em 0.25em; } tr.diffskip > td.chunkctrl .jcbutton.down:not(.up){ /* Simulate an arrow pointing down */ border-radius: 0.25em 0.25em 3em 3em; } tr.diffskip > td.chunkctrl .jcbutton > span { /* In order to increase the glyph size w/o increasing the em-based button size or border-radius, we need an extra layer of DOM element for the glyph. */ font-size: 150%; } tr.diffskip > td.chunkctrl .jcbutton.up:not(.down) > span::before { content: '⇡'; } tr.diffskip > td.chunkctrl .jcbutton.down:not(.up) > span::before { content: '⇣'; } tr.diffskip > td.chunkctrl .jcbutton.up.down > span::before { content: '⇡⇣'; } tr.diffskip > td.chunkctrl .jcbutton:hover { cursor: pointer; opacity: 1; filter: contrast(1); } td.diffln { width: 1px; text-align: right; padding: 0 1em 0 0; } td.difflne { padding-bottom: 0.4em; } td.diffsep { width: 1px; padding: 0 0.3em 0 0.5em; } td.difftxt pre { overflow-x: auto; } td.diffln ins { background-color: #a0e4b2; text-decoration: none; |
︙ | ︙ |
Changes to src/diff.c.
︙ | ︙ | |||
2594 2595 2596 2597 2598 2599 2600 | lnFrom += del; lnTo += ins; } } /* ** Extract the number of lines of context from diffFlags. Supply an | | > > > > | | | > > > | 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 | lnFrom += del; lnTo += ins; } } /* ** Extract the number of lines of context from diffFlags. Supply an ** appropriate default if no context width is specified. If pCfg is ** NULL then the compile-time default is used (which gets propagated ** to JS-side state by certain pages). */ int diff_context_lines(DiffConfig *pCfg){ const int dflt = 5; if(pCfg!=0){ int n = pCfg->nContext; if( n<=0 && (pCfg->diffFlags & DIFF_CONTEXT_EX)==0 ) n = dflt; return n; }else{ return dflt; } } /* ** Extract the width of columns for side-by-side diff. Supply an ** appropriate default if no width is given. ** ** Calculate the default automatically, based on terminal's current width: |
︙ | ︙ |
Changes to src/diff.js.
1 2 3 4 5 6 7 8 9 | /* Refinements to the display of unified and side-by-side diffs. ** ** In all cases, the table columns tagged with "difftxt" are expanded, ** where possible, to fill the width of the screen. ** ** For a side-by-side diff, if either column is two wide to fit on the ** display, scrollbars are added. The scrollbars are linked, so that ** both sides scroll together. Left and right arrows also scroll. */ | | > | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | /* Refinements to the display of unified and side-by-side diffs. ** ** In all cases, the table columns tagged with "difftxt" are expanded, ** where possible, to fill the width of the screen. ** ** For a side-by-side diff, if either column is two wide to fit on the ** display, scrollbars are added. The scrollbars are linked, so that ** both sides scroll together. Left and right arrows also scroll. */ window.addEventListener('load',function(){ var SCROLL_LEN = 25; function initDiff(diff){ var txtCols = diff.querySelectorAll('td.difftxt'); var txtPres = diff.querySelectorAll('td.difftxt pre'); var width = 0; if(txtPres.length>=2){ width = Math.max(txtPres[0].scrollWidth, txtPres[1].scrollWidth); } var i; for(i=0; i<txtCols.length; i++){ txtCols[i].style.width = width + 'px'; txtPres[i].style.maxWidth = width + 'px'; txtPres[i].style.width = width + 'px'; txtPres[i].onscroll = function(e){ for(var j=0; j<txtPres.length; j++) txtPres[j].scrollLeft = this.scrollLeft; |
︙ | ︙ | |||
32 33 34 35 36 37 38 | return false; }; } var i, diffs = document.querySelectorAll('table.splitdiff') for(i=0; i<diffs.length; i++){ initDiff(diffs[i]); } | < | > | < | < < | | | > | > | | | | > | > | | | | > | > | | | | | | < | | 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | return false; }; } var i, diffs = document.querySelectorAll('table.splitdiff') for(i=0; i<diffs.length; i++){ initDiff(diffs[i]); } const checkWidth = function f(){ if(undefined === f.lastWidth){ f.lastWidth = 0; } if( document.body.clientWidth===f.lastWidth ) return; f.lastWidth = document.body.clientWidth; var w = f.lastWidth*0.5 - 100; if(!f.colsL){ f.colsL = document.querySelectorAll('td.difftxtl pre'); } for(let i=0; i<f.colsL.length; i++){ f.colsL[i].style.width = w + "px"; f.colsL[i].style.maxWidth = w + "px"; } if(!f.colsR){ f.colsR = document.querySelectorAll('td.difftxtr pre'); } for(let i=0; i<f.colsR.length; i++){ f.colsR[i].style.width = w + "px"; f.colsR[i].style.maxWidth = w + "px"; } if(!f.allDiffs){ f.allDiffs = document.querySelectorAll('table.diff'); } w = f.lastWidth; for(let i=0; i<f.allDiffs.length; i++){ f.allDiffs[i].style.width = '100%'; // setting to w causes unsightly horiz. scrollbar f.allDiffs[i].style.maxWidth = w + "px"; } }; checkWidth(); window.addEventListener('resize', checkWidth); }, false); |
Changes to src/diffcmd.c.
︙ | ︙ | |||
196 197 198 199 200 201 202 | @ <meta charset="UTF-8"> @ <style> @ h1 { @ font-size: 150%; @ } @ @ table.diff { | | | 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 | @ <meta charset="UTF-8"> @ <style> @ h1 { @ font-size: 150%; @ } @ @ table.diff { @ width: 100%; @ border-spacing: 0; @ border: 1px solid black; @ } @ table.diff td { @ vertical-align: top; @ } @ table.diff pre { |
︙ | ︙ |
Changes to src/fileedit.c.
︙ | ︙ | |||
1997 1998 1999 2000 2001 2002 2003 | ** used for dynamically toggling certain UI components on and off. ** Must come after window.fossil has been intialized and before ** fossil.page.fileedit.js. Potential TODO: move this into the ** window.fossil bootstrapping so that we don't have to "fulfill" ** the JS multiple times. */ ajax_emit_js_preview_modes(1); | | | 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 | ** used for dynamically toggling certain UI components on and off. ** Must come after window.fossil has been intialized and before ** fossil.page.fileedit.js. Potential TODO: move this into the ** window.fossil bootstrapping so that we don't have to "fulfill" ** the JS multiple times. */ ajax_emit_js_preview_modes(1); builtin_fossil_js_bundle_or("diff", NULL); builtin_request_js("fossil.page.fileedit.js"); builtin_fulfill_js_requests(); { /* Dynamically populate the editor, display any error in the err ** blob, and/or switch to tab #0, where the file selector ** lives. The extra C scopes here correspond to JS-level scopes, ** to improve grokability. */ |
︙ | ︙ |
Name change from src/fossil.info-diff.js to src/fossil.diff.js.
1 | /** | | < > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 | /** diff-related JS APIs for fossil. */ "use strict"; window.fossil.onPageLoad(function(){ /** Adds toggle checkboxes to each file entry in the diff views for /info and similar pages. */ const D = window.fossil.dom; const addToggle = function(diffElem){ const sib = diffElem.previousElementSibling, btn = sib ? D.addClass(D.checkbox(true), 'diff-toggle') : 0; if(!sib) return; D.append(sib,btn); btn.addEventListener('click', function(){ diffElem.classList.toggle('hidden'); }, false); }; document.querySelectorAll('table.diff').forEach(addToggle); }); window.fossil.onPageLoad(function(){ const F = window.fossil, D = F.dom; const Diff = F.diff = { e:{/*certain cached DOM elements*/}, config: { chunkLoadLines: ( F.config.diffContextLines * 3 /*per /chat discussion*/ ) || 20, chunkFetch: { /* Default callack handlers for Diff.fetchArtifactChunk(), unless overridden by options passeed to that function. */ beforesend: function(){}, aftersend: function(){}, onerror: function(e){ F.toast.error("XHR error: ",e.message); } } } }; /** Uses the /jchunk AJAX route to fetch specific lines of a given artifact. The argument must be an Object suitable for passing as the second argument to fossil.fetch(). Its urlParams property must be an object with these properties: { name: full hash of the target file, from: first 1-based line number of the file to fetch (inclusive), to: last 1-based line number of the file to fetch (inclusive) } The fetchOpt object is NOT cloned for use by the call: it is used as-is and may be modified by this call. Thus callers "really should" pass a temporary object, not a long-lived one. If fetchOpt does not define any of the (beforesend, aftersend, onerror) callbacks, the defaults from fossil.diff.config.chunkFetch are used, so any given client page may override those to provide page-level default handling. Note that onload callback is ostensibly optional but this function is not of much use without an onload handler. Conversely, the default onerror handler is often customized on a per-page basis to send the error output somewhere where the user can see it. The response, on success, will be an array of strings, each entry being one line from the requested artifact. If the 'to' line is greater than the length of the file, the array will be shorter than (to-from) lines. The /jchunk route reports errors via JSON objects with an "error" string property describing the problem. This is an async operation. Returns the fossil object. */ Diff.fetchArtifactChunk = function(fetchOpt){ if(!fetchOpt.beforesend) fetchOpt.beforesend = Diff.config.chunkFetch.beforesend; if(!fetchOpt.aftersend) fetchOpt.aftersend = Diff.config.chunkFetch.aftersend; if(!fetchOpt.onerror) fetchOpt.onerror = Diff.config.chunkFetch.onerror; fetchOpt.responseType = 'json'; return F.fetch('jchunk', fetchOpt); }; /** Extracts either the starting or ending line number from a line-numer column in the given tr. isSplit must be true if tr represents a split diff, else false. Expects its tr to be valid: GIGO applies. Returns the starting line number if getStart, else the ending line number. Returns the line number from the LHS file if getLHS is true, else the RHS. */ const extractLineNo = function f(getLHS, getStart, tr, isSplit){ if(!f.rx){ f.rx = { start: /^\s*(\d+)/, end: /(\d+)\n?$/ } } const td = tr.querySelector('td:nth-child('+( /* TD element with the line numbers */ getLHS ? 1 : (isSplit ? 4 : 2) )+')'); const m = f.rx[getStart ? 'start' : 'end'].exec(td.innerText); return m ? +m[1] : undefined/*"shouldn't happen"*/; }; /** Fetches /jchunk for the given TR element then replaces the TR's contents with data from the result of that request. */ const fetchTrChunk = function(tr){ if(tr.dataset.xfer /* already being fetched */) return; const table = tr.parentElement.parentElement; const hash = table.dataset.lefthash; if(!hash) return; const isSbs = table.classList.contains('splitdiff')/*else udiff*/; tr.dataset.xfer = 1 /* sentinel against multiple concurrent ajax requests */; const lineTo = +tr.dataset.endln; var lineFrom = +tr.dataset.startln; /* TODO: for the time being, for simplicity, we'll read the whole [startln, endln] chunk. "Later on" we'll maybe want to read it in chunks of, say, 20 lines or so, adjusting lineFrom to be 1 if it would be less than 1. */ Diff.fetchArtifactChunk({ urlParams:{ name: hash, from: lineFrom, to: lineTo }, aftersend: function(){ delete tr.dataset.xfer; Diff.config.chunkFetch.aftersend.apply( this, Array.prototype.slice.call(arguments,0) ); }, onload: function(result){ //console.debug("Chunk result: ",result); /* Replace content of tr.diffskip with the fetches result. When we refactor this to load in smaller chunks, we'll instead need to keep this skipper in place and: - Add a new TR above or above it, as apropriate. - Change the TR.dataset.startln/endln values to account for the just-fetched set. */ D.clearElement(tr); const cols = [], preCode = [D.pre()], preLines = [D.pre(), D.pre()]; if(isSbs){ cols.push(D.addClass(D.td(tr), 'diffln', 'difflnl')); cols.push(D.addClass(D.td(tr), 'difftxt', 'difftxtl')); cols.push(D.addClass(D.td(tr), 'diffsep')); cols.push(D.addClass(D.td(tr), 'diffln', 'difflnr')); cols.push(D.addClass(D.td(tr), 'difftxt', 'difftxtr')); D.append(cols[0], preLines[0]); D.append(cols[1], preCode[0]); D.append(cols[3], preLines[1]); preCode.push(D.pre()); D.append(cols[4], preCode[1]); }else{ cols.push(D.addClass(D.td(tr), 'diffln', 'difflnl')); cols.push(D.addClass(D.td(tr), 'diffln', 'difflnr')); cols.push(D.addClass(D.td(tr), 'diffsep')); cols.push(D.addClass(D.td(tr), 'difftxt', 'difftxtu')); D.append(cols[0], preLines[0]); D.append(cols[1], preLines[1]); D.append(cols[3], preCode[0]); } let lineno = [], i; for( i = lineFrom; i <= lineTo; ++i ){ lineno.push(i); } preLines[0].append(lineno.join('\n')+'\n'); if(1){ const code = result.join('\n')+'\n'; preCode.forEach((e)=>e.innerText = code); } //console.debug("Updated TR",tr); Diff.initTableDiff(table).checkTableWidth(true); /* Reminders to self during development: SBS diff col layout: <td.diffln.difflnl><pre>...LHS line numbers...</pre></td> <td.difftxt.difftxtl><pre>...code lines...</pre></td> <td.diffsep>empty for this case (common lines)</td> <td.diffln.difflnr><pre>...RHS line numbers...</pre></td> <td.difftxt.difftxtr><pre>...dupe of col 2</pre></td> Unified diff col layout: <td.diffln.difflnl><pre>LHS line numbers</pre></td> <td.diffln.difflnr><pre>RHS line numbers</pre></td> <td.diffsep>empty in this case (common lines)</td> <td.difftxt.difftxtu><pre>code line</pre></td> C-side TODOs: - If we have that data readily available, it would be a big help (simplify our line calculations) if we stored the line number ranges in all elements which have that state handy. */ } }); }; /** Installs chunk-loading controls into TR.diffskip element tr. Each instance corresponds to a single TR.diffskip element. The goal is to base these controls roughly on github's, a good example of which, for use as a model, is: https://github.com/msteveb/autosetup/commit/235925e914a52a542 */ const ChunkLoadControls = function(tr){ this.e = {/*DOM elements*/ tr: tr, table: tr.parentElement/*TBODY*/.parentElement }; this.isSplit = this.e.table.classList.contains('splitdiff')/*else udiff*/; this.fileHash = this.e.table.dataset.lefthash; tr.$chunker = this /* keep GC from reaping this */; this.pos = { /* These line numbers correspond to the LHS file. Because the contents are common to both sides, we have the same number for the RHS, but need to extract those line numbers from the neighboring TR blocks */ startLhs: +tr.dataset.startln, endLhs: +tr.dataset.endln }; D.clearElement(tr); this.e.td = D.addClass( /* Holder for our UI controls */ D.attr(D.td(tr), 'colspan', this.isSplit ? 5 : 4), 'chunkctrl' ); this.e.btnWrapper = D.div(); D.append(this.e.td, this.e.btnWrapper); /** Depending on various factors, we need one or more of: - A single button to load the initial chunk incrementally - A single button to load all lines then remove this control - Two buttons: one to load upwards, one to load downwards - A single button to load the final chunk incrementally */ if(tr.nextElementSibling){ this.pos.next = { startLhs: extractLineNo(true, true, tr.nextElementSibling, this.isSplit), startRhs: extractLineNo(false, true, tr.nextElementSibling, this.isSplit) }; } if(tr.previousElementSibling){ this.pos.prev = { endLhs: extractLineNo(true, false, tr.previousElementSibling, this.isSplit), endRhs: extractLineNo(false, false, tr.previousElementSibling, this.isSplit) }; } let btnUp = false, btnDown = false; /** this.pos.next refers to the line numbers in the next TR's chunk. this.pos.prev refers to the line numbers in the previous TR's chunk. */ if((this.pos.startLhs + Diff.config.chunkLoadLines >= this.pos.endLhs ) || (this.pos.prev && this.pos.next && ((this.pos.next.startLhs - this.pos.prev.endLhs) <= Diff.config.chunkLoadLines))){ /* Place a single button to load the whole block, rather than separate up/down buttons. */ btnDown = false; btnUp = this.createButton(this.FetchType.FillGap); }else{ /* Figure out which chunk-load buttons to add... */ if(this.pos.prev){ btnDown = this.createButton(this.FetchType.PrevDown); } if(this.pos.next){ btnUp = this.createButton(this.FetchType.NextUp); } } //this.e.btnUp = btnUp; //this.e.btnDown = btnDown; if(btnDown) D.append(this.e.btnWrapper, btnDown); if(btnUp) D.append(this.e.btnWrapper, btnUp); /* For debugging only... */ this.e.posState = D.span(); D.append(this.e.btnWrapper, this.e.posState); this.updatePosDebug(); }; ChunkLoadControls.prototype = { /** An "enum" of values describing the types of context fetches/operations performed by this type. The values in this object must not be changed without modifying all logic which relies on their relative order. */ FetchType:{ /** Append context to the bottom of the previous diff chunk. */ PrevDown: 1, /** Fill a complete gap between the previous/next diff chunks or at the start of the next chunk or end of the previous chunks. */ FillGap: 0, /** Prepend context to the start of the next diff chunk. */ NextUp: -1 }, config: { /* glyphUp: '⇡', //'&#uarr;', glyphDown: '⇣' //'&#darr;' */ }, /** Creates and returns a button element for fetching a chunk in the given fetchType (as documented for fetchChunk()). */ createButton: function(fetchType){ let b; switch(fetchType){ case this.FetchType.PrevDown: b = D.append( D.addClass(D.span(), 'down'), D.span(/*glyph holder*/) ); break; case this.FetchType.FillGap: b = D.append( D.addClass(D.span(), 'up', 'down'), D.span(/*glyph holder*/) ); break; case this.FetchType.NextUp: b = D.append( D.addClass(D.span(), 'up'), D.span(/*glyph holder*/) ); break; default: throw new Error("Internal API misuse: unexpected fetchType value "+fetchType); } D.addClass(b, 'jcbutton'); b.addEventListener('click', ()=>this.fetchChunk(fetchType),false); return b; }, updatePosDebug: function(){ if(this.e.posState){ D.clearElement(this.e.posState); //D.append(D.clearElement(this.e.posState), JSON.stringify(this.pos)); } return this; }, /* Attempt to clean up resources and remove some circular references to that GC can do the right thing. */ destroy: function(){ D.remove(this.e.tr); delete this.e.tr.$chunker; delete this.e.tr; delete this.e; delete this.pos; }, /** If the gap between this.pos.endLhs/startLhs is less than or equal to Diff.config.chunkLoadLines then this function replaces any up/down buttons with a gap-filler button, else it's a no-op. Returns this object. */ maybeReplaceButtons: function(){ if(this.pos.endLhs - this.pos.startLhs <= Diff.config.chunkLoadLines){ D.clearElement(this.e.btnWrapper); D.append(this.e.btnWrapper, this.createButton(this.FetchType.FillGap)); } return this; }, /** Callack for /jchunk responses. */ injectResponse: function f(fetchType/*as for fetchChunk()*/, urlParam/*from fetchChunk()*/, lines/*response lines*/){ if(!lines.length){ /* No more data to load */ this.destroy(); return this; } //console.debug("Loaded line range ", //urlParam.from,"-",urlParam.to, "fetchType ",fetchType); const lineno = [], trPrev = this.e.tr.previousElementSibling, trNext = this.e.tr.nextElementSibling, doAppend = ( !!trPrev && fetchType>=this.FetchType.FillGap ) /* true to append to previous TR, else prepend to NEXT TR */; const tr = doAppend ? trPrev : trNext; const joinTr = ( this.FetchType.FillGap===fetchType && trPrev && trNext ) ? trNext : false /* Truthy if we want to combine trPrev, the new content, and trNext into trPrev and then remove trNext. */; let i, td; if(!f.convertLines){ f.convertLines = function(li){ return li.join('\n') .replaceAll('&','&') .replaceAll('<','<')+'\n'; }; } if(1){ // LHS line numbers... const selector = '.difflnl > pre'; td = tr.querySelector(selector); const lnTo = Math.min(urlParam.to, urlParam.from + lines.length - 1/*b/c request range is inclusive*/); for( i = urlParam.from; i <= lnTo; ++i ){ lineno.push(i); } const lineNoTxt = lineno.join('\n')+'\n'; const content = [td.innerHTML]; if(doAppend) content.push(lineNoTxt); else content.unshift(lineNoTxt); if(joinTr){ content.push(trNext.querySelector(selector).innerHTML); } td.innerHTML = content.join(''); } if(1){// code block(s)... const selector = '.difftxt > pre'; td = tr.querySelectorAll(selector); const code = f.convertLines(lines); let joinNdx = 0; td.forEach(function(e){ const content = [e.innerHTML]; if(doAppend) content.push(code); else content.unshift(code); if(joinTr){ content.push(trNext.querySelectorAll(selector)[joinNdx++].innerHTML) } e.innerHTML = content.join(''); }); } if(1){// Add blank lines in (.diffsep>pre) const selector = '.diffsep > pre'; td = tr.querySelector(selector); for(i = 0; i < lineno.length; ++i) lineno[i] = ''; const blanks = lineno.join('\n')+'\n'; const content = [td.innerHTML]; if(doAppend) content.push(blanks); else content.unshift(blanks); if(joinTr){ content.push(trNext.querySelector(selector).innerHTML); } td.innerHTML = content.join(''); } if(this.FetchType.FillGap===fetchType){ /* Closing the whole gap between two chunks or a whole gap at the start or end of a diff. */ // RHS line numbers... let startLnR = this.pos.prev ? this.pos.prev.endRhs+1 /* Closing the whole gap between two chunks or end-of-file gap. */ : this.pos.next.startRhs - lines.length /* start-of-file gap */; lineno.length = lines.length; for( i = startLnR; i < startLnR + lines.length; ++i ){ lineno[i-startLnR] = i; } const selector = '.difflnr > pre'; td = tr.querySelector(selector); const lineNoTxt = lineno.join('\n')+'\n'; lineno.length = 0; const content = [td.innerHTML]; if(doAppend) content.push(lineNoTxt); else content.unshift(lineNoTxt); if(joinTr){ content.push(trNext.querySelector(selector).innerHTML); } td.innerHTML = content.join(''); if(joinTr) D.remove(joinTr); Diff.checkTableWidth(true); this.destroy(); return this; }else if(this.FetchType.PrevDown===fetchType){ /* Append context to previous TR. */ // RHS line numbers... let startLnR = this.pos.prev.endRhs+1; lineno.length = lines.length; for( i = startLnR; i < startLnR + lines.length; ++i ){ lineno[i-startLnR] = i; } this.pos.startLhs += lines.length; this.pos.prev.endRhs += lines.length; this.pos.prev.endLhs += lines.length; const selector = '.difflnr > pre'; td = tr.querySelector(selector); const lineNoTxt = lineno.join('\n')+'\n'; lineno.length = 0; const content = [td.innerHTML]; if(doAppend) content.push(lineNoTxt); else content.unshift(lineNoTxt); td.innerHTML = content.join(''); if(lines.length < (urlParam.to - urlParam.from)){ /* No more data. */ this.destroy(); }else{ this.maybeReplaceButtons(); this.updatePosDebug(); } Diff.checkTableWidth(true); return this; }else if(this.FetchType.NextUp===fetchType){ /* Prepend content to next TR. */ // RHS line numbers... if(doAppend){ throw new Error("Internal precondition violation: doAppend is true."); } let startLnR = this.pos.next.startRhs - lines.length; lineno.length = lines.length; for( i = startLnR; i < startLnR + lines.length; ++i ){ lineno[i-startLnR] = i; } this.pos.endLhs -= lines.length; this.pos.next.startRhs -= lines.length; this.pos.next.startLhs -= lines.length; const selector = '.difflnr > pre'; td = tr.querySelector(selector); const lineNoTxt = lineno.join('\n')+'\n'; lineno.length = 0; td.innerHTML = lineNoTxt + td.innerHTML; if(this.pos.endLhs<=1 || lines.length < (urlParam.to - urlParam.from)){ /* No more data. */ this.destroy(); }else{ this.maybeReplaceButtons(); this.updatePosDebug(); } Diff.checkTableWidth(true); return this; }else{ throw new Error("Unexpected 'fetchType' value."); } }, /** Fetches and inserts a line chunk. fetchType is: this.FetchType.NextUp = upwards from next chunk (this.pos.next) this.FetchType.FillGap = the whole gap between this.pos.prev and this.pos.next, or the whole gap before/after the initial/final chunk in the diff. this.FetchType.PrevDown = downwards from the previous chunk (this.pos.prev) Those values are set at the time this object is initialized but one instance of this class may have 2 buttons, one each for fetchTypes NextUp and PrevDown. This is an async operation. While it is in transit, any calls to this function will have no effect except (possibly) to emit a warning. Returns this object. */ fetchChunk: function(fetchType){ /* Forewarning, this is a bit confusing: when fetching the previous lines, we're doing so on behalf of the *next* diff chunk (this.pos.next), and vice versa. */ if(this.$isFetching){ F.toast.warning("Cannot load chunk while a load is pending."); return this; } if(fetchType===this.FetchType.NextUp && !this.pos.next || fetchType===this.FetchType.PrevDown && !this.pos.prev){ console.error("Attempt to fetch diff lines but don't have any."); return this; } const fOpt = { urlParams:{ name: this.fileHash, from: 0, to: 0 }, aftersend: ()=>delete this.$isFetching, onload: (list)=>this.injectResponse(fetchType,up,list) }; const up = fOpt.urlParams; if(fetchType===this.FetchType.FillGap){ /* Easiest case: filling a whole gap. */ up.from = this.pos.startLhs; up.to = this.pos.endLhs; }else if(this.FetchType.PrevDown===fetchType){ /* Append to previous TR. */ if(!this.pos.prev){ console.error("Attempt to fetch next diff lines but don't have any."); return this; } up.from = this.pos.prev.endLhs + 1; up.to = up.from + Diff.config.chunkLoadLines - 1/*b/c request range is inclusive*/; if( this.pos.next && this.pos.next.startLhs <= up.to ){ up.to = this.pos.next.startLhs - 1; fetchType = this.FetchType.FillGap; } }else{ /* Prepend to next TR */ if(!this.pos.next){ console.error("Attempt to fetch previous diff lines but don't have any."); return this; } up.to = this.pos.next.startLhs - 1; up.from = Math.max(1, up.to - Diff.config.chunkLoadLines + 1); if( this.pos.prev && this.pos.prev.endLhs >= up.from ){ up.from = this.pos.prev.endLhs + 1; fetchType = this.FetchType.FillGap; } } this.$isFetching = true; //console.debug("fetchChunk(",fetchType,")",up); Diff.fetchArtifactChunk(fOpt); return this; } }; const addDiffSkipHandlers = function(){ const tables = document.querySelectorAll('table.diff[data-lefthash]:not(.diffskipped)'); /* Potential performance-related TODO: instead of installing all of these at once, install them as the corresponding TR is scrolled into view. */ tables.forEach(function(table){ D.addClass(table, 'diffskipped'/*avoid processing these more than once */); table.querySelectorAll('tr.diffskip[data-startln]').forEach(function(tr){ new ChunkLoadControls(D.addClass(tr, 'jchunk')); }); }); return F; }; addDiffSkipHandlers(); }); /* Refinements to the display of unified and side-by-side diffs. ** ** In all cases, the table columns tagged with "difftxt" are expanded, ** where possible, to fill the width of the screen. ** ** For a side-by-side diff, if either column is two wide to fit on the ** display, scrollbars are added. The scrollbars are linked, so that ** both sides scroll together. Left and right arrows also scroll. */ window.fossil.onPageLoad(function(){ const SCROLL_LEN = 25; const F = window.fossil, D = F.dom, Diff = F.diff; var lastWidth; Diff.checkTableWidth = function f(force){ if(undefined === f.contentNode){ f.contentNode = document.querySelector('div.content'); } force = true; const parentCS = window.getComputedStyle(f.contentNode); const parentWidth = ( //document.body.clientWidth; //parentCS.width; f.contentNode.clientWidth - parseFloat(parentCS.marginLeft) - parseFloat(parentCS.marginRight) ); if( !force && parentWidth===lastWidth ) return this; lastWidth = parentWidth; let w = lastWidth*0.5 - 100; //console.debug( "w = ",w,", lastWidth =",lastWidth," body = ",document.body.clientWidth); if(force || !f.colsL){ f.colsL = document.querySelectorAll('td.difftxtl pre'); } f.colsL.forEach(function(e){ e.style.width = w + "px"; e.style.maxWidth = w + "px"; }); if(force || !f.colsR){ f.colsR = document.querySelectorAll('td.difftxtr pre'); } f.colsR.forEach(function(e){ e.style.width = w + "px"; e.style.maxWidth = w + "px"; }); if(0){ // seems to be unnecessary if(!f.allDiffs){ f.allDiffs = document.querySelectorAll('table.diff'); } w = lastWidth; f.allDiffs.forEach(function f(e){ if(0 && !f.$){ f.$ = e.getClientRects()[0]; console.debug("diff table w =",w," f.$x",f.$); w - 2*f.$.x /* left margin (assume right==left, for simplicity) */; } e.style.maxWidth = w + "px"; }); //console.debug("checkTableWidth(",force,") lastWidth =",lastWidth); } return this; }; const scrollLeft = function(event){ //console.debug("scrollLeft",this,event); const table = this.parentElement/*TD*/.parentElement/*TR*/. parentElement/*TBODY*/.parentElement/*TABLE*/; table.$txtPres.forEach((e)=>(e===this) ? 1 : (e.scrollLeft = this.scrollLeft)); return false; }; Diff.initTableDiff = function f(diff){ if(!diff){ let i, diffs = document.querySelectorAll('table.splitdiff'); for(i=0; i<diffs.length; ++i){ f.call(this, diffs[i]); } return this; } diff.$txtCols = diff.querySelectorAll('td.difftxt'); diff.$txtPres = diff.querySelectorAll('td.difftxt pre'); var width = 0; diff.$txtPres.forEach(function(e){ if(width < e.scrollWidth) width = e.scrollWidth; }); //console.debug("diff.$txtPres =",diff.$txtPres); diff.$txtCols.forEach((e)=>e.style.width = width + 'px'); diff.$txtPres.forEach(function(e){ e.style.maxWidth = width + 'px'; e.style.width = width + 'px'; if(!e.classList.contains('scroller')){ D.addClass(e, 'scroller'); e.addEventListener('scroll', scrollLeft, false); } }); diff.tabIndex = 0; if(!diff.classList.contains('scroller')){ D.addClass(diff, 'scroller'); diff.addEventListener('keydown', function(e){ e = e || event; const len = {37: -SCROLL_LEN, 39: SCROLL_LEN}[e.keyCode]; if( !len ) return; this.$txtPres[0].scrollLeft += len; /* ^^^ bug: if there is a 2nd column and it has a scrollbar but txtPres[0] does not, no scrolling happens here. We need to find the widest of txtPres and scroll that one. Example: Checkin a7fbefee38a1c522 file diff.c */ return false; }, false); } return this; } window.fossil.page.tweakSbsDiffs = function(){ document.querySelectorAll('table.splitdiff').forEach((e)=>Diff.initTableDiff); }; Diff.initTableDiff().checkTableWidth(); window.addEventListener('resize', ()=>Diff.checkTableWidth()); }, false); |
Changes to src/fossil.dom.js.
︙ | ︙ | |||
429 430 431 432 433 434 435 | const a = argsToArray(arguments); a.unshift('remove'); return domAddRemoveClass.apply(this, a); }; /** Toggles CSS class c on e (a single element for forEach-capable | | | 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 | const a = argsToArray(arguments); a.unshift('remove'); return domAddRemoveClass.apply(this, a); }; /** Toggles CSS class c on e (a single element for forEach-capable collection of elements). Returns its first argument. */ dom.toggleClass = function f(e,c){ if(e.forEach){ e.forEach((x)=>x.classList.toggle(c)); }else{ e.classList.toggle(c); } |
︙ | ︙ |
Changes to src/info.c.
︙ | ︙ | |||
440 441 442 443 444 445 446 | } /* ** Generate javascript to enhance HTML diffs. */ void append_diff_javascript(int diffType){ if( diffType==0 ) return; | | | 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 | } /* ** Generate javascript to enhance HTML diffs. */ void append_diff_javascript(int diffType){ if( diffType==0 ) return; builtin_fossil_js_bundle_or("diff", NULL); } /* ** Construct an appropriate diffFlag for text_diff() based on query ** parameters and the to boolean arguments. */ DiffConfig *construct_diff_flags(int diffType, DiffConfig *pCfg){ |
︙ | ︙ | |||
931 932 933 934 935 936 937 | const char *zNew = db_column_text(&q3,3); const char *zOldName = db_column_text(&q3, 4); append_file_change_line(zUuid, zName, zOld, zNew, zOldName, pCfg,mperm); } db_finalize(&q3); append_diff_javascript(diffType); | < | 931 932 933 934 935 936 937 938 939 940 941 942 943 944 | const char *zNew = db_column_text(&q3,3); const char *zOldName = db_column_text(&q3, 4); append_file_change_line(zUuid, zName, zOld, zNew, zOldName, pCfg,mperm); } db_finalize(&q3); append_diff_javascript(diffType); style_finish_page(); } /* ** WEBPAGE: winfo ** URL: /winfo?name=HASH ** |
︙ | ︙ | |||
1336 1337 1338 1339 1340 1341 1342 | pFileFrom = manifest_file_next(pFrom, 0); pFileTo = manifest_file_next(pTo, 0); } } manifest_destroy(pFrom); manifest_destroy(pTo); append_diff_javascript(diffType); | < | 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 | pFileFrom = manifest_file_next(pFrom, 0); pFileTo = manifest_file_next(pTo, 0); } } manifest_destroy(pFrom); manifest_destroy(pTo); append_diff_javascript(diffType); style_finish_page(); } #if INTERFACE /* ** Possible return values from object_description() */ |
︙ | ︙ | |||
1882 1883 1884 1885 1886 1887 1888 | } g.isConst = 1; deliver_artifact(rid, P("m")); } /* | | | > > > > > | | | > > | > > > > > | > > > > > > | > > > > > > > | > > > > < | > | 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 | } g.isConst = 1; deliver_artifact(rid, P("m")); } /* ** WEBPAGE: jchunk hidden ** URL: /jchunk/HASH?from=N&to=M ** ** Return lines of text from a file as a JSON array - one entry in the ** array for each line of text. ** ** **Warning:** This is an internal-use-only interface that is subject to ** change at any moment. External application should not use this interface ** since the application will break when this interface changes, and this ** interface will undoubtedly change. ** ** This page is intended to be used in an XHR from javascript on a ** diff page, to return unseen context to fill in additional context ** when the user clicks on the appropriate button. The response is ** always in JSON form and errors are reported as documented for ** ajax_route_error(). */ void jchunk_page(void){ int rid = 0; const char *zName = PD("name", ""); int iFrom = atoi(PD("from","0")); int iTo = atoi(PD("to","0")); int ln; int go = 1; const char *zSep; Blob content; Blob line; Blob *pOut; if(0){ ajax_route_error(400, "Just testing client-side error handling."); return; } login_check_credentials(); if( !g.perm.Read ){ ajax_route_error(403, "Access requires Read permissions."); return; } #if 1 /* Re-enable this block once this code is integrated somewhere into the UI. */ rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zName); if( rid==0 ){ ajax_route_error(404, "Unknown artifact: %h", zName); return; } #else /* This impl is only to simplify "manual" testing via the JS console. */ rid = symbolic_name_to_rid(zName, "*"); if( rid==0 ){ ajax_route_error(404, "Unknown artifact: %h", zName); return; }else if( rid<0 ){ ajax_route_error(418, "Ambiguous artifact name: %h", zName); return; } #endif if( iFrom<1 || iTo<iFrom ){ ajax_route_error(500, "Invalid line range from=%d, to=%d.", iFrom, iTo); return; } content_get(rid, &content); g.isConst = 1; cgi_set_content_type("text/json"); ln = 0; while( go && ln<iFrom ){ |
︙ | ︙ |
Changes to src/main.mk.
︙ | ︙ | |||
222 223 224 225 226 227 228 | $(SRCDIR)/default.css \ $(SRCDIR)/diff.js \ $(SRCDIR)/diff.tcl \ $(SRCDIR)/forum.js \ $(SRCDIR)/fossil.bootstrap.js \ $(SRCDIR)/fossil.confirmer.js \ $(SRCDIR)/fossil.copybutton.js \ | | | | | 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 | $(SRCDIR)/default.css \ $(SRCDIR)/diff.js \ $(SRCDIR)/diff.tcl \ $(SRCDIR)/forum.js \ $(SRCDIR)/fossil.bootstrap.js \ $(SRCDIR)/fossil.confirmer.js \ $(SRCDIR)/fossil.copybutton.js \ $(SRCDIR)/fossil.diff.js \ $(SRCDIR)/fossil.dom.js \ $(SRCDIR)/fossil.fetch.js \ $(SRCDIR)/fossil.numbered-lines.js \ $(SRCDIR)/fossil.page.brlist.js \ $(SRCDIR)/fossil.page.fileedit.js \ $(SRCDIR)/fossil.page.forumpost.js \ $(SRCDIR)/fossil.page.pikchrshow.js \ $(SRCDIR)/fossil.page.whistory.js \ $(SRCDIR)/fossil.page.wikiedit.js \ |
︙ | ︙ |
Changes to src/wiki.c.
︙ | ︙ | |||
1452 1453 1454 1455 1456 1457 1458 | CX("<h2>Wiki Name Rules</h2>"); well_formed_wiki_name_rules(); CX("</div>"/*#wikiedit-tab-save*/); } builtin_fossil_js_bundle_or("fetch", "dom", "tabs", "confirmer", "storage", "popupwidget", "copybutton", "pikchr", NULL); | | | 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 | CX("<h2>Wiki Name Rules</h2>"); well_formed_wiki_name_rules(); CX("</div>"/*#wikiedit-tab-save*/); } builtin_fossil_js_bundle_or("fetch", "dom", "tabs", "confirmer", "storage", "popupwidget", "copybutton", "pikchr", NULL); builtin_fossil_js_bundle_or("diff", NULL); builtin_request_js("fossil.page.wikiedit.js"); builtin_fulfill_js_requests(); /* Dynamically populate the editor... */ style_script_begin(__FILE__,__LINE__); { /* Render the current page list to save us an XHR request during page initialization. This must be OUTSIDE of |
︙ | ︙ |
Changes to win/Makefile.mingw.
︙ | ︙ | |||
631 632 633 634 635 636 637 | $(SRCDIR)/default.css \ $(SRCDIR)/diff.js \ $(SRCDIR)/diff.tcl \ $(SRCDIR)/forum.js \ $(SRCDIR)/fossil.bootstrap.js \ $(SRCDIR)/fossil.confirmer.js \ $(SRCDIR)/fossil.copybutton.js \ | | | | | 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 | $(SRCDIR)/default.css \ $(SRCDIR)/diff.js \ $(SRCDIR)/diff.tcl \ $(SRCDIR)/forum.js \ $(SRCDIR)/fossil.bootstrap.js \ $(SRCDIR)/fossil.confirmer.js \ $(SRCDIR)/fossil.copybutton.js \ $(SRCDIR)/fossil.diff.js \ $(SRCDIR)/fossil.dom.js \ $(SRCDIR)/fossil.fetch.js \ $(SRCDIR)/fossil.numbered-lines.js \ $(SRCDIR)/fossil.page.brlist.js \ $(SRCDIR)/fossil.page.fileedit.js \ $(SRCDIR)/fossil.page.forumpost.js \ $(SRCDIR)/fossil.page.pikchrshow.js \ $(SRCDIR)/fossil.page.whistory.js \ $(SRCDIR)/fossil.page.wikiedit.js \ |
︙ | ︙ |
Changes to win/Makefile.msc.
︙ | ︙ | |||
573 574 575 576 577 578 579 | "$(SRCDIR)\default.css" \ "$(SRCDIR)\diff.js" \ "$(SRCDIR)\diff.tcl" \ "$(SRCDIR)\forum.js" \ "$(SRCDIR)\fossil.bootstrap.js" \ "$(SRCDIR)\fossil.confirmer.js" \ "$(SRCDIR)\fossil.copybutton.js" \ | | | | | 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 | "$(SRCDIR)\default.css" \ "$(SRCDIR)\diff.js" \ "$(SRCDIR)\diff.tcl" \ "$(SRCDIR)\forum.js" \ "$(SRCDIR)\fossil.bootstrap.js" \ "$(SRCDIR)\fossil.confirmer.js" \ "$(SRCDIR)\fossil.copybutton.js" \ "$(SRCDIR)\fossil.diff.js" \ "$(SRCDIR)\fossil.dom.js" \ "$(SRCDIR)\fossil.fetch.js" \ "$(SRCDIR)\fossil.numbered-lines.js" \ "$(SRCDIR)\fossil.page.brlist.js" \ "$(SRCDIR)\fossil.page.fileedit.js" \ "$(SRCDIR)\fossil.page.forumpost.js" \ "$(SRCDIR)\fossil.page.pikchrshow.js" \ "$(SRCDIR)\fossil.page.whistory.js" \ "$(SRCDIR)\fossil.page.wikiedit.js" \ |
︙ | ︙ | |||
1181 1182 1183 1184 1185 1186 1187 | echo "$(SRCDIR)\default.css" >> $@ echo "$(SRCDIR)\diff.js" >> $@ echo "$(SRCDIR)\diff.tcl" >> $@ echo "$(SRCDIR)\forum.js" >> $@ echo "$(SRCDIR)\fossil.bootstrap.js" >> $@ echo "$(SRCDIR)\fossil.confirmer.js" >> $@ echo "$(SRCDIR)\fossil.copybutton.js" >> $@ | | | | | 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 | echo "$(SRCDIR)\default.css" >> $@ echo "$(SRCDIR)\diff.js" >> $@ echo "$(SRCDIR)\diff.tcl" >> $@ echo "$(SRCDIR)\forum.js" >> $@ echo "$(SRCDIR)\fossil.bootstrap.js" >> $@ echo "$(SRCDIR)\fossil.confirmer.js" >> $@ echo "$(SRCDIR)\fossil.copybutton.js" >> $@ echo "$(SRCDIR)\fossil.diff.js" >> $@ echo "$(SRCDIR)\fossil.dom.js" >> $@ echo "$(SRCDIR)\fossil.fetch.js" >> $@ echo "$(SRCDIR)\fossil.numbered-lines.js" >> $@ echo "$(SRCDIR)\fossil.page.brlist.js" >> $@ echo "$(SRCDIR)\fossil.page.fileedit.js" >> $@ echo "$(SRCDIR)\fossil.page.forumpost.js" >> $@ echo "$(SRCDIR)\fossil.page.pikchrshow.js" >> $@ echo "$(SRCDIR)\fossil.page.whistory.js" >> $@ echo "$(SRCDIR)\fossil.page.wikiedit.js" >> $@ |
︙ | ︙ |