| | @@ -240,12 +240,12 @@ |
| 240 | 240 | console.warn("Pruned oldest local file edit entry:",e); |
| 241 | 241 | } |
| 242 | 242 | if(n) this._fireStashEvent(); |
| 243 | 243 | } |
| 244 | 244 | }; |
| 245 | | - $stash.prune.defaultMaxCount = P.config.defaultMaxStashSize; |
| 246 | | - |
| 245 | + $stash.prune.defaultMaxCount = P.config.defaultMaxStashSize || 10; |
| 246 | + P.$stash = $stash /* we have to expose this for the new-page case :/ */; |
| 247 | 247 | |
| 248 | 248 | /** |
| 249 | 249 | Internal workaround to select the current preview mode |
| 250 | 250 | and fire a change event if the value actually changes |
| 251 | 251 | or if forceEvent is truthy. |
| | @@ -263,16 +263,23 @@ |
| 263 | 263 | } |
| 264 | 264 | }; |
| 265 | 265 | |
| 266 | 266 | const WikiList = { |
| 267 | 267 | e: {}, |
| 268 | + /** Update OPTION elements to reflect whether the page has |
| 269 | + local changes or is new/unsaved. */ |
| 268 | 270 | refreshStashMarks: function(){ |
| 269 | 271 | this.e.select.querySelectorAll( |
| 270 | 272 | 'option' |
| 271 | 273 | ).forEach(function(o){ |
| 272 | | - if($stash.hasStashedContent(o.value)) D.addClass(o, 'stashed'); |
| 273 | | - else D.removeClass(o, 'stashed'); |
| 274 | + const stashed = $stash.getWinfo({name:o.value}); |
| 275 | + if(stashed){ |
| 276 | + const isNew = 'sandbox'===stashed.type ? false : !stashed.version; |
| 277 | + D.addClass(o, isNew ? 'stashed-new' :'stashed'); |
| 278 | + }else{ |
| 279 | + D.removeClass(o, 'stashed', 'stashed-new'); |
| 280 | + } |
| 274 | 281 | }); |
| 275 | 282 | }, |
| 276 | 283 | init: function(parentElem){ |
| 277 | 284 | const sel = D.select(), btn = D.button("Reload page list"); |
| 278 | 285 | this.e.select = sel; |
| | @@ -280,25 +287,45 @@ |
| 280 | 287 | D.clearElement(parentElem); |
| 281 | 288 | D.append( |
| 282 | 289 | parentElem, btn, |
| 283 | 290 | D.append(D.span(), "Select a page to edit:"), |
| 284 | 291 | sel, |
| 285 | | - D.append(D.span(), "* = local edits exist."), |
| 292 | + D.append(D.span(), "* = local edits exist"), |
| 293 | + D.append(D.span(), "+ = new/unsaved page") |
| 286 | 294 | ); |
| 287 | 295 | D.attr(sel, 'size', 10); |
| 288 | 296 | D.option(D.disable(D.clearElement(sel)), "Loading..."); |
| 289 | 297 | const self = this; |
| 290 | 298 | btn.addEventListener( |
| 291 | 299 | 'click', |
| 292 | | - function(){ |
| 300 | + function click(){ |
| 301 | + if(!click.sorticase){ |
| 302 | + click.sorticase = function(l,r){ |
| 303 | + l = l.toLowerCase(); |
| 304 | + r = r.toLowerCase(); |
| 305 | + return l<=r ? -1 : 1; |
| 306 | + }; |
| 307 | + } |
| 293 | 308 | F.fetch('wikiajax/list',{ |
| 294 | 309 | responseType: 'json', |
| 295 | 310 | onload: function(list){ |
| 311 | + /* Jump through some hoops to integrate new/unsaved |
| 312 | + pages into the list of existing pages... We use a map |
| 313 | + as an intermediary in order to filter out any local-stash |
| 314 | + dupes from server-side copies. */ |
| 315 | + const map = {}, ndx = $stash.getIndex(); |
| 296 | 316 | D.clearElement(sel); |
| 297 | | - list.forEach((e)=>D.option(sel, e)); |
| 298 | | - //D.option(sel, "sandbox"); |
| 317 | + list.forEach((name)=>map[name] = true); |
| 318 | + Object.keys(ndx).forEach(function(key){ |
| 319 | + const winfo = ndx[key]; |
| 320 | + if(!winfo.version/*new page*/) map[winfo.name] = true; |
| 321 | + }); |
| 322 | + Object.keys(map) |
| 323 | + .sort(click.sorticase) |
| 324 | + .forEach((name)=>D.option(sel, name)); |
| 299 | 325 | D.enable(sel); |
| 326 | + if(P.winfo) sel.value = P.winfo.name; |
| 300 | 327 | self.refreshStashMarks(); |
| 301 | 328 | } |
| 302 | 329 | }); |
| 303 | 330 | }, |
| 304 | 331 | false |
| | @@ -338,11 +365,15 @@ |
| 338 | 365 | toDisable: undefined /* elements to disable during ajax activity */ |
| 339 | 366 | }; |
| 340 | 367 | F.fetch.beforesend = function f(){ |
| 341 | 368 | if(!ajaxState.toDisable){ |
| 342 | 369 | ajaxState.toDisable = document.querySelectorAll( |
| 343 | | - 'button, input, select, textarea' |
| 370 | + ['button:not([disabled])', |
| 371 | + 'input:not([disabled])', |
| 372 | + 'select:not([disabled])', |
| 373 | + 'textarea:not([disabled])' |
| 374 | + ].join(',') |
| 344 | 375 | ); |
| 345 | 376 | } |
| 346 | 377 | if(1===++ajaxState.count){ |
| 347 | 378 | D.addClass(document.body, 'waiting'); |
| 348 | 379 | D.disable(ajaxState.toDisable); |
| | @@ -433,11 +464,21 @@ |
| 433 | 464 | if(0) P.e.btnCommit.addEventListener( |
| 434 | 465 | "click",(e)=>P.commit(), false |
| 435 | 466 | ); |
| 436 | 467 | F.confirmer(P.e.btnReload, { |
| 437 | 468 | confirmText: "Really reload, losing edits?", |
| 438 | | - onconfirm: (e)=>P.unstashContent().loadPage(), |
| 469 | + onconfirm: function(e){ |
| 470 | + const w = P.winfo; |
| 471 | + if(!w){ |
| 472 | + F.error("No page loaded."); |
| 473 | + return; |
| 474 | + }else if(!w.version){ |
| 475 | + F.error("Cannot reload a new/unsaved page."); |
| 476 | + return; |
| 477 | + } |
| 478 | + P.unstashContent().loadPage(); |
| 479 | + }, |
| 439 | 480 | ticks: 3 |
| 440 | 481 | }); |
| 441 | 482 | P.e.taEditor.addEventListener( |
| 442 | 483 | 'change', ()=>P.stashContentChange(), false |
| 443 | 484 | ); |
| | @@ -451,11 +492,10 @@ |
| 451 | 492 | P.stashContentChange(true); |
| 452 | 493 | } |
| 453 | 494 | }, |
| 454 | 495 | false |
| 455 | 496 | ); |
| 456 | | - |
| 457 | 497 | |
| 458 | 498 | const selectFontSize = E('select[name=editor_font_size]'); |
| 459 | 499 | if(selectFontSize){ |
| 460 | 500 | selectFontSize.addEventListener( |
| 461 | 501 | "change",function(e){ |
| | @@ -472,32 +512,43 @@ |
| 472 | 512 | } |
| 473 | 513 | |
| 474 | 514 | P.addEventListener( |
| 475 | 515 | // Clear certain views when new content is loaded/set |
| 476 | 516 | 'wiki-content-replaced', |
| 477 | | - ()=>D.clearElement(P.e.diffTarget, P.e.previewTarget) |
| 517 | + ()=>{ |
| 518 | + P.previewNeedsUpdate = true; |
| 519 | + D.clearElement(P.e.diffTarget, P.e.previewTarget); |
| 520 | + } |
| 478 | 521 | ); |
| 479 | 522 | P.addEventListener( |
| 480 | 523 | // Clear certain views after a save |
| 481 | 524 | 'wiki-saved', |
| 482 | 525 | (e)=>{ |
| 483 | | - if(!e.detail.dryRun){ |
| 484 | | - D.clearElement(P.e.diffTarget, P.e.previewTarget); |
| 485 | | - } |
| 526 | + D.clearElement(P.e.diffTarget, P.e.previewTarget); |
| 527 | + // TODO: replace preview with new content |
| 486 | 528 | } |
| 487 | 529 | ); |
| 530 | + WikiList.init( P.e.tabs.pageList.firstElementChild ); |
| 488 | 531 | P.addEventListener( |
| 489 | | - // Update title on wiki page load |
| 532 | + // Update various state on wiki page load |
| 490 | 533 | 'wiki-page-loaded', |
| 491 | 534 | 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; |
| 535 | + delete P.winfo; |
| 536 | + const winfo = ev.detail; |
| 537 | + P.winfo = winfo; |
| 538 | + P.previewNeedsUpdate = true; |
| 539 | + P.e.selectMimetype.value = winfo.mimetype; |
| 540 | + P.tabs.switchToTab(P.e.tabs.content); |
| 541 | + P.wikiContent(winfo.content || ''); |
| 542 | + WikiList.e.select.value = winfo.name; |
| 543 | + if(!winfo.version){ |
| 544 | + F.error('You are editing a new, unsaved page:',winfo.name); |
| 545 | + } |
| 546 | + P.updatePageTitle(); |
| 495 | 547 | }, |
| 496 | 548 | false |
| 497 | 549 | ); |
| 498 | | - WikiList.init( P.e.tabs.pageList.firstElementChild ); |
| 499 | 550 | }/*F.onPageLoad()*/); |
| 500 | 551 | |
| 501 | 552 | /** |
| 502 | 553 | Returns true if fossil.page.winfo is set, indicating that a page |
| 503 | 554 | has been loaded, else it reports an error and returns false. |
| | @@ -507,10 +558,29 @@ |
| 507 | 558 | */ |
| 508 | 559 | const affirmPageLoaded = function(quiet){ |
| 509 | 560 | if(!P.winfo && !quiet) F.error("No wiki page is loaded."); |
| 510 | 561 | return !!P.winfo; |
| 511 | 562 | }; |
| 563 | + |
| 564 | + /** |
| 565 | + Update the page title and header based on the state |
| 566 | + of this.winfo. A no-op if this.winfo is not set. |
| 567 | + */ |
| 568 | + P.updatePageTitle = function f(){ |
| 569 | + if(!affirmPageLoaded(true)) return; |
| 570 | + if(!f.titleElement){ |
| 571 | + f.titleElement = document.head.querySelector('title'); |
| 572 | + f.pageTitleHeader = document.querySelector('div.header .title'); |
| 573 | + } |
| 574 | + var title = ['Wiki Editor:']; |
| 575 | + if(!P.winfo.version) title.push('[+]'); |
| 576 | + else if($stash.getWinfo(P.winfo)) title.push('[*]') |
| 577 | + title.push(P.winfo.name); |
| 578 | + title = title.join(' '); |
| 579 | + f.titleElement.innerText = title; |
| 580 | + f.pageTitleHeader.innerText = title; |
| 581 | + }; |
| 512 | 582 | |
| 513 | 583 | /** |
| 514 | 584 | Getter (if called with no args) or setter (if passed an arg) for |
| 515 | 585 | the current file content. |
| 516 | 586 | |
| | @@ -601,37 +671,20 @@ |
| 601 | 671 | }else if(1===arguments.length && 'string' !== typeof name){ |
| 602 | 672 | /* Assume winfo-like object */ |
| 603 | 673 | const arg = arguments[0]; |
| 604 | 674 | name = arg.name; |
| 605 | 675 | } |
| 606 | | - const self = this; |
| 607 | | - const onload = (r)=>{ |
| 608 | | - delete self.winfo; |
| 609 | | - self.winfo = { |
| 610 | | - name: r.name, |
| 611 | | - mimetype: r.mimetype, |
| 612 | | - type: r.type, |
| 613 | | - version: r.version, |
| 614 | | - parent: r.parent |
| 615 | | - }; |
| 616 | | - self.previewNeedsUpdate = true; |
| 617 | | - self.e.selectMimetype.value = r.mimetype; |
| 618 | | - self.tabs.switchToTab(self.e.tabs.content); |
| 619 | | - self.wikiContent(r.content); |
| 620 | | - self.dispatchEvent('wiki-page-loaded', r); |
| 621 | | - }; |
| 622 | | - const semiWinfo = {name: name}; |
| 623 | | - const stashWinfo = this.getStashedWinfo(semiWinfo); |
| 676 | + const onload = (r)=>this.dispatchEvent('wiki-page-loaded', r); |
| 677 | + const stashWinfo = this.getStashedWinfo({name: name}); |
| 624 | 678 | if(stashWinfo){ // fake a response from the stash... |
| 625 | | - this.winfo = stashWinfo; |
| 626 | 679 | onload({ |
| 627 | 680 | name: stashWinfo.name, |
| 628 | 681 | mimetype: stashWinfo.mimetype, |
| 629 | 682 | type: stashWinfo.type, |
| 630 | 683 | version: stashWinfo.version, |
| 631 | 684 | parent: stashWinfo.parent, |
| 632 | | - content: this.contentFromStash() |
| 685 | + content: $stash.stashedContent(stashWinfo) |
| 633 | 686 | }); |
| 634 | 687 | F.message("Fetched from the local-edit storage:", |
| 635 | 688 | stashWinfo.name); |
| 636 | 689 | return this; |
| 637 | 690 | } |
| | @@ -774,10 +827,11 @@ |
| 774 | 827 | $stash.updateWinfo(wi); |
| 775 | 828 | }else{ |
| 776 | 829 | $stash.updateWinfo(wi, P.wikiContent()); |
| 777 | 830 | } |
| 778 | 831 | F.message("Stashed change(s) to page ["+wi.name+"]."); |
| 832 | + P.updatePageTitle(); |
| 779 | 833 | $stash.prune(); |
| 780 | 834 | this.previewNeedsUpdate = true; |
| 781 | 835 | } |
| 782 | 836 | return this; |
| 783 | 837 | }; |
| | @@ -818,8 +872,7 @@ |
| 818 | 872 | filename/checkin values), return it, else return undefined. |
| 819 | 873 | */ |
| 820 | 874 | P.getStashedWinfo = function(winfo){ |
| 821 | 875 | return $stash.getWinfo(winfo); |
| 822 | 876 | }; |
| 823 | | - P.$stash = $stash /* only for development/debugging */; |
| 824 | 877 | |
| 825 | 878 | })(window.fossil); |
| 826 | 879 | |