Fossil SCM
Style tweaks and re-did how the OPTION elements are marked is-new/is-modified so that the mobile browsers can show that state.
Commit
d9f4b6dbedbe051de94fad5f91ce6fa0e54571e966d4db68be20078f79d6cb00
Parent
fb77abd3b5b116b…
2 files changed
+76
-47
+15
-11
+76
-47
| --- src/fossil.page.wikiedit.js | ||
| +++ src/fossil.page.wikiedit.js | ||
| @@ -65,13 +65,11 @@ | ||
| 65 | 65 | const E = (s)=>document.querySelector(s), |
| 66 | 66 | D = F.dom, |
| 67 | 67 | P = F.page; |
| 68 | 68 | |
| 69 | 69 | P.config = { |
| 70 | - /* Symbolic markers to denote certain edit state. Note that | |
| 71 | - the symbols themselves are *actually* defined in CSS, so if | |
| 72 | - they're changed there they also need to be changed here.*/ | |
| 70 | + /* Symbolic markers to denote certain edit state. */ | |
| 73 | 71 | editStateMarkers: { |
| 74 | 72 | isNew: '[+]', |
| 75 | 73 | isModified: '[*]' |
| 76 | 74 | } |
| 77 | 75 | }; |
| @@ -268,10 +266,37 @@ | ||
| 268 | 266 | if(forceEvent){ |
| 269 | 267 | // Force UI update |
| 270 | 268 | s.dispatchEvent(new Event('change',{target:s})); |
| 271 | 269 | } |
| 272 | 270 | }; |
| 271 | + | |
| 272 | + /** Internal helper to get an edit status indicator for the given winfo object. */ | |
| 273 | + const getEditMarker = function f(winfo, textOnly){ | |
| 274 | + const esm = P.config.editStateMarkers; | |
| 275 | + if(1===winfo){ /* force is-new */ | |
| 276 | + return textOnly ? esm.isNew : | |
| 277 | + D.addClass(D.append(D.span(),esm.isNew), 'is-new'); | |
| 278 | + }else if(2===winfo){ /* force is-modified */ | |
| 279 | + return textOnly ? esm.isModified : | |
| 280 | + D.addClass(D.append(D.span(),esm.isModified), 'is-modified'); | |
| 281 | + }else if(winfo && winfo.version){ /* is existing page modified? */ | |
| 282 | + if($stash.getWinfo(winfo)){ | |
| 283 | + return textOnly ? esm.isModified : | |
| 284 | + D.addClass(D.append(D.span(),esm.isModified), 'is-modified'); | |
| 285 | + } | |
| 286 | + } | |
| 287 | + else if(winfo){ /* is new non-sandbox or is modified sandbox? */ | |
| 288 | + if('sandbox'!==winfo.type){ | |
| 289 | + return textOnly ? esm.isNew : | |
| 290 | + D.addClass(D.append(D.span(),esm.isNew), 'is-new'); | |
| 291 | + }else if($stash.getWinfo(winfo)){ | |
| 292 | + return textOnly ? esm.isModified : | |
| 293 | + D.addClass(D.append(D.span(),esm.isModified), 'is-modified'); | |
| 294 | + } | |
| 295 | + } | |
| 296 | + return textOnly ? '' : D.span(); | |
| 297 | + }; | |
| 273 | 298 | |
| 274 | 299 | /** |
| 275 | 300 | Sets up and maintains the widgets for the list of wiki pages. |
| 276 | 301 | */ |
| 277 | 302 | const WikiList = { |
| @@ -288,26 +313,43 @@ | ||
| 288 | 313 | de/serialization. We need this map to support the add-new-page |
| 289 | 314 | feature, to give us a way to check for dupes without asking |
| 290 | 315 | the server or walking through the whole selection list. |
| 291 | 316 | */} |
| 292 | 317 | }, |
| 293 | - /** Updates OPTION elements to reflect whether the page has | |
| 294 | - local changes or is new/unsaved. */ | |
| 295 | - refreshStashMarks: function(){ | |
| 318 | + /** | |
| 319 | + Updates OPTION elements to reflect whether the page has local | |
| 320 | + changes or is new/unsaved. This implementation is horribly | |
| 321 | + inefficient, in that we have to walk and validate the whole | |
| 322 | + list for each stash-level change. | |
| 323 | + | |
| 324 | + Reminder to self: in order to mark is-edited/is-new state we | |
| 325 | + have to update the OPTION element's inner text to reflect the | |
| 326 | + is-modified/is-new flags, rather than use CSS classes to tag | |
| 327 | + them, because mobile Chrome can neither restyle OPTION elements | |
| 328 | + no render ::before content on them. We *also* use CSS tags, but | |
| 329 | + they aren't sufficient for the mobile browsers. | |
| 330 | + */ | |
| 331 | + _refreshStashMarks: function callee(){ | |
| 332 | + if(!callee.eachOpt){ | |
| 333 | + const self = this; | |
| 334 | + callee.eachOpt = function(key){ | |
| 335 | + const opt = self.e.select.options[key]; | |
| 336 | + const stashed = $stash.getWinfo({name:opt.value}); | |
| 337 | + var prefix = ''; | |
| 338 | + if(stashed){ | |
| 339 | + const isNew = 'sandbox'===stashed.type ? false : !stashed.version; | |
| 340 | + prefix = getEditMarker(isNew ? 1 : 2, true); | |
| 341 | + D.addClass(opt, isNew ? 'stashed-new' : 'stashed'); | |
| 342 | + }else{ | |
| 343 | + D.removeClass(opt, 'stashed', 'stashed-new'); | |
| 344 | + } | |
| 345 | + opt.innerText = prefix + opt.value; | |
| 346 | + self.cache.names[opt.value] = true; | |
| 347 | + }; | |
| 348 | + } | |
| 296 | 349 | this.cache.names = {/*must reset it to acount for local page removals*/}; |
| 297 | - const select = this.e.select, self = this; | |
| 298 | - Object.keys(select.options).forEach(function(key){ | |
| 299 | - const opt = select.options[key]; | |
| 300 | - const stashed = $stash.getWinfo({name:opt.value}); | |
| 301 | - if(stashed){ | |
| 302 | - const isNew = 'sandbox'===stashed.type ? false : !stashed.version; | |
| 303 | - D.addClass(opt, isNew ? 'stashed-new' :'stashed'); | |
| 304 | - }else{ | |
| 305 | - D.removeClass(opt, 'stashed', 'stashed-new'); | |
| 306 | - } | |
| 307 | - self.cache.names[opt.value] = true; | |
| 308 | - }); | |
| 350 | + Object.keys(this.e.select.options).forEach(callee.eachOpt); | |
| 309 | 351 | }, |
| 310 | 352 | /** Removes the given wiki page entry from the page selection |
| 311 | 353 | list, if it's in the list. */ |
| 312 | 354 | removeEntry: function(name){ |
| 313 | 355 | const sel = this.e.select; |
| @@ -320,11 +362,12 @@ | ||
| 320 | 362 | sel.selectedIndex = ndx; |
| 321 | 363 | }, |
| 322 | 364 | |
| 323 | 365 | /** |
| 324 | 366 | Rebuilds the selection list. Necessary when it's loaded from |
| 325 | - the server or we locally create a new page. */ | |
| 367 | + the server or we locally create a new page. | |
| 368 | + */ | |
| 326 | 369 | _rebuildList: function callee(){ |
| 327 | 370 | /* Jump through some hoops to integrate new/unsaved |
| 328 | 371 | pages into the list of existing pages... We use a map |
| 329 | 372 | as an intermediary in order to filter out any local-stash |
| 330 | 373 | dupes from server-side copies. */ |
| @@ -356,11 +399,11 @@ | ||
| 356 | 399 | const cb = self.e.filterCheckboxes[wtype]; |
| 357 | 400 | if(cb && !cb.checked) D.addClass(opt, 'hidden'); |
| 358 | 401 | }); |
| 359 | 402 | D.enable(sel); |
| 360 | 403 | if(P.winfo) sel.value = P.winfo.name; |
| 361 | - this.refreshStashMarks(); | |
| 404 | + this._refreshStashMarks(); | |
| 362 | 405 | }, |
| 363 | 406 | |
| 364 | 407 | /** Loads the page list and populates the selection list. */ |
| 365 | 408 | loadList: function callee(){ |
| 366 | 409 | delete this.pageMap; |
| @@ -453,11 +496,11 @@ | ||
| 453 | 496 | D.attr(sel, 'size', 15); |
| 454 | 497 | D.option(D.disable(D.clearElement(sel)), "Loading..."); |
| 455 | 498 | |
| 456 | 499 | /** Set up filter checkboxes for the various types |
| 457 | 500 | of wiki pages... */ |
| 458 | - const fsFilter = D.fieldset("Wiki page types"), | |
| 501 | + const fsFilter = D.fieldset("Page types"), | |
| 459 | 502 | fsFilterBody = D.div(), |
| 460 | 503 | filters = ['normal', 'branch', 'checkin', 'tag'] |
| 461 | 504 | ; |
| 462 | 505 | D.append(fsFilter, fsFilterBody); |
| 463 | 506 | D.addClass(fsFilterBody, 'flex-container', 'flex-column', 'stretch'); |
| @@ -491,14 +534,12 @@ | ||
| 491 | 534 | fsLegendBody = D.div(); |
| 492 | 535 | D.append(fsLegend, fsLegendBody); |
| 493 | 536 | D.addClass(fsLegendBody, 'flex-container', 'flex-column', 'stretch'); |
| 494 | 537 | D.append( |
| 495 | 538 | fsLegendBody, |
| 496 | - D.append(D.span(), P.config.editStateMarkers.isModified, | |
| 497 | - " = page has local edits"), | |
| 498 | - D.append(D.span(), P.config.editStateMarkers.isNew, | |
| 499 | - " = page is new/unsaved") | |
| 539 | + D.append(D.span(), getEditMarker(1,false)," = page is new/unsaved"), | |
| 540 | + D.append(D.span(), getEditMarker(2,false)," = page has local edits") | |
| 500 | 541 | ); |
| 501 | 542 | |
| 502 | 543 | const fsNewPage = D.fieldset("Create new page"), |
| 503 | 544 | fsNewPageBody = D.div(), |
| 504 | 545 | newPageName = D.input('text'), |
| @@ -528,11 +569,11 @@ | ||
| 528 | 569 | btn.addEventListener('click', ()=>this.loadList(), false); |
| 529 | 570 | this.loadList(); |
| 530 | 571 | const onSelect = (e)=>P.loadPage(e.target.value); |
| 531 | 572 | sel.addEventListener('change', onSelect, false); |
| 532 | 573 | sel.addEventListener('dblclick', onSelect, false); |
| 533 | - F.page.addEventListener('wiki-stash-updated', ()=>this.refreshStashMarks()); | |
| 574 | + F.page.addEventListener('wiki-stash-updated', ()=>this._refreshStashMarks()); | |
| 534 | 575 | delete this.init; |
| 535 | 576 | } |
| 536 | 577 | }; |
| 537 | 578 | |
| 538 | 579 | /** |
| @@ -684,15 +725,15 @@ | ||
| 684 | 725 | "then use the Discard button."); |
| 685 | 726 | return; |
| 686 | 727 | } |
| 687 | 728 | P.unstashContent() |
| 688 | 729 | if(w.version || w.type==='sandbox'){ |
| 689 | - P.loadPage(); | |
| 730 | + P.loadPage(w); | |
| 690 | 731 | }else{ |
| 691 | 732 | WikiList.removeEntry(w.name); |
| 692 | - P.updatePageTitle(); | |
| 693 | 733 | delete P.winfo; |
| 734 | + P.updatePageTitle(); | |
| 694 | 735 | F.message("Discarded new page ["+w.name+"]."); |
| 695 | 736 | } |
| 696 | 737 | }, |
| 697 | 738 | ticks: 3 |
| 698 | 739 | }); |
| @@ -792,11 +833,11 @@ | ||
| 792 | 833 | if(!P.winfo && !quiet) F.error("No wiki page is loaded."); |
| 793 | 834 | return !!P.winfo; |
| 794 | 835 | }; |
| 795 | 836 | |
| 796 | 837 | /** Updates the in-tab title/edit status information */ |
| 797 | - P.updateEditStatus = function f(editFlag/*for use by updatePageTitle() only*/){ | |
| 838 | + P.updateEditStatus = function f(){ | |
| 798 | 839 | if(!f.eLinks){ |
| 799 | 840 | f.eName = P.e.editStatus.querySelector('span.name'); |
| 800 | 841 | f.eLinks = P.e.editStatus.querySelector('span.links'); |
| 801 | 842 | } |
| 802 | 843 | const wi = this.winfo; |
| @@ -803,15 +844,11 @@ | ||
| 803 | 844 | D.clearElement(f.eName, f.eLinks); |
| 804 | 845 | if(!wi){ |
| 805 | 846 | D.append(f.eName, '(no page loaded)'); |
| 806 | 847 | return; |
| 807 | 848 | } |
| 808 | - var marker = editFlag || ''; | |
| 809 | - if(0===arguments){ | |
| 810 | - if(!wi.version && 'sandbox'!==wi.type) marker = P.config.editStateMarkers.isNew; | |
| 811 | - else if($stash.getWinfo(wi)) marker = P.config.editStateMarkers.isModified; | |
| 812 | - } | |
| 849 | + var marker = getEditMarker(wi, false); | |
| 813 | 850 | D.append(f.eName,marker,wi.name,); |
| 814 | 851 | if(wi.version){ |
| 815 | 852 | D.append( |
| 816 | 853 | f.eLinks, |
| 817 | 854 | D.a(F.repoUrl('whistory',{name:wi.name}),'[history]'), |
| @@ -827,21 +864,14 @@ | ||
| 827 | 864 | */ |
| 828 | 865 | P.updatePageTitle = function f(){ |
| 829 | 866 | if(!f.titleElement){ |
| 830 | 867 | f.titleElement = document.head.querySelector('title'); |
| 831 | 868 | } |
| 832 | - var title, marker = ''; | |
| 833 | - const wi = P.winfo; | |
| 834 | - if(wi){ | |
| 835 | - if(!wi.version && 'sandbox'!==wi.type) marker = P.config.editStateMarkers.isNew; | |
| 836 | - else if($stash.getWinfo(wi)) marker = P.config.editStateMarkers.isModified; | |
| 837 | - title = wi.name; | |
| 838 | - }else{ | |
| 839 | - title = 'no page loaded'; | |
| 840 | - } | |
| 869 | + const wi = P.winfo, marker = getEditMarker(wi, true), | |
| 870 | + title = wi ? wi.name : 'no page loaded'; | |
| 841 | 871 | f.titleElement.innerText = 'Wiki Editor: ' + marker + title; |
| 842 | - this.updateEditStatus(marker); | |
| 872 | + this.updateEditStatus(); | |
| 843 | 873 | return this; |
| 844 | 874 | }; |
| 845 | 875 | |
| 846 | 876 | /** |
| 847 | 877 | Change the save button depending on whether we have stuff to save |
| @@ -848,11 +878,11 @@ | ||
| 848 | 878 | or not. |
| 849 | 879 | */ |
| 850 | 880 | P.updateSaveButton = function(){ |
| 851 | 881 | if(!this.winfo || !this.getStashedWinfo(this.winfo)){ |
| 852 | 882 | D.disable(this.e.btnSave).innerText = |
| 853 | - "There are no changes to save"; | |
| 883 | + "No changes to save"; | |
| 854 | 884 | }else{ |
| 855 | 885 | D.enable(this.e.btnSave).innerText = "Save changes"; |
| 856 | 886 | } |
| 857 | 887 | return this; |
| 858 | 888 | }; |
| @@ -951,12 +981,11 @@ | ||
| 951 | 981 | name = arg.name; |
| 952 | 982 | } |
| 953 | 983 | const onload = (r)=>this.dispatchEvent('wiki-page-loaded', r); |
| 954 | 984 | const stashWinfo = this.getStashedWinfo({name: name}); |
| 955 | 985 | if(stashWinfo){ // fake a response from the stash... |
| 956 | - F.message("Fetched from the local-edit storage:", | |
| 957 | - stashWinfo.name); | |
| 986 | + F.message("Fetched from the local-edit storage:", stashWinfo.name); | |
| 958 | 987 | onload({ |
| 959 | 988 | name: stashWinfo.name, |
| 960 | 989 | mimetype: stashWinfo.mimetype, |
| 961 | 990 | type: stashWinfo.type, |
| 962 | 991 | version: stashWinfo.version, |
| 963 | 992 |
| --- src/fossil.page.wikiedit.js | |
| +++ src/fossil.page.wikiedit.js | |
| @@ -65,13 +65,11 @@ | |
| 65 | const E = (s)=>document.querySelector(s), |
| 66 | D = F.dom, |
| 67 | P = F.page; |
| 68 | |
| 69 | P.config = { |
| 70 | /* Symbolic markers to denote certain edit state. Note that |
| 71 | the symbols themselves are *actually* defined in CSS, so if |
| 72 | they're changed there they also need to be changed here.*/ |
| 73 | editStateMarkers: { |
| 74 | isNew: '[+]', |
| 75 | isModified: '[*]' |
| 76 | } |
| 77 | }; |
| @@ -268,10 +266,37 @@ | |
| 268 | if(forceEvent){ |
| 269 | // Force UI update |
| 270 | s.dispatchEvent(new Event('change',{target:s})); |
| 271 | } |
| 272 | }; |
| 273 | |
| 274 | /** |
| 275 | Sets up and maintains the widgets for the list of wiki pages. |
| 276 | */ |
| 277 | const WikiList = { |
| @@ -288,26 +313,43 @@ | |
| 288 | de/serialization. We need this map to support the add-new-page |
| 289 | feature, to give us a way to check for dupes without asking |
| 290 | the server or walking through the whole selection list. |
| 291 | */} |
| 292 | }, |
| 293 | /** Updates OPTION elements to reflect whether the page has |
| 294 | local changes or is new/unsaved. */ |
| 295 | refreshStashMarks: function(){ |
| 296 | this.cache.names = {/*must reset it to acount for local page removals*/}; |
| 297 | const select = this.e.select, self = this; |
| 298 | Object.keys(select.options).forEach(function(key){ |
| 299 | const opt = select.options[key]; |
| 300 | const stashed = $stash.getWinfo({name:opt.value}); |
| 301 | if(stashed){ |
| 302 | const isNew = 'sandbox'===stashed.type ? false : !stashed.version; |
| 303 | D.addClass(opt, isNew ? 'stashed-new' :'stashed'); |
| 304 | }else{ |
| 305 | D.removeClass(opt, 'stashed', 'stashed-new'); |
| 306 | } |
| 307 | self.cache.names[opt.value] = true; |
| 308 | }); |
| 309 | }, |
| 310 | /** Removes the given wiki page entry from the page selection |
| 311 | list, if it's in the list. */ |
| 312 | removeEntry: function(name){ |
| 313 | const sel = this.e.select; |
| @@ -320,11 +362,12 @@ | |
| 320 | sel.selectedIndex = ndx; |
| 321 | }, |
| 322 | |
| 323 | /** |
| 324 | Rebuilds the selection list. Necessary when it's loaded from |
| 325 | the server or we locally create a new page. */ |
| 326 | _rebuildList: function callee(){ |
| 327 | /* Jump through some hoops to integrate new/unsaved |
| 328 | pages into the list of existing pages... We use a map |
| 329 | as an intermediary in order to filter out any local-stash |
| 330 | dupes from server-side copies. */ |
| @@ -356,11 +399,11 @@ | |
| 356 | const cb = self.e.filterCheckboxes[wtype]; |
| 357 | if(cb && !cb.checked) D.addClass(opt, 'hidden'); |
| 358 | }); |
| 359 | D.enable(sel); |
| 360 | if(P.winfo) sel.value = P.winfo.name; |
| 361 | this.refreshStashMarks(); |
| 362 | }, |
| 363 | |
| 364 | /** Loads the page list and populates the selection list. */ |
| 365 | loadList: function callee(){ |
| 366 | delete this.pageMap; |
| @@ -453,11 +496,11 @@ | |
| 453 | D.attr(sel, 'size', 15); |
| 454 | D.option(D.disable(D.clearElement(sel)), "Loading..."); |
| 455 | |
| 456 | /** Set up filter checkboxes for the various types |
| 457 | of wiki pages... */ |
| 458 | const fsFilter = D.fieldset("Wiki page types"), |
| 459 | fsFilterBody = D.div(), |
| 460 | filters = ['normal', 'branch', 'checkin', 'tag'] |
| 461 | ; |
| 462 | D.append(fsFilter, fsFilterBody); |
| 463 | D.addClass(fsFilterBody, 'flex-container', 'flex-column', 'stretch'); |
| @@ -491,14 +534,12 @@ | |
| 491 | fsLegendBody = D.div(); |
| 492 | D.append(fsLegend, fsLegendBody); |
| 493 | D.addClass(fsLegendBody, 'flex-container', 'flex-column', 'stretch'); |
| 494 | D.append( |
| 495 | fsLegendBody, |
| 496 | D.append(D.span(), P.config.editStateMarkers.isModified, |
| 497 | " = page has local edits"), |
| 498 | D.append(D.span(), P.config.editStateMarkers.isNew, |
| 499 | " = page is new/unsaved") |
| 500 | ); |
| 501 | |
| 502 | const fsNewPage = D.fieldset("Create new page"), |
| 503 | fsNewPageBody = D.div(), |
| 504 | newPageName = D.input('text'), |
| @@ -528,11 +569,11 @@ | |
| 528 | btn.addEventListener('click', ()=>this.loadList(), false); |
| 529 | this.loadList(); |
| 530 | const onSelect = (e)=>P.loadPage(e.target.value); |
| 531 | sel.addEventListener('change', onSelect, false); |
| 532 | sel.addEventListener('dblclick', onSelect, false); |
| 533 | F.page.addEventListener('wiki-stash-updated', ()=>this.refreshStashMarks()); |
| 534 | delete this.init; |
| 535 | } |
| 536 | }; |
| 537 | |
| 538 | /** |
| @@ -684,15 +725,15 @@ | |
| 684 | "then use the Discard button."); |
| 685 | return; |
| 686 | } |
| 687 | P.unstashContent() |
| 688 | if(w.version || w.type==='sandbox'){ |
| 689 | P.loadPage(); |
| 690 | }else{ |
| 691 | WikiList.removeEntry(w.name); |
| 692 | P.updatePageTitle(); |
| 693 | delete P.winfo; |
| 694 | F.message("Discarded new page ["+w.name+"]."); |
| 695 | } |
| 696 | }, |
| 697 | ticks: 3 |
| 698 | }); |
| @@ -792,11 +833,11 @@ | |
| 792 | if(!P.winfo && !quiet) F.error("No wiki page is loaded."); |
| 793 | return !!P.winfo; |
| 794 | }; |
| 795 | |
| 796 | /** Updates the in-tab title/edit status information */ |
| 797 | P.updateEditStatus = function f(editFlag/*for use by updatePageTitle() only*/){ |
| 798 | if(!f.eLinks){ |
| 799 | f.eName = P.e.editStatus.querySelector('span.name'); |
| 800 | f.eLinks = P.e.editStatus.querySelector('span.links'); |
| 801 | } |
| 802 | const wi = this.winfo; |
| @@ -803,15 +844,11 @@ | |
| 803 | D.clearElement(f.eName, f.eLinks); |
| 804 | if(!wi){ |
| 805 | D.append(f.eName, '(no page loaded)'); |
| 806 | return; |
| 807 | } |
| 808 | var marker = editFlag || ''; |
| 809 | if(0===arguments){ |
| 810 | if(!wi.version && 'sandbox'!==wi.type) marker = P.config.editStateMarkers.isNew; |
| 811 | else if($stash.getWinfo(wi)) marker = P.config.editStateMarkers.isModified; |
| 812 | } |
| 813 | D.append(f.eName,marker,wi.name,); |
| 814 | if(wi.version){ |
| 815 | D.append( |
| 816 | f.eLinks, |
| 817 | D.a(F.repoUrl('whistory',{name:wi.name}),'[history]'), |
| @@ -827,21 +864,14 @@ | |
| 827 | */ |
| 828 | P.updatePageTitle = function f(){ |
| 829 | if(!f.titleElement){ |
| 830 | f.titleElement = document.head.querySelector('title'); |
| 831 | } |
| 832 | var title, marker = ''; |
| 833 | const wi = P.winfo; |
| 834 | if(wi){ |
| 835 | if(!wi.version && 'sandbox'!==wi.type) marker = P.config.editStateMarkers.isNew; |
| 836 | else if($stash.getWinfo(wi)) marker = P.config.editStateMarkers.isModified; |
| 837 | title = wi.name; |
| 838 | }else{ |
| 839 | title = 'no page loaded'; |
| 840 | } |
| 841 | f.titleElement.innerText = 'Wiki Editor: ' + marker + title; |
| 842 | this.updateEditStatus(marker); |
| 843 | return this; |
| 844 | }; |
| 845 | |
| 846 | /** |
| 847 | Change the save button depending on whether we have stuff to save |
| @@ -848,11 +878,11 @@ | |
| 848 | or not. |
| 849 | */ |
| 850 | P.updateSaveButton = function(){ |
| 851 | if(!this.winfo || !this.getStashedWinfo(this.winfo)){ |
| 852 | D.disable(this.e.btnSave).innerText = |
| 853 | "There are no changes to save"; |
| 854 | }else{ |
| 855 | D.enable(this.e.btnSave).innerText = "Save changes"; |
| 856 | } |
| 857 | return this; |
| 858 | }; |
| @@ -951,12 +981,11 @@ | |
| 951 | name = arg.name; |
| 952 | } |
| 953 | const onload = (r)=>this.dispatchEvent('wiki-page-loaded', r); |
| 954 | const stashWinfo = this.getStashedWinfo({name: name}); |
| 955 | if(stashWinfo){ // fake a response from the stash... |
| 956 | F.message("Fetched from the local-edit storage:", |
| 957 | stashWinfo.name); |
| 958 | onload({ |
| 959 | name: stashWinfo.name, |
| 960 | mimetype: stashWinfo.mimetype, |
| 961 | type: stashWinfo.type, |
| 962 | version: stashWinfo.version, |
| 963 |
| --- src/fossil.page.wikiedit.js | |
| +++ src/fossil.page.wikiedit.js | |
| @@ -65,13 +65,11 @@ | |
| 65 | const E = (s)=>document.querySelector(s), |
| 66 | D = F.dom, |
| 67 | P = F.page; |
| 68 | |
| 69 | P.config = { |
| 70 | /* Symbolic markers to denote certain edit state. */ |
| 71 | editStateMarkers: { |
| 72 | isNew: '[+]', |
| 73 | isModified: '[*]' |
| 74 | } |
| 75 | }; |
| @@ -268,10 +266,37 @@ | |
| 266 | if(forceEvent){ |
| 267 | // Force UI update |
| 268 | s.dispatchEvent(new Event('change',{target:s})); |
| 269 | } |
| 270 | }; |
| 271 | |
| 272 | /** Internal helper to get an edit status indicator for the given winfo object. */ |
| 273 | const getEditMarker = function f(winfo, textOnly){ |
| 274 | const esm = P.config.editStateMarkers; |
| 275 | if(1===winfo){ /* force is-new */ |
| 276 | return textOnly ? esm.isNew : |
| 277 | D.addClass(D.append(D.span(),esm.isNew), 'is-new'); |
| 278 | }else if(2===winfo){ /* force is-modified */ |
| 279 | return textOnly ? esm.isModified : |
| 280 | D.addClass(D.append(D.span(),esm.isModified), 'is-modified'); |
| 281 | }else if(winfo && winfo.version){ /* is existing page modified? */ |
| 282 | if($stash.getWinfo(winfo)){ |
| 283 | return textOnly ? esm.isModified : |
| 284 | D.addClass(D.append(D.span(),esm.isModified), 'is-modified'); |
| 285 | } |
| 286 | } |
| 287 | else if(winfo){ /* is new non-sandbox or is modified sandbox? */ |
| 288 | if('sandbox'!==winfo.type){ |
| 289 | return textOnly ? esm.isNew : |
| 290 | D.addClass(D.append(D.span(),esm.isNew), 'is-new'); |
| 291 | }else if($stash.getWinfo(winfo)){ |
| 292 | return textOnly ? esm.isModified : |
| 293 | D.addClass(D.append(D.span(),esm.isModified), 'is-modified'); |
| 294 | } |
| 295 | } |
| 296 | return textOnly ? '' : D.span(); |
| 297 | }; |
| 298 | |
| 299 | /** |
| 300 | Sets up and maintains the widgets for the list of wiki pages. |
| 301 | */ |
| 302 | const WikiList = { |
| @@ -288,26 +313,43 @@ | |
| 313 | de/serialization. We need this map to support the add-new-page |
| 314 | feature, to give us a way to check for dupes without asking |
| 315 | the server or walking through the whole selection list. |
| 316 | */} |
| 317 | }, |
| 318 | /** |
| 319 | Updates OPTION elements to reflect whether the page has local |
| 320 | changes or is new/unsaved. This implementation is horribly |
| 321 | inefficient, in that we have to walk and validate the whole |
| 322 | list for each stash-level change. |
| 323 | |
| 324 | Reminder to self: in order to mark is-edited/is-new state we |
| 325 | have to update the OPTION element's inner text to reflect the |
| 326 | is-modified/is-new flags, rather than use CSS classes to tag |
| 327 | them, because mobile Chrome can neither restyle OPTION elements |
| 328 | no render ::before content on them. We *also* use CSS tags, but |
| 329 | they aren't sufficient for the mobile browsers. |
| 330 | */ |
| 331 | _refreshStashMarks: function callee(){ |
| 332 | if(!callee.eachOpt){ |
| 333 | const self = this; |
| 334 | callee.eachOpt = function(key){ |
| 335 | const opt = self.e.select.options[key]; |
| 336 | const stashed = $stash.getWinfo({name:opt.value}); |
| 337 | var prefix = ''; |
| 338 | if(stashed){ |
| 339 | const isNew = 'sandbox'===stashed.type ? false : !stashed.version; |
| 340 | prefix = getEditMarker(isNew ? 1 : 2, true); |
| 341 | D.addClass(opt, isNew ? 'stashed-new' : 'stashed'); |
| 342 | }else{ |
| 343 | D.removeClass(opt, 'stashed', 'stashed-new'); |
| 344 | } |
| 345 | opt.innerText = prefix + opt.value; |
| 346 | self.cache.names[opt.value] = true; |
| 347 | }; |
| 348 | } |
| 349 | this.cache.names = {/*must reset it to acount for local page removals*/}; |
| 350 | Object.keys(this.e.select.options).forEach(callee.eachOpt); |
| 351 | }, |
| 352 | /** Removes the given wiki page entry from the page selection |
| 353 | list, if it's in the list. */ |
| 354 | removeEntry: function(name){ |
| 355 | const sel = this.e.select; |
| @@ -320,11 +362,12 @@ | |
| 362 | sel.selectedIndex = ndx; |
| 363 | }, |
| 364 | |
| 365 | /** |
| 366 | Rebuilds the selection list. Necessary when it's loaded from |
| 367 | the server or we locally create a new page. |
| 368 | */ |
| 369 | _rebuildList: function callee(){ |
| 370 | /* Jump through some hoops to integrate new/unsaved |
| 371 | pages into the list of existing pages... We use a map |
| 372 | as an intermediary in order to filter out any local-stash |
| 373 | dupes from server-side copies. */ |
| @@ -356,11 +399,11 @@ | |
| 399 | const cb = self.e.filterCheckboxes[wtype]; |
| 400 | if(cb && !cb.checked) D.addClass(opt, 'hidden'); |
| 401 | }); |
| 402 | D.enable(sel); |
| 403 | if(P.winfo) sel.value = P.winfo.name; |
| 404 | this._refreshStashMarks(); |
| 405 | }, |
| 406 | |
| 407 | /** Loads the page list and populates the selection list. */ |
| 408 | loadList: function callee(){ |
| 409 | delete this.pageMap; |
| @@ -453,11 +496,11 @@ | |
| 496 | D.attr(sel, 'size', 15); |
| 497 | D.option(D.disable(D.clearElement(sel)), "Loading..."); |
| 498 | |
| 499 | /** Set up filter checkboxes for the various types |
| 500 | of wiki pages... */ |
| 501 | const fsFilter = D.fieldset("Page types"), |
| 502 | fsFilterBody = D.div(), |
| 503 | filters = ['normal', 'branch', 'checkin', 'tag'] |
| 504 | ; |
| 505 | D.append(fsFilter, fsFilterBody); |
| 506 | D.addClass(fsFilterBody, 'flex-container', 'flex-column', 'stretch'); |
| @@ -491,14 +534,12 @@ | |
| 534 | fsLegendBody = D.div(); |
| 535 | D.append(fsLegend, fsLegendBody); |
| 536 | D.addClass(fsLegendBody, 'flex-container', 'flex-column', 'stretch'); |
| 537 | D.append( |
| 538 | fsLegendBody, |
| 539 | D.append(D.span(), getEditMarker(1,false)," = page is new/unsaved"), |
| 540 | D.append(D.span(), getEditMarker(2,false)," = page has local edits") |
| 541 | ); |
| 542 | |
| 543 | const fsNewPage = D.fieldset("Create new page"), |
| 544 | fsNewPageBody = D.div(), |
| 545 | newPageName = D.input('text'), |
| @@ -528,11 +569,11 @@ | |
| 569 | btn.addEventListener('click', ()=>this.loadList(), false); |
| 570 | this.loadList(); |
| 571 | const onSelect = (e)=>P.loadPage(e.target.value); |
| 572 | sel.addEventListener('change', onSelect, false); |
| 573 | sel.addEventListener('dblclick', onSelect, false); |
| 574 | F.page.addEventListener('wiki-stash-updated', ()=>this._refreshStashMarks()); |
| 575 | delete this.init; |
| 576 | } |
| 577 | }; |
| 578 | |
| 579 | /** |
| @@ -684,15 +725,15 @@ | |
| 725 | "then use the Discard button."); |
| 726 | return; |
| 727 | } |
| 728 | P.unstashContent() |
| 729 | if(w.version || w.type==='sandbox'){ |
| 730 | P.loadPage(w); |
| 731 | }else{ |
| 732 | WikiList.removeEntry(w.name); |
| 733 | delete P.winfo; |
| 734 | P.updatePageTitle(); |
| 735 | F.message("Discarded new page ["+w.name+"]."); |
| 736 | } |
| 737 | }, |
| 738 | ticks: 3 |
| 739 | }); |
| @@ -792,11 +833,11 @@ | |
| 833 | if(!P.winfo && !quiet) F.error("No wiki page is loaded."); |
| 834 | return !!P.winfo; |
| 835 | }; |
| 836 | |
| 837 | /** Updates the in-tab title/edit status information */ |
| 838 | P.updateEditStatus = function f(){ |
| 839 | if(!f.eLinks){ |
| 840 | f.eName = P.e.editStatus.querySelector('span.name'); |
| 841 | f.eLinks = P.e.editStatus.querySelector('span.links'); |
| 842 | } |
| 843 | const wi = this.winfo; |
| @@ -803,15 +844,11 @@ | |
| 844 | D.clearElement(f.eName, f.eLinks); |
| 845 | if(!wi){ |
| 846 | D.append(f.eName, '(no page loaded)'); |
| 847 | return; |
| 848 | } |
| 849 | var marker = getEditMarker(wi, false); |
| 850 | D.append(f.eName,marker,wi.name,); |
| 851 | if(wi.version){ |
| 852 | D.append( |
| 853 | f.eLinks, |
| 854 | D.a(F.repoUrl('whistory',{name:wi.name}),'[history]'), |
| @@ -827,21 +864,14 @@ | |
| 864 | */ |
| 865 | P.updatePageTitle = function f(){ |
| 866 | if(!f.titleElement){ |
| 867 | f.titleElement = document.head.querySelector('title'); |
| 868 | } |
| 869 | const wi = P.winfo, marker = getEditMarker(wi, true), |
| 870 | title = wi ? wi.name : 'no page loaded'; |
| 871 | f.titleElement.innerText = 'Wiki Editor: ' + marker + title; |
| 872 | this.updateEditStatus(); |
| 873 | return this; |
| 874 | }; |
| 875 | |
| 876 | /** |
| 877 | Change the save button depending on whether we have stuff to save |
| @@ -848,11 +878,11 @@ | |
| 878 | or not. |
| 879 | */ |
| 880 | P.updateSaveButton = function(){ |
| 881 | if(!this.winfo || !this.getStashedWinfo(this.winfo)){ |
| 882 | D.disable(this.e.btnSave).innerText = |
| 883 | "No changes to save"; |
| 884 | }else{ |
| 885 | D.enable(this.e.btnSave).innerText = "Save changes"; |
| 886 | } |
| 887 | return this; |
| 888 | }; |
| @@ -951,12 +981,11 @@ | |
| 981 | name = arg.name; |
| 982 | } |
| 983 | const onload = (r)=>this.dispatchEvent('wiki-page-loaded', r); |
| 984 | const stashWinfo = this.getStashedWinfo({name: name}); |
| 985 | if(stashWinfo){ // fake a response from the stash... |
| 986 | F.message("Fetched from the local-edit storage:", stashWinfo.name); |
| 987 | onload({ |
| 988 | name: stashWinfo.name, |
| 989 | mimetype: stashWinfo.mimetype, |
| 990 | type: stashWinfo.type, |
| 991 | version: stashWinfo.version, |
| 992 |
+15
-11
| --- src/style.wikiedit.css | ||
| +++ src/style.wikiedit.css | ||
| @@ -9,10 +9,11 @@ | ||
| 9 | 9 | body.wikiedit select, |
| 10 | 10 | body.wikiedit select:focus{ |
| 11 | 11 | /* The sudden appearance of a border (as in the Ardoise skin) |
| 12 | 12 | shifts the layout in unsightly ways */ |
| 13 | 13 | border: initial; |
| 14 | + border-width: 1px; | |
| 14 | 15 | } |
| 15 | 16 | body.wikiedit div.wikiedit-preview { |
| 16 | 17 | margin: 0; |
| 17 | 18 | padding: 0; |
| 18 | 19 | } |
| @@ -53,23 +54,19 @@ | ||
| 53 | 54 | align-items: start; |
| 54 | 55 | } |
| 55 | 56 | body.wikiedit .WikiList select { |
| 56 | 57 | font-size: 110%; |
| 57 | 58 | margin: initial; |
| 58 | - height: initial /* some skins set these to a fix height */; | |
| 59 | + height: initial /* some skins set these to a fixed height */; | |
| 60 | + font-family: monospace; | |
| 59 | 61 | } |
| 60 | 62 | body.wikiedit .WikiList select option { |
| 61 | - margin: 0.5em 0; | |
| 62 | -} | |
| 63 | -body.wikiedit .WikiList select option.stashed::before { | |
| 64 | -/* Maintenance reminder: the option.stashed/stashed-new "content" values | |
| 65 | - are duplicated in fossil.page.wikiedit.js and need to be changed there | |
| 66 | - if they are changed here: see fossil.page.config.editStateMarkers */ | |
| 67 | - content: "[*] "; | |
| 68 | -} | |
| 69 | -body.wikiedit .WikiList select option.stashed-new::before { | |
| 70 | - content: "[+] "; | |
| 63 | + margin: 0 0 0.5em 0.55em; | |
| 64 | +} | |
| 65 | +body.wikiedit .WikiList select option.stashed, | |
| 66 | +body.wikiedit .WikiList select option.stashed-new { | |
| 67 | + margin-left: -1em; | |
| 71 | 68 | } |
| 72 | 69 | body.wikiedit textarea { |
| 73 | 70 | max-width: initial; |
| 74 | 71 | } |
| 75 | 72 | body.wikiedit .tabs .tab-panel { |
| @@ -77,13 +74,16 @@ | ||
| 77 | 74 | overflow: auto; |
| 78 | 75 | } |
| 79 | 76 | body.wikiedit .WikiList fieldset { |
| 80 | 77 | padding: 0.25em; |
| 81 | 78 | border-width: 1px /* Ardoise skin sets this to 0 */; |
| 79 | + min-width: 6em; | |
| 80 | + border-style: inset; | |
| 82 | 81 | } |
| 83 | 82 | body.wikiedit .WikiList legend { |
| 84 | 83 | font-size: 90%; |
| 84 | + margin: 0 0 0 0.5em; | |
| 85 | 85 | } |
| 86 | 86 | body.wikiedit .WikiList fieldset > :not(legend) { |
| 87 | 87 | /* Stretch page selection list when it's empty or only has short page names */ |
| 88 | 88 | width: 100%; |
| 89 | 89 | } |
| @@ -133,9 +133,13 @@ | ||
| 133 | 133 | font-size: 1.2em; |
| 134 | 134 | } |
| 135 | 135 | |
| 136 | 136 | body.wikiedit #wikiedit-edit-status > span { |
| 137 | 137 | display: block; |
| 138 | +} | |
| 139 | +body.wikiedit .WikiList span.is-new, | |
| 140 | +body.wikiedit .WikiList span.is-modified { | |
| 141 | + font-family: monospace; | |
| 138 | 142 | } |
| 139 | 143 | body.wikiedit #wikiedit-edit-status > span.links > a { |
| 140 | 144 | margin: 0 0.25em; |
| 141 | 145 | } |
| 142 | 146 |
| --- src/style.wikiedit.css | |
| +++ src/style.wikiedit.css | |
| @@ -9,10 +9,11 @@ | |
| 9 | body.wikiedit select, |
| 10 | body.wikiedit select:focus{ |
| 11 | /* The sudden appearance of a border (as in the Ardoise skin) |
| 12 | shifts the layout in unsightly ways */ |
| 13 | border: initial; |
| 14 | } |
| 15 | body.wikiedit div.wikiedit-preview { |
| 16 | margin: 0; |
| 17 | padding: 0; |
| 18 | } |
| @@ -53,23 +54,19 @@ | |
| 53 | align-items: start; |
| 54 | } |
| 55 | body.wikiedit .WikiList select { |
| 56 | font-size: 110%; |
| 57 | margin: initial; |
| 58 | height: initial /* some skins set these to a fix height */; |
| 59 | } |
| 60 | body.wikiedit .WikiList select option { |
| 61 | margin: 0.5em 0; |
| 62 | } |
| 63 | body.wikiedit .WikiList select option.stashed::before { |
| 64 | /* Maintenance reminder: the option.stashed/stashed-new "content" values |
| 65 | are duplicated in fossil.page.wikiedit.js and need to be changed there |
| 66 | if they are changed here: see fossil.page.config.editStateMarkers */ |
| 67 | content: "[*] "; |
| 68 | } |
| 69 | body.wikiedit .WikiList select option.stashed-new::before { |
| 70 | content: "[+] "; |
| 71 | } |
| 72 | body.wikiedit textarea { |
| 73 | max-width: initial; |
| 74 | } |
| 75 | body.wikiedit .tabs .tab-panel { |
| @@ -77,13 +74,16 @@ | |
| 77 | overflow: auto; |
| 78 | } |
| 79 | body.wikiedit .WikiList fieldset { |
| 80 | padding: 0.25em; |
| 81 | border-width: 1px /* Ardoise skin sets this to 0 */; |
| 82 | } |
| 83 | body.wikiedit .WikiList legend { |
| 84 | font-size: 90%; |
| 85 | } |
| 86 | body.wikiedit .WikiList fieldset > :not(legend) { |
| 87 | /* Stretch page selection list when it's empty or only has short page names */ |
| 88 | width: 100%; |
| 89 | } |
| @@ -133,9 +133,13 @@ | |
| 133 | font-size: 1.2em; |
| 134 | } |
| 135 | |
| 136 | body.wikiedit #wikiedit-edit-status > span { |
| 137 | display: block; |
| 138 | } |
| 139 | body.wikiedit #wikiedit-edit-status > span.links > a { |
| 140 | margin: 0 0.25em; |
| 141 | } |
| 142 |
| --- src/style.wikiedit.css | |
| +++ src/style.wikiedit.css | |
| @@ -9,10 +9,11 @@ | |
| 9 | body.wikiedit select, |
| 10 | body.wikiedit select:focus{ |
| 11 | /* The sudden appearance of a border (as in the Ardoise skin) |
| 12 | shifts the layout in unsightly ways */ |
| 13 | border: initial; |
| 14 | border-width: 1px; |
| 15 | } |
| 16 | body.wikiedit div.wikiedit-preview { |
| 17 | margin: 0; |
| 18 | padding: 0; |
| 19 | } |
| @@ -53,23 +54,19 @@ | |
| 54 | align-items: start; |
| 55 | } |
| 56 | body.wikiedit .WikiList select { |
| 57 | font-size: 110%; |
| 58 | margin: initial; |
| 59 | height: initial /* some skins set these to a fixed height */; |
| 60 | font-family: monospace; |
| 61 | } |
| 62 | body.wikiedit .WikiList select option { |
| 63 | margin: 0 0 0.5em 0.55em; |
| 64 | } |
| 65 | body.wikiedit .WikiList select option.stashed, |
| 66 | body.wikiedit .WikiList select option.stashed-new { |
| 67 | margin-left: -1em; |
| 68 | } |
| 69 | body.wikiedit textarea { |
| 70 | max-width: initial; |
| 71 | } |
| 72 | body.wikiedit .tabs .tab-panel { |
| @@ -77,13 +74,16 @@ | |
| 74 | overflow: auto; |
| 75 | } |
| 76 | body.wikiedit .WikiList fieldset { |
| 77 | padding: 0.25em; |
| 78 | border-width: 1px /* Ardoise skin sets this to 0 */; |
| 79 | min-width: 6em; |
| 80 | border-style: inset; |
| 81 | } |
| 82 | body.wikiedit .WikiList legend { |
| 83 | font-size: 90%; |
| 84 | margin: 0 0 0 0.5em; |
| 85 | } |
| 86 | body.wikiedit .WikiList fieldset > :not(legend) { |
| 87 | /* Stretch page selection list when it's empty or only has short page names */ |
| 88 | width: 100%; |
| 89 | } |
| @@ -133,9 +133,13 @@ | |
| 133 | font-size: 1.2em; |
| 134 | } |
| 135 | |
| 136 | body.wikiedit #wikiedit-edit-status > span { |
| 137 | display: block; |
| 138 | } |
| 139 | body.wikiedit .WikiList span.is-new, |
| 140 | body.wikiedit .WikiList span.is-modified { |
| 141 | font-family: monospace; |
| 142 | } |
| 143 | body.wikiedit #wikiedit-edit-status > span.links > a { |
| 144 | margin: 0 0.25em; |
| 145 | } |
| 146 |