| | @@ -7,11 +7,11 @@ |
| 7 | 7 | const E = (s)=>document.querySelector(s), |
| 8 | 8 | D = F.dom, |
| 9 | 9 | P = F.page; |
| 10 | 10 | |
| 11 | 11 | /** |
| 12 | | - Manager object for the checkin/file selection list. |
| 12 | + Widget for the checkin/file selection list. |
| 13 | 13 | */ |
| 14 | 14 | P.fileSelector = { |
| 15 | 15 | e:{ |
| 16 | 16 | container: E('#fileedit-file-selector') |
| 17 | 17 | }, |
| | @@ -18,12 +18,20 @@ |
| 18 | 18 | finfo: {}, |
| 19 | 19 | cache: { |
| 20 | 20 | checkins: undefined, |
| 21 | 21 | files:{} |
| 22 | 22 | }, |
| 23 | + /** |
| 24 | + Fetches the list of leaf checkins from the server and updates |
| 25 | + the UI with that list. |
| 26 | + */ |
| 23 | 27 | loadLeaves: function(){ |
| 24 | | - D.append(D.clearElement(this.e.ciListLabel),"Loading leaves..."); |
| 28 | + D.append(D.clearElement( |
| 29 | + this.e.ciListLabel, |
| 30 | + this.e.selectCi, |
| 31 | + this.e.selectFiles |
| 32 | + ),"Loading leaves..."); |
| 25 | 33 | D.disable(this.e.btnLoadFile, this.e.selectFiles, this.e.selectCi); |
| 26 | 34 | const self = this; |
| 27 | 35 | F.fetch('fileedit',{ |
| 28 | 36 | urlParams:'ajax=filelist&leaves', |
| 29 | 37 | responseType: 'json', |
| | @@ -40,10 +48,16 @@ |
| 40 | 48 | }); |
| 41 | 49 | self.loadFiles(loadThisOne ? loadThisOne.checkin : false); |
| 42 | 50 | } |
| 43 | 51 | }); |
| 44 | 52 | }, |
| 53 | + /** |
| 54 | + Loads the file list for the given checkin UUID. It uses a |
| 55 | + cached copy on subsequent calls for the same UUID. If passed a |
| 56 | + falsy value, it instead clears and disables the file selection |
| 57 | + list. |
| 58 | + */ |
| 45 | 59 | loadFiles: function(ciUuid){ |
| 46 | 60 | delete this.finfo.filename; |
| 47 | 61 | this.finfo.checkin = ciUuid; |
| 48 | 62 | const selFiles = this.e.selectFiles; |
| 49 | 63 | if(!ciUuid){ |
| | @@ -79,20 +93,31 @@ |
| 79 | 93 | responseType: 'json', |
| 80 | 94 | onload |
| 81 | 95 | }); |
| 82 | 96 | return this; |
| 83 | 97 | }, |
| 98 | + /** |
| 99 | + Initializes the checkin/file selector widget. Must only be |
| 100 | + called once. |
| 101 | + */ |
| 84 | 102 | init: function(){ |
| 85 | 103 | const selCi = this.e.selectCi = D.select(), |
| 86 | 104 | selFiles = this.e.selectFiles |
| 87 | 105 | = D.addClass(D.select(), 'file-list'), |
| 88 | 106 | btnLoad = this.e.btnLoadFile = |
| 89 | 107 | D.addClass(D.button("Load file"), "flex-shrink"), |
| 90 | 108 | filesLabel = this.e.fileListLabel = |
| 91 | 109 | D.addClass(D.div(),'flex-shrink','file-list-label'), |
| 110 | + ciLabelWrapper = D.addClass( |
| 111 | + D.div(), 'flex-container','flex-row', 'flex-shrink', |
| 112 | + 'stretch' |
| 113 | + ), |
| 114 | + btnReload = D.addClass( |
| 115 | + D.button('Reload'), 'flex-shrink' |
| 116 | + ), |
| 92 | 117 | ciLabel = this.e.ciListLabel = |
| 93 | | - D.addClass(D.div(),'flex-shrink','checkin-list-label') |
| 118 | + D.addClass(D.span(),'flex-shrink','checkin-list-label') |
| 94 | 119 | ; |
| 95 | 120 | D.attr(selCi, 'title',"The list of opened leaves."); |
| 96 | 121 | D.attr(selFiles, 'title', |
| 97 | 122 | "The list of editable files for the selected checkin."); |
| 98 | 123 | D.attr(btnLoad, 'title', |
| | @@ -99,17 +124,17 @@ |
| 99 | 124 | "Load the selected file into the editor."); |
| 100 | 125 | D.disable(selCi, selFiles, btnLoad); |
| 101 | 126 | D.attr(selFiles, 'size', 10); |
| 102 | 127 | D.append( |
| 103 | 128 | this.e.container, |
| 104 | | - ciLabel, |
| 129 | + D.append(ciLabelWrapper, |
| 130 | + btnReload, ciLabel), |
| 105 | 131 | selCi, |
| 106 | 132 | filesLabel, |
| 107 | 133 | selFiles, |
| 108 | 134 | btnLoad |
| 109 | 135 | ); |
| 110 | | - |
| 111 | 136 | this.loadLeaves(); |
| 112 | 137 | selCi.addEventListener( |
| 113 | 138 | 'change', (e)=>this.loadFiles(e.target.value), false |
| 114 | 139 | ); |
| 115 | 140 | btnLoad.addEventListener( |
| | @@ -118,10 +143,13 @@ |
| 118 | 143 | if(this.finfo.filename){ |
| 119 | 144 | P.loadFile(this.finfo.filename, this.finfo.checkin); |
| 120 | 145 | } |
| 121 | 146 | }, false |
| 122 | 147 | ); |
| 148 | + btnReload.addEventListener( |
| 149 | + 'click', (e)=>this.loadLeaves(), false |
| 150 | + ); |
| 123 | 151 | delete this.init; |
| 124 | 152 | } |
| 125 | 153 | }; |
| 126 | 154 | |
| 127 | 155 | window.addEventListener("load", function() { |
| | @@ -246,10 +274,28 @@ |
| 246 | 274 | // Force UI update |
| 247 | 275 | new Event('change',{target:selectFontSize}) |
| 248 | 276 | ); |
| 249 | 277 | } |
| 250 | 278 | }, false)/*onload event handler*/; |
| 279 | + |
| 280 | + /** |
| 281 | + Getter (if called with no args) or setter (if passed an arg) for |
| 282 | + the current file content. We use a function, rather than direct |
| 283 | + access so that clients can hypothetically swap out this method |
| 284 | + from their skin in order to facilitate plugging-in of fancy |
| 285 | + 3rd-party editor widgets. |
| 286 | + |
| 287 | + The setter form returns this object. |
| 288 | + */ |
| 289 | + P.value = function(){ |
| 290 | + if(0===arguments.length){ |
| 291 | + return this.e.taEditor.value; |
| 292 | + }else{ |
| 293 | + this.e.taEditor.value = arguments[0]; |
| 294 | + return this; |
| 295 | + } |
| 296 | + }; |
| 251 | 297 | |
| 252 | 298 | /** |
| 253 | 299 | Toggles between single- and multi-line comment |
| 254 | 300 | mode. |
| 255 | 301 | */ |
| | @@ -344,11 +390,11 @@ |
| 344 | 390 | filename:file, |
| 345 | 391 | checkin:rev |
| 346 | 392 | }, |
| 347 | 393 | onload:(r)=>{ |
| 348 | 394 | F.message('Loaded content.'); |
| 349 | | - self.e.taEditor.value = r; |
| 395 | + self.value(r); |
| 350 | 396 | self.updateVersion(file,rev); |
| 351 | 397 | self.tabs.switchToTab(self.e.tabs.content); |
| 352 | 398 | } |
| 353 | 399 | }); |
| 354 | 400 | return this; |
| | @@ -372,11 +418,11 @@ |
| 372 | 418 | const updateView = function(c){ |
| 373 | 419 | D.clearElement(f.target); |
| 374 | 420 | if('string'===typeof c) f.target.innerHTML = c; |
| 375 | 421 | if(switchToTab) self.tabs.switchToTab(self.e.tabs.preview); |
| 376 | 422 | }; |
| 377 | | - return this._postPreview(this.e.taEditor.value, updateView); |
| 423 | + return this._postPreview(this.value(), updateView); |
| 378 | 424 | }; |
| 379 | 425 | |
| 380 | 426 | /** |
| 381 | 427 | Callback for use with F.connectPagePreviewers() |
| 382 | 428 | */ |
| | @@ -416,11 +462,11 @@ |
| 416 | 462 | |
| 417 | 463 | Returns this object, noting that the operation is async. |
| 418 | 464 | */ |
| 419 | 465 | P.diff = function f(sbs){ |
| 420 | 466 | if(!affirmHasFile()) return this; |
| 421 | | - const content = this.e.taEditor.value, |
| 467 | + const content = this.value(), |
| 422 | 468 | self = this; |
| 423 | 469 | if(!f.target){ |
| 424 | 470 | f.target = this.e.tabs.diff.querySelector( |
| 425 | 471 | '#fileedit-tab-diff-wrapper' |
| 426 | 472 | ); |
| | @@ -456,11 +502,11 @@ |
| 456 | 502 | Returns this object. |
| 457 | 503 | */ |
| 458 | 504 | P.commit = function f(){ |
| 459 | 505 | if(!affirmHasFile()) return this; |
| 460 | 506 | const self = this; |
| 461 | | - const content = this.e.taEditor.value, |
| 507 | + const content = this.value(), |
| 462 | 508 | target = document.querySelector('#fileedit-manifest'), |
| 463 | 509 | cbDryRun = E('[name=dry_run]'), |
| 464 | 510 | isDryRun = cbDryRun.checked, |
| 465 | 511 | filename = this.finfo.filename; |
| 466 | 512 | if(!f.updateView){ |
| | @@ -480,11 +526,12 @@ |
| 480 | 526 | ]; |
| 481 | 527 | if(!c.dryRun){ |
| 482 | 528 | msg.push('Re-activating dry-run mode.'); |
| 483 | 529 | self.e.taComment.value = ''; |
| 484 | 530 | cbDryRun.checked = true; |
| 485 | | - P.updateVersion(filename, c.uuid); |
| 531 | + self.updateVersion(filename, c.uuid); |
| 532 | + self.fileSelector.loadLeaves(); |
| 486 | 533 | } |
| 487 | 534 | F.message.apply(fossil, msg); |
| 488 | 535 | self.tabs.switchToTab(self.e.tabs.commit); |
| 489 | 536 | }; |
| 490 | 537 | } |
| 491 | 538 | |