Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | fossil.message() and friends now use local timestamps instead of UTC. Fixed a bug in wikiedit which caused a newly-created page to disappear from the page selection list after it was saved. Other minor cleanups in adjacent code. |
---|---|
Downloads: | Tarball | ZIP archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA3-256: |
3dc4613d19edd0d94bc8b15b81c7987b |
User & Date: | stephan 2020-08-01 23:38:36.398 |
Context
2020-08-02
| ||
13:23 | Tiny style tweak for the wikiedit list filter toggles. ... (check-in: b0a38d5fb3 user: stephan tags: trunk) | |
2020-08-01
| ||
23:38 | fossil.message() and friends now use local timestamps instead of UTC. Fixed a bug in wikiedit which caused a newly-created page to disappear from the page selection list after it was saved. Other minor cleanups in adjacent code. ... (check-in: 3dc4613d19 user: stephan tags: trunk) | |
22:25 | Minor CSS tweak for mobile browsers. ... (check-in: bfd79af058 user: stephan tags: trunk) | |
Changes
Changes to src/fossil.bootstrap.js.
︙ | ︙ | |||
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | if(!f.rx1){ f.rx1 = /\.\d+Z$/; } const d = new Date(); return d.toISOString().replace(f.rx1,'').split('T').join(' '); }; /* ** By default fossil.message() sends its arguments console.debug(). If ** fossil.message.targetElement is set, it is assumed to be a DOM ** element, its innerText gets assigned to the concatenation of all ** arguments (with a space between each), and the CSS 'error' class is ** removed from the object. Pass it a falsy value to clear the target ** element. ** ** Returns this object. */ F.message = function f(msg){ const args = Array.prototype.slice.call(arguments,0); const tgt = f.targetElement; | > > > > > > > > > > > > > > > | > > > | 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 | if(!f.rx1){ f.rx1 = /\.\d+Z$/; } const d = new Date(); return d.toISOString().replace(f.rx1,'').split('T').join(' '); }; /** Returns the local time string of Date object d, defaulting to the current time. */ const localTimeString = function ff(d){ if(!ff.pad){ ff.pad = (x)=>(''+x).length>1 ? x : '0'+x; } d || (d = new Date()); return [ d.getFullYear(),'-',ff.pad(d.getMonth()+1/*sigh*/), '-',ff.pad(d.getDate()), ' ',ff.pad(d.getHours()),':',ff.pad(d.getMinutes()), ':',ff.pad(d.getSeconds()) ].join(''); }; /* ** By default fossil.message() sends its arguments console.debug(). If ** fossil.message.targetElement is set, it is assumed to be a DOM ** element, its innerText gets assigned to the concatenation of all ** arguments (with a space between each), and the CSS 'error' class is ** removed from the object. Pass it a falsy value to clear the target ** element. ** ** Returns this object. */ F.message = function f(msg){ const args = Array.prototype.slice.call(arguments,0); const tgt = f.targetElement; if(args.length) args.unshift( localTimeString()+':' //timestring(),'UTC:' ); if(tgt){ tgt.classList.remove('error'); tgt.innerText = args.join(' '); } else{ if(args.length){ args.unshift('Fossil status:'); |
︙ | ︙ |
Changes to src/fossil.page.fileedit.js.
︙ | ︙ | |||
516 517 518 519 520 521 522 | D.addClass(this.e.btnClear, 'hidden'); D.option(D.disable(this.e.select),"No local edits"); return; } D.enable(this.e.select); D.removeClass(this.e.btnClear, 'hidden'); D.disable(D.option(this.e.select,0,"Select a local edit...")); | | | | 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 | D.addClass(this.e.btnClear, 'hidden'); D.option(D.disable(this.e.select),"No local edits"); return; } D.enable(this.e.select); D.removeClass(this.e.btnClear, 'hidden'); D.disable(D.option(this.e.select,0,"Select a local edit...")); const currentFinfo = theFinfo || P.finfo || {filename:''}; ilist.sort(f.compare).forEach(function(finfo,n){ const key = stasher.indexKey(finfo), branch = finfo.branch || P.fileSelectWidget.checkinBranchName(finfo.checkin)||''; /* Remember that we don't know the branch name for non-leaf versions which P.fileSelectWidget() has never seen/cached. */ const opt = D.option( self.e.select, n+1/*value is (almost) irrelevant*/, [F.hashDigits(finfo.checkin), ' [',branch||'?branch?','] ', f.timestring(new Date(finfo.stashTime)),' ', false ? finfo.filename : F.shortenFilename(finfo.filename) ].join('') ); opt._finfo = finfo; if(0===f.compare(currentFinfo, finfo)){ D.attr(opt, 'selected', true); |
︙ | ︙ |
Changes to src/fossil.page.wikiedit.js.
︙ | ︙ | |||
300 301 302 303 304 305 306 307 308 309 310 311 312 313 | e: { filterCheckboxes: { /*map of wiki page type to checkbox for list filtering purposes, except for "sandbox" type, which is assumed to be covered by the "normal" type filter. */}, }, cache: { names: { /* Map of page names to "something." We don't map to their winfo bits because those regularly get swapped out via de/serialization. We need this map to support the add-new-page feature, to give us a way to check for dupes without asking the server or walking through the whole selection list. */} | > | 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 | e: { filterCheckboxes: { /*map of wiki page type to checkbox for list filtering purposes, except for "sandbox" type, which is assumed to be covered by the "normal" type filter. */}, }, cache: { pageList: [], names: { /* Map of page names to "something." We don't map to their winfo bits because those regularly get swapped out via de/serialization. We need this map to support the add-new-page feature, to give us a way to check for dupes without asking the server or walking through the whole selection list. */} |
︙ | ︙ | |||
353 354 355 356 357 358 359 360 361 362 363 364 365 366 | var ndx = sel.selectedIndex; sel.value = name; if(sel.selectedIndex>-1){ if(ndx === sel.selectedIndex) ndx = -1; sel.options.remove(sel.selectedIndex); } sel.selectedIndex = ndx; }, /** Rebuilds the selection list. Necessary when it's loaded from the server or we locally create a new page. */ _rebuildList: function callee(){ | > > | 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 | var ndx = sel.selectedIndex; sel.value = name; if(sel.selectedIndex>-1){ if(ndx === sel.selectedIndex) ndx = -1; sel.options.remove(sel.selectedIndex); } sel.selectedIndex = ndx; delete this.cache.names[name]; this.cache.pageList = this.cache.pageList.filter((wi)=>name !== wi.name); }, /** Rebuilds the selection list. Necessary when it's loaded from the server or we locally create a new page. */ _rebuildList: function callee(){ |
︙ | ︙ | |||
466 467 468 469 470 471 472 473 474 475 476 477 | else if(0===name.indexOf('tag/')) wtype = 'tag'; /* ^^^ note that we're not validating that, e.g., checkin/XYZ has a full artifact ID after "checkin/". */ const winfo = { name: name, type: wtype, mimetype: 'text/x-fossil-wiki', version: null, parent: null }; $stash.updateWinfo(winfo, ''); this._rebuildList(); P.loadPage(winfo.name); return true; }, | > > > | | 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 | else if(0===name.indexOf('tag/')) wtype = 'tag'; /* ^^^ note that we're not validating that, e.g., checkin/XYZ has a full artifact ID after "checkin/". */ const winfo = { name: name, type: wtype, mimetype: 'text/x-fossil-wiki', version: null, parent: null }; this.cache.pageList.push( winfo/*keeps entry from getting lost from the list on save*/ ); $stash.updateWinfo(winfo, ''); this._rebuildList(); P.loadPage(winfo.name); return true; }, /** Installs a wiki page selection list into the given parent DOM element and loads the page list from the server. */ init: function(parentElem){ const sel = D.select(), btn = D.addClass(D.button("Reload page list"), 'save'); this.e.select = sel; |
︙ | ︙ | |||
622 623 624 625 626 627 628 | $stash._fireStashEvent(/*read the page-load-time stash*/); delete this.init; }, /** Regenerates the edit selection list. */ updateList: function f(stasher,theWinfo){ | < | | | 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 | $stash._fireStashEvent(/*read the page-load-time stash*/); delete this.init; }, /** Regenerates the edit selection list. */ updateList: function f(stasher,theWinfo){ if(!f.compare){ const cmpBase = (l,r)=>l<r ? -1 : (l===r ? 0 : 1); f.compare = (l,r)=>cmpBase(l.name.toLowerCase(), r.name.toLowerCase()); f.rxZ = /\.\d+Z$/ /* ms and 'Z' part of date string */; const pad=(x)=>(''+x).length>1 ? x : '0'+x; f.timestring = function(d){ return [ d.getFullYear(),'-',pad(d.getMonth()+1/*sigh*/),'-',pad(d.getDate()), '@',pad(d.getHours()),':',pad(d.getMinutes()) ].join(''); }; } const index = stasher.getIndex(), ilist = []; |
︙ | ︙ | |||
655 656 657 658 659 660 661 | /* The problem with this Clear button is that it allows the user to nuke a non-empty newly-added page without the failsafe confirmation we have if they use P.e.btnReload. Not yet sure how best to resolve that, so we'll leave the button hidden for the time being. */ D.removeClass(this.e.btnClear, 'hidden'); } D.disable(D.option(this.e.select,0,"Select a local edit...")); | | > | > | | | 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 | /* The problem with this Clear button is that it allows the user to nuke a non-empty newly-added page without the failsafe confirmation we have if they use P.e.btnReload. Not yet sure how best to resolve that, so we'll leave the button hidden for the time being. */ D.removeClass(this.e.btnClear, 'hidden'); } D.disable(D.option(this.e.select,0,"Select a local edit...")); const currentWinfo = theWinfo || P.winfo || {name:''}; ilist.sort(f.compare).forEach(function(winfo,n){ const key = stasher.indexKey(winfo), rev = winfo.version || ''; const opt = D.option( self.e.select, n+1/*value is (almost) irrelevant*/, [winfo.name, ' [', rev ? F.hashDigits(rev) : ( winfo.type==='sandbox' ? 'sandbox' : 'new/local' ),'] ', f.timestring(new Date(winfo.stashTime)) ].join('') ); opt._winfo = winfo; if(0===f.compare(currentWinfo, winfo)){ D.attr(opt, 'selected', true); } }); } }/*P.stashWidget*/; /** |
︙ | ︙ | |||
1242 1243 1244 1245 1246 1247 1248 | if(!affirmPageLoaded()) return this; const self = this; const content = this.wikiContent(); if(!callee.onload){ callee.onload = function(w){ const oldWinfo = self.winfo; self.unstashContent(oldWinfo); | < < | 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 | if(!affirmPageLoaded()) return this; const self = this; const content = this.wikiContent(); if(!callee.onload){ callee.onload = function(w){ const oldWinfo = self.winfo; self.unstashContent(oldWinfo); self.dispatchEvent('wiki-page-loaded', w); F.message("Saved page: ["+w.name+"]."); } } const fd = new FormData(), w = P.winfo; fd.append('page',w.name); fd.append('mimetype', w.mimetype); |
︙ | ︙ |
Changes to src/wiki.c.
︙ | ︙ | |||
805 806 807 808 809 810 811 | ** not send. ** ** Responds with JSON. On error, an object in the form documented by ** ajax_route_error(). On success, an object in the form documented ** for wiki_ajax_emit_page_object(). ** ** The wikiajax API disallows saving of a sandbox pseudo-page, and | | < | | | < | < < | > < | 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 | ** not send. ** ** Responds with JSON. On error, an object in the form documented by ** ajax_route_error(). On success, an object in the form documented ** for wiki_ajax_emit_page_object(). ** ** The wikiajax API disallows saving of a sandbox pseudo-page, and ** will respond with an error if asked to save one. Should we want to ** enable it, it's implemented like this for any saved page for which ** is_sandbox(zPageName) is true: ** ** db_set("sandbox",zBody,0); ** db_set("sandbox-mimetype",zMimetype,0); ** */ static void wiki_ajax_route_save(void){ const char *zPageName = P("page"); const char *zMimetype = P("mimetype"); const char *zContent = P("content"); const int isNew = ajax_p_bool("isnew"); Blob content = empty_blob; int parentRid = 0; int rollback = 0; if(!wiki_ajax_can_write(zPageName, &parentRid)){ return; }else if(is_sandbox(zPageName)){ ajax_route_error(403,"Saving a sandbox page is prohibited."); return; } /* These isNew checks are just me being pedantic. We could just as easily derive isNew based on whether or not the page already exists. */ if(isNew){ if(parentRid>0){ ajax_route_error(403,"Requested a new page, " "but it already exists with RID %d: %s", parentRid, zPageName); return; } }else if(parentRid==0){ ajax_route_error(403,"Creating new page [%s] requires passing " "isnew=1.", zPageName); return; } blob_init(&content, zContent ? zContent : "", -1); db_begin_transaction(); wiki_cmd_commit(zPageName, parentRid, &content, zMimetype, 0); rollback = wiki_ajax_emit_page_object(zPageName, 1) ? 0 : 1; db_end_transaction(rollback); } |
︙ | ︙ | |||
998 999 1000 1001 1002 1003 1004 | wiki_ajax_emit_page_object(zName, includeContent); } } db_finalize(&q); db_end_transaction(0); CX("]"); } | < | 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 | wiki_ajax_emit_page_object(zName, includeContent); } } db_finalize(&q); db_end_transaction(0); CX("]"); } /* ** WEBPAGE: wikiajax ** ** An internal dispatcher for wiki AJAX operations. Not for direct ** client use. All routes defined by this interface are app-internal, ** subject to change |
︙ | ︙ |