| | @@ -7,19 +7,21 @@ |
| 7 | 7 | fossil.tabs, fossil.storage, fossil.confirmer. |
| 8 | 8 | |
| 9 | 9 | Custom events which can be listened for via |
| 10 | 10 | fossil.page.addEventListener(): |
| 11 | 11 | |
| 12 | | - - Event 'wiki-loaded': passes on information when it |
| 12 | + - Event 'wiki-page-loaded': passes on information when it |
| 13 | 13 | loads a wiki (whether from the network or its internal local-edit |
| 14 | 14 | cache), in the form of an "winfo" object: |
| 15 | 15 | |
| 16 | 16 | { |
| 17 | 17 | name: string, |
| 18 | 18 | mimetype: mimetype string, |
| 19 | + type: "normal" | "tag" | "checkin" | "branch" | "sandbox", |
| 20 | + version: UUID string or null for a sandbox page, |
| 21 | + parent: parent UUID string or null if no parent, |
| 19 | 22 | content: string |
| 20 | | - version: UUID string // POTENTIAL TODO. Currently edit only the latest version. |
| 21 | 23 | } |
| 22 | 24 | |
| 23 | 25 | The internal docs and code frequently use the term "winfo", and such |
| 24 | 26 | references refer to an object with that form. |
| 25 | 27 | |
| | @@ -136,11 +138,11 @@ |
| 136 | 138 | } |
| 137 | 139 | return this.index; |
| 138 | 140 | }, |
| 139 | 141 | _fireStashEvent: function(){ |
| 140 | 142 | if(this._disableNextEvent) delete this._disableNextEvent; |
| 141 | | - else F.page.dispatchEvent('wikiedit-stash-updated', this); |
| 143 | + else F.page.dispatchEvent('wiki-stash-updated', this); |
| 142 | 144 | }, |
| 143 | 145 | /** |
| 144 | 146 | Returns the stashed version, if any, for the given winfo object. |
| 145 | 147 | */ |
| 146 | 148 | getWinfo: function(winfo){ |
| | @@ -160,14 +162,16 @@ |
| 160 | 162 | updateWinfo: function(winfo,content){ |
| 161 | 163 | const ndx = this.getIndex(), |
| 162 | 164 | key = this.indexKey(winfo), |
| 163 | 165 | old = ndx[key]; |
| 164 | 166 | const record = old || (ndx[key]={ |
| 165 | | - name: winfo.name, |
| 166 | | - mimetype: winfo.mimetype, |
| 167 | | - isSandbox: !!winfo.isSandbox |
| 167 | + name: winfo.name |
| 168 | 168 | }); |
| 169 | + record.mimetype = winfo.mimetype; |
| 170 | + record.type = winfo.type; |
| 171 | + record.parent = winfo.parent; |
| 172 | + record.version = winfo.version; |
| 169 | 173 | record.stashTime = new Date().getTime(); |
| 170 | 174 | this.storeIndex(); |
| 171 | 175 | if(arguments.length>1){ |
| 172 | 176 | F.storage.set(this.contentKey(key), content); |
| 173 | 177 | } |
| | @@ -180,12 +184,13 @@ |
| 180 | 184 | */ |
| 181 | 185 | stashedContent: function(winfo){ |
| 182 | 186 | return F.storage.get(this.contentKey(this.indexKey(winfo))); |
| 183 | 187 | }, |
| 184 | 188 | /** Returns true if we have stashed content for the given winfo |
| 185 | | - record. */ |
| 189 | + record or page name. */ |
| 186 | 190 | hasStashedContent: function(winfo){ |
| 191 | + if('string'===typeof winfo) winfo = {name: winfo}; |
| 187 | 192 | return F.storage.contains(this.contentKey(this.indexKey(winfo))); |
| 188 | 193 | }, |
| 189 | 194 | /** Unstashes the given winfo record and its content. |
| 190 | 195 | Returns this. */ |
| 191 | 196 | unstash: function(winfo){ |
| | @@ -255,10 +260,64 @@ |
| 255 | 260 | if(forceEvent){ |
| 256 | 261 | // Force UI update |
| 257 | 262 | s.dispatchEvent(new Event('change',{target:s})); |
| 258 | 263 | } |
| 259 | 264 | }; |
| 265 | + |
| 266 | + const WikiList = { |
| 267 | + e: {}, |
| 268 | + refreshStashMarks: function(){ |
| 269 | + this.e.select.querySelectorAll( |
| 270 | + 'option' |
| 271 | + ).forEach(function(o){ |
| 272 | + if($stash.hasStashedContent(o.value)) D.addClass(o, 'stashed'); |
| 273 | + else D.removeClass(o, 'stashed'); |
| 274 | + }); |
| 275 | + }, |
| 276 | + init: function(parentElem){ |
| 277 | + const sel = D.select(), btn = D.button("Reload page list"); |
| 278 | + this.e.select = sel; |
| 279 | + D.addClass(parentElem, 'wikiedit-page-list-wrapper'); |
| 280 | + D.clearElement(parentElem); |
| 281 | + D.append( |
| 282 | + parentElem, btn, |
| 283 | + D.append(D.span(), "Select a page to edit:"), |
| 284 | + sel, |
| 285 | + D.append(D.span(), "* = local edits exist."), |
| 286 | + ); |
| 287 | + D.attr(sel, 'size', 10); |
| 288 | + D.option(D.disable(D.clearElement(sel)), "Loading..."); |
| 289 | + const self = this; |
| 290 | + btn.addEventListener( |
| 291 | + 'click', |
| 292 | + function(){ |
| 293 | + F.fetch('wikiajax/list',{ |
| 294 | + responseType: 'json', |
| 295 | + onload: function(list){ |
| 296 | + D.clearElement(sel); |
| 297 | + list.forEach((e)=>D.option(sel, e)); |
| 298 | + //D.option(sel, "sandbox"); |
| 299 | + D.enable(sel); |
| 300 | + self.refreshStashMarks(); |
| 301 | + } |
| 302 | + }); |
| 303 | + }, |
| 304 | + false |
| 305 | + ); |
| 306 | + btn.click(); |
| 307 | + sel.addEventListener( |
| 308 | + 'change', |
| 309 | + (e)=>P.loadPage(e.target.value), |
| 310 | + false |
| 311 | + ); |
| 312 | + F.page.addEventListener( |
| 313 | + 'wiki-stash-updated', |
| 314 | + ()=>this.refreshStashMarks(), |
| 315 | + false |
| 316 | + ); |
| 317 | + } |
| 318 | + }; |
| 260 | 319 | |
| 261 | 320 | /** |
| 262 | 321 | Keep track of how many in-flight AJAX requests there are so we |
| 263 | 322 | can disable input elements while any are pending. For |
| 264 | 323 | simplicity's sake we simply disable ALL OF IT while any AJAX is |
| | @@ -303,17 +362,18 @@ |
| 303 | 362 | P.tabs = new fossil.TabManager('#wikiedit-tabs'); |
| 304 | 363 | P.e = { /* various DOM elements we work with... */ |
| 305 | 364 | taEditor: E('#wikiedit-content-editor'), |
| 306 | 365 | // btnCommit: E("#wikiedit-btn-commit"), |
| 307 | 366 | btnReload: E("#wikiedit-tab-content button.wikiedit-content-reload"), |
| 308 | | - selectMimetype: E('#select-mimetype select'), |
| 367 | + selectMimetype: E('select[name=mimetype]'), |
| 309 | 368 | selectFontSizeWrap: E('#select-font-size'), |
| 310 | 369 | // selectDiffWS: E('select[name=diff_ws]'), |
| 311 | 370 | cbAutoPreview: E('#cb-preview-autoupdate > input[type=checkbox]'), |
| 312 | 371 | previewTarget: E('#wikiedit-tab-preview-wrapper'), |
| 313 | 372 | diffTarget: E('#wikiedit-tab-diff-wrapper'), |
| 314 | 373 | tabs:{ |
| 374 | + pageList: E('#wikiedit-tab-pages'), |
| 315 | 375 | content: E('#wikiedit-tab-content'), |
| 316 | 376 | preview: E('#wikiedit-tab-preview'), |
| 317 | 377 | diff: E('#wikiedit-tab-diff') |
| 318 | 378 | //commit: E('#wikiedit-tab-commit') |
| 319 | 379 | } |
| | @@ -329,11 +389,11 @@ |
| 329 | 389 | P.tabs.addEventListener( |
| 330 | 390 | /* Set up auto-refresh of the preview tab... */ |
| 331 | 391 | 'before-switch-to', function(ev){ |
| 332 | 392 | if(ev.detail===P.e.tabs.preview){ |
| 333 | 393 | P.baseHrefForWiki(); |
| 334 | | - if(P.e.cbAutoPreview.checked) P.preview(); |
| 394 | + if(P.previewNeedsUpdate && P.e.cbAutoPreview.checked) P.preview(); |
| 335 | 395 | }else if(ev.detail===P.e.tabs.diff){ |
| 336 | 396 | /* Work around a weird bug where the page gets wider than |
| 337 | 397 | the window when the diff tab is NOT in view and the |
| 338 | 398 | current SBS diff widget is wider than the window. When |
| 339 | 399 | the diff IS in view then CSS overflow magically reduces |
| | @@ -381,10 +441,22 @@ |
| 381 | 441 | P.e.taEditor.addEventListener( |
| 382 | 442 | 'change', ()=>P.stashContentChange(), false |
| 383 | 443 | ); |
| 384 | 444 | |
| 385 | 445 | P.selectMimetype(false, true); |
| 446 | + P.e.selectMimetype.addEventListener( |
| 447 | + 'change', |
| 448 | + function(e){ |
| 449 | + if(P.winfo){ |
| 450 | + P.winfo.mimetype = e.target.value; |
| 451 | + P.stashContentChange(true); |
| 452 | + } |
| 453 | + }, |
| 454 | + false |
| 455 | + ); |
| 456 | + |
| 457 | + |
| 386 | 458 | const selectFontSize = E('select[name=editor_font_size]'); |
| 387 | 459 | if(selectFontSize){ |
| 388 | 460 | selectFontSize.addEventListener( |
| 389 | 461 | "change",function(e){ |
| 390 | 462 | const ed = P.e.taEditor; |
| | @@ -399,22 +471,33 @@ |
| 399 | 471 | ); |
| 400 | 472 | } |
| 401 | 473 | |
| 402 | 474 | P.addEventListener( |
| 403 | 475 | // Clear certain views when new content is loaded/set |
| 404 | | - 'wikiedit-content-replaced', |
| 476 | + 'wiki-content-replaced', |
| 405 | 477 | ()=>D.clearElement(P.e.diffTarget, P.e.previewTarget) |
| 406 | 478 | ); |
| 407 | 479 | P.addEventListener( |
| 408 | | - // Clear certain views after a non-dry-run commit |
| 409 | | - 'wikiedit-saved', |
| 480 | + // Clear certain views after a save |
| 481 | + 'wiki-saved', |
| 410 | 482 | (e)=>{ |
| 411 | 483 | if(!e.detail.dryRun){ |
| 412 | 484 | D.clearElement(P.e.diffTarget, P.e.previewTarget); |
| 413 | 485 | } |
| 414 | 486 | } |
| 415 | 487 | ); |
| 488 | + P.addEventListener( |
| 489 | + // Update title on wiki page load |
| 490 | + 'wiki-page-loaded', |
| 491 | + function(ev){ |
| 492 | + const title = 'Wiki Editor: '+ev.detail.name; |
| 493 | + document.head.querySelector('title').innerText = title; |
| 494 | + document.querySelector('div.header .title').innerText = title; |
| 495 | + }, |
| 496 | + false |
| 497 | + ); |
| 498 | + WikiList.init( P.e.tabs.pageList.firstElementChild ); |
| 416 | 499 | }/*F.onPageLoad()*/); |
| 417 | 500 | |
| 418 | 501 | /** |
| 419 | 502 | Returns true if fossil.page.winfo is set, indicating that a page |
| 420 | 503 | has been loaded, else it reports an error and returns false. |
| | @@ -430,18 +513,18 @@ |
| 430 | 513 | /** |
| 431 | 514 | Getter (if called with no args) or setter (if passed an arg) for |
| 432 | 515 | the current file content. |
| 433 | 516 | |
| 434 | 517 | The setter form sets the content, dispatches a |
| 435 | | - 'wikiedit-content-replaced' event, and returns this object. |
| 518 | + 'wiki-content-replaced' event, and returns this object. |
| 436 | 519 | */ |
| 437 | 520 | P.wikiContent = function f(){ |
| 438 | 521 | if(0===arguments.length){ |
| 439 | 522 | return f.get(); |
| 440 | 523 | }else{ |
| 441 | 524 | f.set(arguments[0] || ''); |
| 442 | | - this.dispatchEvent('wikiedit-content-replaced', this); |
| 525 | + this.dispatchEvent('wiki-content-replaced', this); |
| 443 | 526 | return this; |
| 444 | 527 | } |
| 445 | 528 | }; |
| 446 | 529 | /* Default get/set impls for file content */ |
| 447 | 530 | P.wikiContent.get = function(){return P.e.taEditor.value}; |
| | @@ -494,12 +577,11 @@ |
| 494 | 577 | UI elements to reflect the loaded state. If passed no arguments |
| 495 | 578 | then it re-uses the values from the currently-loaded page, reloading |
| 496 | 579 | it (emitting an error message if no file is loaded). |
| 497 | 580 | |
| 498 | 581 | Returns this object, noting that the load is async. After loading |
| 499 | | - it triggers a 'wikiedit-file-loaded' event, passing it |
| 500 | | - this.winfo. |
| 582 | + it triggers a 'wiki-page-loaded' event, passing it this.winfo. |
| 501 | 583 | |
| 502 | 584 | If a locally-edited copy of the given file/rev is found, that |
| 503 | 585 | copy is used instead of one fetched from the server, but it is |
| 504 | 586 | still treated as a load event. |
| 505 | 587 | |
| | @@ -525,25 +607,30 @@ |
| 525 | 607 | const onload = (r)=>{ |
| 526 | 608 | delete self.winfo; |
| 527 | 609 | self.winfo = { |
| 528 | 610 | name: r.name, |
| 529 | 611 | mimetype: r.mimetype, |
| 530 | | - type: !!r.type |
| 612 | + type: r.type, |
| 613 | + version: r.version, |
| 614 | + parent: r.parent |
| 531 | 615 | }; |
| 616 | + self.previewNeedsUpdate = true; |
| 532 | 617 | self.e.selectMimetype.value = r.mimetype; |
| 533 | 618 | self.tabs.switchToTab(self.e.tabs.content); |
| 534 | 619 | self.wikiContent(r.content); |
| 535 | | - self.dispatchEvent('wiki-loaded', r); |
| 620 | + self.dispatchEvent('wiki-page-loaded', r); |
| 536 | 621 | }; |
| 537 | 622 | const semiWinfo = {name: name}; |
| 538 | 623 | const stashWinfo = this.getStashedWinfo(semiWinfo); |
| 539 | 624 | if(stashWinfo){ // fake a response from the stash... |
| 540 | 625 | this.winfo = stashWinfo; |
| 541 | 626 | onload({ |
| 542 | 627 | name: stashWinfo.name, |
| 543 | 628 | mimetype: stashWinfo.mimetype, |
| 544 | 629 | type: stashWinfo.type, |
| 630 | + version: stashWinfo.version, |
| 631 | + parent: stashWinfo.parent, |
| 545 | 632 | content: this.contentFromStash() |
| 546 | 633 | }); |
| 547 | 634 | F.message("Fetched from the local-edit storage:", |
| 548 | 635 | stashWinfo.name); |
| 549 | 636 | return this; |
| | @@ -600,11 +687,12 @@ |
| 600 | 687 | ).fetch('wikiajax/preview',{ |
| 601 | 688 | payload: fd, |
| 602 | 689 | onload: (r,header)=>{ |
| 603 | 690 | callback(r); |
| 604 | 691 | F.message('Updated preview.'); |
| 605 | | - P.dispatchEvent('wikiedit-preview-updated',{ |
| 692 | + P.previewNeedsUpdate = false; |
| 693 | + P.dispatchEvent('wiki-preview-updated',{ |
| 606 | 694 | mimetype: mimetype, |
| 607 | 695 | element: P.e.previewTarget |
| 608 | 696 | }); |
| 609 | 697 | }, |
| 610 | 698 | onerror: (e)=>{ |
| | @@ -644,18 +732,17 @@ |
| 644 | 732 | if(!affirmPageLoaded()) return this; |
| 645 | 733 | const content = this.wikiContent(), |
| 646 | 734 | self = this, |
| 647 | 735 | target = this.e.diffTarget; |
| 648 | 736 | const fd = new FormData(); |
| 649 | | - fd.append('filename',this.winfo.filename); |
| 650 | | - fd.append('checkin', this.winfo.checkin); |
| 737 | + fd.append('page',this.winfo.name); |
| 651 | 738 | fd.append('sbs', sbs ? 1 : 0); |
| 652 | 739 | fd.append('content',content); |
| 653 | 740 | if(this.e.selectDiffWS) fd.append('ws',this.e.selectDiffWS.value); |
| 654 | 741 | F.message( |
| 655 | 742 | "Fetching diff..." |
| 656 | | - ).fetch('ajax/wiki-diff',{ |
| 743 | + ).fetch('wikiajax/diff',{ |
| 657 | 744 | payload: fd, |
| 658 | 745 | onload: function(c){ |
| 659 | 746 | target.innerHTML = [ |
| 660 | 747 | "<div>Diff <code>[", |
| 661 | 748 | self.winfo.checkin, |
| | @@ -679,17 +766,19 @@ |
| 679 | 766 | both the winfo and content are updated. |
| 680 | 767 | */ |
| 681 | 768 | P.stashContentChange = function(onlyWinfo){ |
| 682 | 769 | if(affirmPageLoaded(true)){ |
| 683 | 770 | const wi = this.winfo; |
| 771 | + wi.mimetype = P.e.selectMimetype.value; |
| 684 | 772 | if(onlyWinfo && $stash.hasStashedContent(wi)){ |
| 685 | 773 | $stash.updateWinfo(wi); |
| 686 | 774 | }else{ |
| 687 | 775 | $stash.updateWinfo(wi, P.wikiContent()); |
| 688 | 776 | } |
| 689 | | - F.message("Stashed page ["+wi.name+"]."); |
| 777 | + F.message("Stashed change(s) to page ["+wi.name+"]."); |
| 690 | 778 | $stash.prune(); |
| 779 | + this.previewNeedsUpdate = true; |
| 691 | 780 | } |
| 692 | 781 | return this; |
| 693 | 782 | }; |
| 694 | 783 | |
| 695 | 784 | /** |
| | @@ -697,10 +786,11 @@ |
| 697 | 786 | F.storage. Returns this. |
| 698 | 787 | */ |
| 699 | 788 | P.unstashContent = function(){ |
| 700 | 789 | const winfo = arguments[0] || this.winfo; |
| 701 | 790 | if(winfo){ |
| 791 | + this.previewNeedsUpdate = true; |
| 702 | 792 | $stash.unstash(winfo); |
| 703 | 793 | //console.debug("Unstashed",winfo); |
| 704 | 794 | F.message("Unstashed page ["+winfo.name+"]."); |
| 705 | 795 | } |
| 706 | 796 | return this; |
| 707 | 797 | |