Fossil
Check-in [0d5006be]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Ported several features between wikiedit and fileedit, improving them both a bit.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 0d5006bed5f8c8f22a93a70311c76826beadb12dfa14f0bbef4409746f7a23fa
User & Date: stephan 2020-08-01 21:31:18
Context
2020-08-01
22:25
Minor CSS tweak for mobile browsers. (check-in: bfd79af0 user: stephan tags: trunk)
21:31
Ported several features between wikiedit and fileedit, improving them both a bit. (check-in: 0d5006be user: stephan tags: trunk)
18:47
Updated the wiki CLI command to account for the sandbox pseudo-page and removed an obsolete TODO. (check-in: 7bc94270 user: stephan tags: trunk)
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/fileedit.c.

1624
1625
1626
1627
1628
1629
1630





1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
....
1933
1934
1935
1936
1937
1938
1939
1940


1941
1942
1943
1944
1945
1946
1947
1948
  style_emit_script_tag(1,0);
  
  /* Status bar */
  CX("<div id='fossil-status-bar' "
     "title='Status message area. Double-click to clear them.'>"
     "Status messages will go here.</div>\n"
     /* will be moved into the tab container via JS */);






  /* Main tab container... */
  CX("<div id='fileedit-tabs' class='tab-container'></div>");

  /* The .hidden class on the following tab elements is to help lessen
     the FOUC effect of the tabs before JS re-assembles them. */

  /***** File/version info tab *****/
  {
    CX("<div id='fileedit-tab-fileselect' "
       "data-tab-parent='fileedit-tabs' "
       "data-tab-label='File Info &amp; Selection' "
       "class='hidden'"
       ">");
    CX("<fieldset id='file-version-details'>"
       "<legend>File/Version</legend>"
       "<div>No file loaded.</div>"
       "</fieldset>");
    CX("<h1>Select a file to edit:</h1>");
    CX("<div id='fileedit-file-selector'></div>");
    CX("</div>"/*#fileedit-tab-fileselect*/);
  }
  
  /******* Content tab *******/
  {
................................................................................
  }
  CX("</div>"/*#fileedit-tab-help*/);

  {
    /* Dynamically populate the editor, display any error in the err
    ** blob, and/or switch to tab #0, where the file selector
    ** lives... */
    blob_appendf(&endScript,


                 "fossil.onPageLoad(");
    if(zRev && zFilename){
      assert(0==blob_size(&err));
      blob_appendf(&endScript,
                   "()=>fossil.page.loadFile(%!j,%!j)",
                   zFilename, cimi.zParentUuid);
    }else{
      blob_appendf(&endScript,"function(){\n");







>
>
>
>
>











|


<
<
<
<







 







|
>
>
|







1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649




1650
1651
1652
1653
1654
1655
1656
....
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
  style_emit_script_tag(1,0);
  
  /* Status bar */
  CX("<div id='fossil-status-bar' "
     "title='Status message area. Double-click to clear them.'>"
     "Status messages will go here.</div>\n"
     /* will be moved into the tab container via JS */);

  CX("<div id='fileedit-edit-status'>"
     "<span class='name'>(no file loaded)</span>"
     "<span class='links'></span>"
     "</div>");

  /* Main tab container... */
  CX("<div id='fileedit-tabs' class='tab-container'></div>");

  /* The .hidden class on the following tab elements is to help lessen
     the FOUC effect of the tabs before JS re-assembles them. */

  /***** File/version info tab *****/
  {
    CX("<div id='fileedit-tab-fileselect' "
       "data-tab-parent='fileedit-tabs' "
       "data-tab-label='File Selection' "
       "class='hidden'"
       ">");




    CX("<h1>Select a file to edit:</h1>");
    CX("<div id='fileedit-file-selector'></div>");
    CX("</div>"/*#fileedit-tab-fileselect*/);
  }
  
  /******* Content tab *******/
  {
................................................................................
  }
  CX("</div>"/*#fileedit-tab-help*/);

  {
    /* Dynamically populate the editor, display any error in the err
    ** blob, and/or switch to tab #0, where the file selector
    ** lives... */
    blob_appendf(&endScript, "fossil.config['fileedit-glob'] = ");
    glob_render_as_json(fileedit_glob(), &endScript);
    blob_append(&endScript, ";\n", 2);
    blob_append(&endScript, "fossil.onPageLoad(", -1);
    if(zRev && zFilename){
      assert(0==blob_size(&err));
      blob_appendf(&endScript,
                   "()=>fossil.page.loadFile(%!j,%!j)",
                   zFilename, cimi.zParentUuid);
    }else{
      blob_appendf(&endScript,"function(){\n");

Changes to src/fossil.confirmer.js.

266
267
268
269
270
271
272
273

274
275
276
277
278
279
280
    new f.Holder(elem,opt);
    return this;
  };
  /**
     The default options for initConfirmer(). Tweak them to set the
     defaults. A couple of them (initialText and confirmText) are
     dynamically-generated, and can't reasonably be set in the
     defaults.

  */
  F.confirmer.defaultOpts = {
    timeout:3000,
    ticks: undefined,
    ticktime: 998/*not *quite* 1000*/,
    onconfirm: undefined,
    ontimeout: undefined,







|
>







266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
    new f.Holder(elem,opt);
    return this;
  };
  /**
     The default options for initConfirmer(). Tweak them to set the
     defaults. A couple of them (initialText and confirmText) are
     dynamically-generated, and can't reasonably be set in the
     defaults. Some, like ticks, cannot be set here because that would
     end up indirectly replacing non-tick timeouts with ticks.
  */
  F.confirmer.defaultOpts = {
    timeout:3000,
    ticks: undefined,
    ticktime: 998/*not *quite* 1000*/,
    onconfirm: undefined,
    ontimeout: undefined,

Changes to src/fossil.page.fileedit.js.

405
406
407
408
409
410
411












412
413
414
415
416
417
418
419
420
421

422
423
424
425
426
427
428
429
430
...
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
...
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
...
642
643
644
645
646
647
648
649
650
651
652
653
654
655

656
657
658
659
660
661
662
663
664
665
666
667
668
669
...
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
...
757
758
759
760
761
762
763


764

765
766
767
768
769
770
771
...
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
...
912
913
914
915
916
917
918
919




920
921
922
923
924
925
926
927

928
929
930
931





932
933
934
935
936
937



938
939
940

941
942


943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
....
1015
1016
1017
1018
1019
1020
1021

1022
1023
1024
1025
1026
1027
1028
....
1098
1099
1100
1101
1102
1103
1104

1105
1106
1107
1108
1109
1110
1111
....
1273
1274
1275
1276
1277
1278
1279

1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290

1291
1292
1293
1294
1295
1296
1297
        selCi,
        filesLabel,
        selFiles,
        /* Use a wrapper for btnLoad so that the button itself does not
          stretch to fill the parent width: */
        D.append(D.addClass(D.div(), 'flex-shrink'), btnLoad)
      );












      this.loadLeaves();
      selCi.addEventListener(
        'change', (e)=>this.loadFiles(e.target.value), false
      );
      btnLoad.addEventListener(
        'click', (e)=>{
          this.finfo.filename = selFiles.value;
          if(this.finfo.filename){
            P.loadFile(this.finfo.filename, this.finfo.checkin);
          }

        }, false
      );
      btnReload.addEventListener(
        'click', (e)=>this.loadLeaves(), false
      );
      delete this.init;
    }
  }/*P.fileSelectWidget*/;

................................................................................
      F.page.addEventListener('fileedit-file-loaded',(e)=>this.updateList($stash, e.detail));
      sel.addEventListener('change',function(e){
        const opt = this.selectedOptions[0];
        if(opt && opt._finfo) P.loadFile(opt._finfo);
      });
      F.confirmer(btnClear, {
        confirmText: "REALLY delete ALL local edits?",
        onconfirm: (e)=>P.clearStash().loadFile(/*in case P.finfo() was in the stash*/),
        ticks: 3
      });
      if(F.storage.isTransient()){/*Warn if our storage is particularly transient...*/
        D.append(wrapper, D.append(
          D.addClass(D.span(),'warning'),
          "Warning: persistent storage is not available, "+
            "so uncomitted edits will not survive a page reload."
        ));
................................................................................
      cbLineNumbersWrap: E('#cb-line-numbers'),
      cbAutoPreview: E('#cb-preview-autoupdate > input[type=checkbox]'),
      previewTarget: E('#fileedit-tab-preview-wrapper'),
      manifestTarget: E('#fileedit-manifest'),
      diffTarget: E('#fileedit-tab-diff-wrapper'),
      cbIsExe: E('input[type=checkbox][name=exec_bit]'),
      cbManifest: E('input[type=checkbox][name=include_manifest]'),
      fsFileVersionDetails: E('#file-version-details'),
      tabs:{
        content: E('#fileedit-tab-content'),
        preview: E('#fileedit-tab-preview'),
        diff: E('#fileedit-tab-diff'),
        commit: E('#fileedit-tab-commit'),
        fileSelect: E('#fileedit-tab-fileselect')
      }
................................................................................
    }else if(D.hasClass(P.e.taCommentBig,'hidden')){
      P.e.taComment = P.e.taCommentSmall;
    }else{
      P.e.taComment = P.e.taCommentSmall;
      D.addClass(P.e.taCommentBig, 'hidden');
    }
    D.removeClass(P.e.taComment, 'hidden');

    P.tabs.e.container.insertBefore(
      /* Move the status bar between the tab buttons and
         tab panels. Seems to be the best fit in terms of
         functionality and visibility. */
      E('#fossil-status-bar'), P.tabs.e.tabs
    );


    P.tabs.addEventListener(
      /* Set up auto-refresh of the preview tab... */
      'before-switch-to', function(ev){
        if(ev.detail===P.e.tabs.preview){
          P.baseHrefForFile();
          if(P.e.cbAutoPreview.checked) P.preview();
        }else if(ev.detail===P.e.tabs.diff){
          /* Work around a weird bug where the page gets wider than
             the window when the diff tab is NOT in view and the
             current SBS diff widget is wider than the window. When
             the diff IS in view then CSS overflow magically reduces
             the page size again. Weird. Maybe FF-specific. Note that
             this weirdness happens even though P.e.diffTarget's parent
................................................................................
    );
    P.e.btnCommit.addEventListener(
      "click",(e)=>P.commit(), false
    );
    F.confirmer(P.e.btnReload, {
      confirmText: "Really reload, losing edits?",
      onconfirm: (e)=>P.unstashContent().loadFile(),
      ticks: 3
    });
    E('#comment-toggle').addEventListener(
      "click",(e)=>P.toggleCommentMode(), false
    );

    P.e.taEditor.addEventListener(
      'change', ()=>P.stashContentChange(), false
................................................................................
        new Event('change',{target:selectFontSize})
      );
    }

    P.addEventListener(
      // Clear certain views when new content is loaded/set
      'fileedit-content-replaced',


      ()=>D.clearElement(P.e.diffTarget, P.e.previewTarget, P.e.manifestTarget)

    );
    P.addEventListener(
      // Clear certain views after a non-dry-run commit
      'fileedit-committed',
      (e)=>{
        if(!e.detail.dryRun){
          D.clearElement(P.e.diffTarget, P.e.previewTarget);
................................................................................
        }
      }
    );

    P.fileSelectWidget.init();
    P.stashWidget.init(
      P.e.tabs.content.lastElementChild
      //P.e.tabs.fileSelect.querySelector("h1")
    );
  }/*F.onPageLoad()*/);

  /**
     Getter (if called with no args) or setter (if passed an arg) for
     the current file content.

................................................................................

  /**
     updateVersion() updates the filename and version in various UI
     elements...

     Returns this object.
  */
  P.updateVersion = function(file,rev){




    if(1===arguments.length){/*assume object*/
      this.finfo = arguments[0];
      file = this.finfo.filename;
      rev = this.finfo.checkin;
    }else if(0===arguments.length){
      if(!affirmHasFile()) return this;
      file = this.finfo.filename;
      rev = this.finfo.checkin;

    }else{
      this.finfo = {filename:file,checkin:rev};
    }
    const eTgt = this.e.fsFileVersionDetails.querySelector('div'),





          rHuman = F.hashDigits(rev),
          rUrl = F.hashDigits(rev,true);
    D.clearElement(eTgt);
    D.append(
      eTgt, "File: ",
      D.append(D.code(),



               D.a(F.repoUrl('finfo',{name:file, m:rUrl}), file)),
      D.br()
    );

    D.append(
      eTgt, "Checkin: ",


      D.append(D.code(), D.a(F.repoUrl('info/'+rUrl), rHuman)),
      " [",D.a(F.repoUrl('timeline',{m:rUrl}), "timeline"),"]",
      D.br()
    );
    D.append(
      eTgt, "Mimetype: ",
      D.append(D.code(), this.finfo.mimetype||'???'),
      D.br()
    );
    D.append(
      eTgt,
      D.append(D.code(), "[",
               D.a(F.repoUrl('annotate',{filename:file, checkin:rUrl}),
                   'annotate'), "]"),
      D.append(D.code(), "[",
               D.a(F.repoUrl('blame',{filename:file, checkin:rUrl}),
                   'blame'), "]")
    );
    const purlArgs = F.encodeUrlArgs({
      filename: this.finfo.filename,
      checkin: rUrl
    },false,true);
    const purl = F.repoUrl('fileedit',purlArgs);
    D.append(
      eTgt,
      D.append(D.code(),
               "[",D.a(purl,"Editor permalink"),"]")
    );
    this.setPageTitle("Edit: "+this.finfo.filename);
    return this;
  };

  /**
     loadFile() loads (file,checkinVersion) and updates the relevant
     UI elements to reflect the loaded state. If passed no arguments
     then it re-uses the values from the currently-loaded file, reloading
................................................................................
        branch: headers['x-fileedit-checkin-branch'],
        isExe: ('x'===headers['x-fileedit-file-perm']),
        mimetype: headers['content-type'].split(';').shift()
      });
      self.tabs.switchToTab(self.e.tabs.content);
      self.e.cbIsExe.checked = self.finfo.isExe;
      self.fileContent(r);

      self.dispatchEvent('fileedit-file-loaded', self.finfo);
    };
    const semiFinfo = {filename: file, checkin: rev};
    const stashFinfo = this.getStashedFinfo(semiFinfo);
    if(stashFinfo){ // fake a response from the stash...
      this.finfo = stashFinfo;
      this.e.cbIsExe.checked = !!stashFinfo.isExe;
................................................................................
      responseHeaders: 'x-ajax-render-mode',
      onload: (r,header)=>{
        P.selectPreviewMode(P.previewModes[header]);
        if('wiki'===header) P.baseHrefForFile();
        else P.baseHrefRestore();
        callback(r);
        F.message('Updated preview.');

        P.dispatchEvent('fileedit-preview-updated',{
          previewMode: P.previewModes.current,
          mimetype: P.finfo.mimetype,
          element: P.e.previewTarget
        });
      },
      onerror: (e)=>{
................................................................................
      if(onlyFinfo && $stash.hasStashedContent(fi)){
        $stash.updateFile(fi);
      }else{
        $stash.updateFile(fi, P.fileContent());
      }
      F.message("Stashed change to",F.hashDigits(fi.checkin),fi.filename);
      $stash.prune();

    }
    return this;
  };

  /**
     Removes any stashed state for the current P.finfo (if set) from
     F.storage. Returns this.
  */
  P.unstashContent = function(){
    const finfo = arguments[0] || this.finfo;
    if(finfo){

      $stash.unstash(finfo);
      //console.debug("Unstashed",finfo);
      F.message("Unstashed",F.hashDigits(finfo.checkin),finfo.filename);
    }
    return this;
  };








>
>
>
>
>
>
>
>
>
>
>
>




<
|
|
|
|
|
>
|
|







 







|
|







 







|







 







<






>






|







 







|







 







>
>
|
>







 







<







 







|
>
>
>
>





|
|
|
>



|
>
>
>
>
>
|

<
<
<
<
>
>
>
|
<
<
>

<
>
>
|
|
<
<
<
<
<
<
<
<
<
<
|
<
<
|
<






|
<
<
<
<
|







 







>







 







>







 







>











>







405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427

428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
...
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
...
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
...
654
655
656
657
658
659
660

661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
...
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
...
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
...
787
788
789
790
791
792
793

794
795
796
797
798
799
800
...
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957




958
959
960
961


962
963

964
965
966
967










968


969

970
971
972
973
974
975
976




977
978
979
980
981
982
983
984
....
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
....
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
....
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
        selCi,
        filesLabel,
        selFiles,
        /* Use a wrapper for btnLoad so that the button itself does not
          stretch to fill the parent width: */
        D.append(D.addClass(D.div(), 'flex-shrink'), btnLoad)
      );
      if(F.config['fileedit-glob']){
        D.append(
          this.e.container,
          D.append(
            D.div(),
            D.append(D.code(),"fileedit-glob"),
            " config setting = ",
            D.append(D.code(), JSON.stringify(F.config['fileedit-glob']))
          )
        );
      }

      this.loadLeaves();
      selCi.addEventListener(
        'change', (e)=>this.loadFiles(e.target.value), false
      );

      const doLoad = (e)=>{
        this.finfo.filename = selFiles.value;
        if(this.finfo.filename){
          P.loadFile(this.finfo.filename, this.finfo.checkin);
        }
      };
      btnLoad.addEventListener('click', doLoad, false);
      selFiles.addEventListener('dblclick', doLoad, false);
      btnReload.addEventListener(
        'click', (e)=>this.loadLeaves(), false
      );
      delete this.init;
    }
  }/*P.fileSelectWidget*/;

................................................................................
      F.page.addEventListener('fileedit-file-loaded',(e)=>this.updateList($stash, e.detail));
      sel.addEventListener('change',function(e){
        const opt = this.selectedOptions[0];
        if(opt && opt._finfo) P.loadFile(opt._finfo);
      });
      F.confirmer(btnClear, {
        confirmText: "REALLY delete ALL local edits?",
        onconfirm: (e)=>P.clearStash().loadFile(/*in case P.finfo was in the stash*/),
        ticks: F.config.confirmerButtonTicks
      });
      if(F.storage.isTransient()){/*Warn if our storage is particularly transient...*/
        D.append(wrapper, D.append(
          D.addClass(D.span(),'warning'),
          "Warning: persistent storage is not available, "+
            "so uncomitted edits will not survive a page reload."
        ));
................................................................................
      cbLineNumbersWrap: E('#cb-line-numbers'),
      cbAutoPreview: E('#cb-preview-autoupdate > input[type=checkbox]'),
      previewTarget: E('#fileedit-tab-preview-wrapper'),
      manifestTarget: E('#fileedit-manifest'),
      diffTarget: E('#fileedit-tab-diff-wrapper'),
      cbIsExe: E('input[type=checkbox][name=exec_bit]'),
      cbManifest: E('input[type=checkbox][name=include_manifest]'),
      editStatus: E('#fileedit-edit-status'),
      tabs:{
        content: E('#fileedit-tab-content'),
        preview: E('#fileedit-tab-preview'),
        diff: E('#fileedit-tab-diff'),
        commit: E('#fileedit-tab-commit'),
        fileSelect: E('#fileedit-tab-fileselect')
      }
................................................................................
    }else if(D.hasClass(P.e.taCommentBig,'hidden')){
      P.e.taComment = P.e.taCommentSmall;
    }else{
      P.e.taComment = P.e.taCommentSmall;
      D.addClass(P.e.taCommentBig, 'hidden');
    }
    D.removeClass(P.e.taComment, 'hidden');

    P.tabs.e.container.insertBefore(
      /* Move the status bar between the tab buttons and
         tab panels. Seems to be the best fit in terms of
         functionality and visibility. */
      E('#fossil-status-bar'), P.tabs.e.tabs
    );
    P.tabs.e.container.insertBefore(P.e.editStatus, P.tabs.e.tabs);

    P.tabs.addEventListener(
      /* Set up auto-refresh of the preview tab... */
      'before-switch-to', function(ev){
        if(ev.detail===P.e.tabs.preview){
          P.baseHrefForFile();
          if(P.previewNeedsUpdate && P.e.cbAutoPreview.checked) P.preview();
        }else if(ev.detail===P.e.tabs.diff){
          /* Work around a weird bug where the page gets wider than
             the window when the diff tab is NOT in view and the
             current SBS diff widget is wider than the window. When
             the diff IS in view then CSS overflow magically reduces
             the page size again. Weird. Maybe FF-specific. Note that
             this weirdness happens even though P.e.diffTarget's parent
................................................................................
    );
    P.e.btnCommit.addEventListener(
      "click",(e)=>P.commit(), false
    );
    F.confirmer(P.e.btnReload, {
      confirmText: "Really reload, losing edits?",
      onconfirm: (e)=>P.unstashContent().loadFile(),
      ticks: F.config.confirmerButtonTicks
    });
    E('#comment-toggle').addEventListener(
      "click",(e)=>P.toggleCommentMode(), false
    );

    P.e.taEditor.addEventListener(
      'change', ()=>P.stashContentChange(), false
................................................................................
        new Event('change',{target:selectFontSize})
      );
    }

    P.addEventListener(
      // Clear certain views when new content is loaded/set
      'fileedit-content-replaced',
      ()=>{
        P.previewNeedsUpdate = true;
        D.clearElement(P.e.diffTarget, P.e.previewTarget, P.e.manifestTarget);
      }
    );
    P.addEventListener(
      // Clear certain views after a non-dry-run commit
      'fileedit-committed',
      (e)=>{
        if(!e.detail.dryRun){
          D.clearElement(P.e.diffTarget, P.e.previewTarget);
................................................................................
        }
      }
    );

    P.fileSelectWidget.init();
    P.stashWidget.init(
      P.e.tabs.content.lastElementChild

    );
  }/*F.onPageLoad()*/);

  /**
     Getter (if called with no args) or setter (if passed an arg) for
     the current file content.

................................................................................

  /**
     updateVersion() updates the filename and version in various UI
     elements...

     Returns this object.
  */
  P.updateVersion = function f(file,rev){
    if(!f.eLinks){
      f.eName = P.e.editStatus.querySelector('span.name');
      f.eLinks = P.e.editStatus.querySelector('span.links');
    }
    if(1===arguments.length){/*assume object*/
      this.finfo = arguments[0];
      file = this.finfo.filename;
      rev = this.finfo.checkin;
    }else if(0===arguments.length){
      if(affirmHasFile()){
        file = this.finfo.filename;
        rev = this.finfo.checkin;
      }
    }else{
      this.finfo = {filename:file,checkin:rev};
    }
    const fi = this.finfo;
    D.clearElement(f.eName, f.eLinks);
    if(!fi){
      D.append(f.eName, '(no file loaded)');
      return this;
    }
    const rHuman = F.hashDigits(rev),
          rUrl = F.hashDigits(rev,true);





    //TODO? port over is-edited marker from /wikiedit
    //var marker = getEditMarker(wi, false);
    D.append(f.eName/*,marker*/,D.a(F.repoUrl('finfo',{name:file, m:rUrl}), file));



    D.append(

      f.eLinks,
      D.append(D.span(), "mimetype "+(fi.mimetype||'???')),
      D.a(F.repoUrl('info/'+rUrl), rHuman),
      D.a(F.repoUrl('timeline',{m:rUrl}), "timeline"),










      D.a(F.repoUrl('annotate',{filename:file, checkin:rUrl}),'annotate'),


      D.a(F.repoUrl('blame',{filename:file, checkin:rUrl}),'blame')

    );
    const purlArgs = F.encodeUrlArgs({
      filename: this.finfo.filename,
      checkin: rUrl
    },false,true);
    const purl = F.repoUrl('fileedit',purlArgs);
    D.append( f.eLinks, D.a(purl,"editor permalink") );




    this.setPageTitle("Edit: "+fi.filename);
    return this;
  };

  /**
     loadFile() loads (file,checkinVersion) and updates the relevant
     UI elements to reflect the loaded state. If passed no arguments
     then it re-uses the values from the currently-loaded file, reloading
................................................................................
        branch: headers['x-fileedit-checkin-branch'],
        isExe: ('x'===headers['x-fileedit-file-perm']),
        mimetype: headers['content-type'].split(';').shift()
      });
      self.tabs.switchToTab(self.e.tabs.content);
      self.e.cbIsExe.checked = self.finfo.isExe;
      self.fileContent(r);
      P.previewNeedsUpdate = true;
      self.dispatchEvent('fileedit-file-loaded', self.finfo);
    };
    const semiFinfo = {filename: file, checkin: rev};
    const stashFinfo = this.getStashedFinfo(semiFinfo);
    if(stashFinfo){ // fake a response from the stash...
      this.finfo = stashFinfo;
      this.e.cbIsExe.checked = !!stashFinfo.isExe;
................................................................................
      responseHeaders: 'x-ajax-render-mode',
      onload: (r,header)=>{
        P.selectPreviewMode(P.previewModes[header]);
        if('wiki'===header) P.baseHrefForFile();
        else P.baseHrefRestore();
        callback(r);
        F.message('Updated preview.');
        P.previewNeedsUpdate = false;
        P.dispatchEvent('fileedit-preview-updated',{
          previewMode: P.previewModes.current,
          mimetype: P.finfo.mimetype,
          element: P.e.previewTarget
        });
      },
      onerror: (e)=>{
................................................................................
      if(onlyFinfo && $stash.hasStashedContent(fi)){
        $stash.updateFile(fi);
      }else{
        $stash.updateFile(fi, P.fileContent());
      }
      F.message("Stashed change to",F.hashDigits(fi.checkin),fi.filename);
      $stash.prune();
      this.previewNeedsUpdate = true;
    }
    return this;
  };

  /**
     Removes any stashed state for the current P.finfo (if set) from
     F.storage. Returns this.
  */
  P.unstashContent = function(){
    const finfo = arguments[0] || this.finfo;
    if(finfo){
      this.previewNeedsUpdate = true;
      $stash.unstash(finfo);
      //console.debug("Unstashed",finfo);
      F.message("Unstashed",F.hashDigits(finfo.checkin),finfo.filename);
    }
    return this;
  };

Changes to src/fossil.page.wikiedit.js.

25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
..
61
62
63
64
65
66
67
68
69

70
71
72
73
74
75
76
77
78
79
80
81
...
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
...
567
568
569
570
571
572
573
574



575
576
577







































































































578
579
580
581
582
583
584
...
642
643
644
645
646
647
648
649
650
651
652
653
654
655

656
657
658
659
660
661
662
...
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
...
794
795
796
797
798
799
800
801












802
803
804
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
...
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
...
976
977
978
979
980
981
982

983

984
985
986
987
988
989
990
     The internal docs and code frequently use the term "winfo", and such
     references refer to an object with that form.

     The fossil.page.wikiContent() method gets or sets the current
     file content for the page.

     - Event 'wiki-saved': is fired when a commit completes,
     passing on the same info as fileedit-file-loaded.

     - Event 'wiki-content-replaced': when the editor's content is
     replaced, as opposed to it being edited via user
     interaction. This normally happens via selecting a file to
     load. The event detail is the fossil.page object, not the current
     file content.

................................................................................
         }
       }
     );
  */
  const E = (s)=>document.querySelector(s),
        D = F.dom,
        P = F.page;

  P.config = {

    /* Symbolic markers to denote certain edit state. */
    editStateMarkers: {
      isNew: '[+]',
      isModified: '[*]'
    }
  };

  /**
     $stash is an internal-use-only object for managing "stashed"
     local edits, to help avoid that users accidentally lose content
     by switching tabs or following links or some such. The basic
     theory of operation is...
................................................................................
      // Force UI update
      s.dispatchEvent(new Event('change',{target:s}));
    }
  };

  /** Internal helper to get an edit status indicator for the given winfo object. */
  const getEditMarker = function f(winfo, textOnly){
    const esm = P.config.editStateMarkers;
    if(1===winfo){ /* force is-new */
        return textOnly ? esm.isNew :
        D.addClass(D.append(D.span(),esm.isNew), 'is-new');
    }else if(2===winfo){ /* force is-modified */
        return textOnly ? esm.isModified :
        D.addClass(D.append(D.span(),esm.isModified), 'is-modified');
    }else if(winfo && winfo.version){ /* is existing page modified? */
................................................................................
      
      D.append(parentElem, btn);
      btn.addEventListener('click', ()=>this.loadList(), false);
      this.loadList();
      const onSelect = (e)=>P.loadPage(e.target.value);
      sel.addEventListener('change', onSelect, false);
      sel.addEventListener('dblclick', onSelect, false);
      F.page.addEventListener('wiki-stash-updated', ()=>this._refreshStashMarks());



      delete this.init;
    }
  };








































































































  /**
     Keep track of how many in-flight AJAX requests there are so we
     can disable input elements while any are pending. For
     simplicity's sake we simply disable ALL OF IT while any AJAX is
     pending, rather than disabling operation-specific UI elements,
     which would be a huge maintenance hassle.
................................................................................
        preview: E('#wikiedit-tab-preview'),
        diff: E('#wikiedit-tab-diff'),
        misc: E('#wikiedit-tab-misc')
        //commit: E('#wikiedit-tab-commit')
      }
    };
    P.tabs = new fossil.TabManager(D.clearElement(P.e.tabContainer));
    P.tabs.e.container.insertBefore(P.e.editStatus, P.tabs.e.tabs);
    P.tabs.e.container.insertBefore(
      /* Move the status bar between the tab buttons and
         tab panels. Seems to be the best fit in terms of
         functionality and visibility. */
      E('#fossil-status-bar'), P.tabs.e.tabs
    );

    P.tabs.addEventListener(
      /* Set up some before-switch-to tab event tasks... */
      'before-switch-to', function(ev){
        const theTab = ev.detail, btnSlot = theTab.querySelector('.save-button-slot');
        if(btnSlot){
          /* Several places make sense for a save button, so we'll
             move that button around to those tabs where it makes sense. */
................................................................................
        }else{
          WikiList.removeEntry(w.name);
          delete P.winfo;
          P.updatePageTitle();
          F.message("Discarded new page ["+w.name+"].");
        }
      },
      ticks: 3
    });
    F.confirmer(P.e.btnSave, {
      confirmText: "Really save changes?",
      onconfirm: function(e){
        const w = P.winfo;
        if(!w){
          F.error("No page loaded.");
          return;
        }
        P.save();
      },
      ticks: 3
    });

    P.e.taEditor.addEventListener(
      'change', ()=>P.stashContentChange(), false
    );
    
    P.selectMimetype(false, true);
................................................................................
      // Clear certain views after a save
      'wiki-saved',
      (e)=>{
        D.clearElement(P.e.diffTarget, P.e.previewTarget);
        // TODO: replace preview with new content
      }
    );
    WikiList.init( P.e.tabs.pageList.firstElementChild );












    P.addEventListener(
      // Update various state on wiki page load
      'wiki-page-loaded',
      function(ev){
        delete P.winfo;
        const winfo = ev.detail;
        P.winfo = winfo;
        P.previewNeedsUpdate = true;
        P.e.selectMimetype.value = winfo.mimetype;
        P.tabs.switchToTab(P.e.tabs.content);
        P.wikiContent(winfo.content || '');
        WikiList.e.select.value = winfo.name;
................................................................................
        if(!winfo.version && winfo.type!=='sandbox'){
          F.message('You are editing a new, unsaved page:',winfo.name);
        }
        P.updatePageTitle();
      },
      false
    );
    P.addEventListener('wiki-stash-updated', ()=>P.updateSaveButton())
      .updatePageTitle().updateSaveButton();

  }/*F.onPageLoad()*/);

  /**
     Returns true if fossil.page.winfo is set, indicating that a page
     has been loaded, else it reports an error and returns false.

     If passed a truthy value any error message about not having
................................................................................
    const wi = this.winfo;
    D.clearElement(f.eName, f.eLinks);
    if(!wi){
      D.append(f.eName, '(no page loaded)');
      return;
    }
    var marker = getEditMarker(wi, false);
    D.append(f.eName,marker,wi.name,);
    if(wi.version){
      D.append(
        f.eLinks,
        D.a(F.repoUrl('whistory',{name:wi.name}),'[history]'),
        D.a(F.repoUrl('attachlist',{page:wi.name}),"[attachments]"),
        D.a(F.repoUrl('attachadd',{page:wi.name,from: F.repoUrl('wikiedit',{name: wi.name})}), "[attach]")
      );
    }
  };

  /**
     Update the page title and header based on the state of
     this.winfo. A no-op if this.winfo is not set. Returns this.
................................................................................
      if(!affirmPageLoaded()) return this;
      name = this.winfo.name;
    }else if(1===arguments.length && 'string' !== typeof name){
      /* Assume winfo-like object */
      const arg = arguments[0];
      name = arg.name;
    }

    const onload = (r)=>this.dispatchEvent('wiki-page-loaded', r);

    const stashWinfo = this.getStashedWinfo({name: name});
    if(stashWinfo){ // fake a response from the stash...
      F.message("Fetched from the local-edit storage:", stashWinfo.name);
      onload({
        name: stashWinfo.name,
        mimetype: stashWinfo.mimetype,
        type: stashWinfo.type,







|







 







<

>
|
|
<
<
<







 







|







 







|
>
>
>



>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







<






>







 







|











|







 







|
>
>
>
>
>
>
>
>
>
>
>
>




<







 







|
|
>







 







|



|
|
|







 







>
|
>







25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
..
61
62
63
64
65
66
67

68
69
70
71



72
73
74
75
76
77
78
...
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
...
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
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
...
745
746
747
748
749
750
751

752
753
754
755
756
757
758
759
760
761
762
763
764
765
...
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
...
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920

921
922
923
924
925
926
927
...
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
...
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
....
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
     The internal docs and code frequently use the term "winfo", and such
     references refer to an object with that form.

     The fossil.page.wikiContent() method gets or sets the current
     file content for the page.

     - Event 'wiki-saved': is fired when a commit completes,
     passing on the same info as wiki-page-loaded.

     - Event 'wiki-content-replaced': when the editor's content is
     replaced, as opposed to it being edited via user
     interaction. This normally happens via selecting a file to
     load. The event detail is the fossil.page object, not the current
     file content.

................................................................................
         }
       }
     );
  */
  const E = (s)=>document.querySelector(s),
        D = F.dom,
        P = F.page;

  P.config = {
    /* Max number of locally-edited pages to stash, after which we
       drop the least-recently used. */
    defaultMaxStashSize: 10



  };

  /**
     $stash is an internal-use-only object for managing "stashed"
     local edits, to help avoid that users accidentally lose content
     by switching tabs or following links or some such. The basic
     theory of operation is...
................................................................................
      // Force UI update
      s.dispatchEvent(new Event('change',{target:s}));
    }
  };

  /** Internal helper to get an edit status indicator for the given winfo object. */
  const getEditMarker = function f(winfo, textOnly){
    const esm = F.config.editStateMarkers;
    if(1===winfo){ /* force is-new */
        return textOnly ? esm.isNew :
        D.addClass(D.append(D.span(),esm.isNew), 'is-new');
    }else if(2===winfo){ /* force is-modified */
        return textOnly ? esm.isModified :
        D.addClass(D.append(D.span(),esm.isModified), 'is-modified');
    }else if(winfo && winfo.version){ /* is existing page modified? */
................................................................................
      
      D.append(parentElem, btn);
      btn.addEventListener('click', ()=>this.loadList(), false);
      this.loadList();
      const onSelect = (e)=>P.loadPage(e.target.value);
      sel.addEventListener('change', onSelect, false);
      sel.addEventListener('dblclick', onSelect, false);
      F.page.addEventListener('wiki-stash-updated', ()=>{
        if(P.winfo) this._refreshStashMarks();
        else this._rebuildList();
      });
      delete this.init;
    }
  };

  /**
     Widget for listing and selecting $stash entries.
  */
  P.stashWidget = {
    e:{/*DOM element(s)*/},
    init: function(domInsertPoint/*insert widget BEFORE this element*/){
      const wrapper = D.addClass(
        D.attr(D.div(),'id','wikiedit-stash-selector'),
        'input-with-label'
      );
      const sel = this.e.select = D.select();
      const btnClear = this.e.btnClear
            = D.addClass(D.button("Clear"),'hidden');
      D.append(wrapper, "Local edits (",
               D.append(D.code(),
                        F.storage.storageImplName()),
               "):",
               sel, btnClear);
      D.attr(wrapper, "title", [
        'Locally-edited wiki pages. Timestamps are the last local edit time.',
        'Only the',P.config.defaultMaxStashSize,'most recent pages',
        'are retained. Saving or reloading a file removes it from this list.'
      ].join(' '));
      D.option(D.disable(sel), "(empty)");
      P.addEventListener('wiki-stash-updated',(e)=>this.updateList(e.detail));
      P.addEventListener('wiki-page-loaded',(e)=>this.updateList($stash, e.detail));
      sel.addEventListener('change',function(e){
        const opt = this.selectedOptions[0];
        if(opt && opt._winfo) P.loadPage(opt._winfo);
      });
      F.confirmer(btnClear, {
        confirmText: "REALLY delete ALL local edits?",
        onconfirm: (e)=>P.clearStash(),
        ticks: F.config.confirmerButtonTicks
      });
      if(F.storage.isTransient()){/*Warn if our storage is particularly transient...*/
        D.append(wrapper, D.append(
          D.addClass(D.span(),'warning'),
          "Warning: persistent storage is not available, "+
            "so uncomitted edits will not survive a page reload."
        ));
      }
      domInsertPoint.parentNode.insertBefore(wrapper, domInsertPoint);
      $stash._fireStashEvent(/*read the page-load-time stash*/);
      delete this.init;
    },
    /**
       Regenerates the edit selection list.
    */
    updateList: function f(stasher,theWinfo){
      console.debug("updateList()",arguments);
      if(!f.compare){
        const cmpBase = (l,r)=>l<r ? -1 : (l===r ? 0 : 1);
        f.compare = (l,r)=>cmpBase(l.name, r.name);
        f.rxZ = /\.\d+Z$/ /* ms and 'Z' part of date string */;
        const pad=(x)=>(''+x).length>1 ? x : '0'+x;
        f.timestring = function ff(d){
          return [
            d.getFullYear(),'-',pad(d.getMonth()+1/*sigh*/),'-',pad(d.getDate()),
            '@',pad(d.getHours()),':',pad(d.getMinutes())
          ].join('');
        };
      }
      const index = stasher.getIndex(), ilist = [];
      Object.keys(index).forEach((winfo)=>{
        ilist.push(index[winfo]);
      });
      const self = this;
      D.clearElement(this.e.select);
      if(0===ilist.length){
        D.addClass(this.e.btnClear, 'hidden');
        D.option(D.disable(this.e.select),"No local edits");
        return;
      }
      D.enable(this.e.select);
      if(false){
        /* 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 currentFinfo = theWinfo || P.winfo || {};
      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, 6)+']' : ' [new/local]',
           ' @ ',
           f.timestring(new Date(winfo.stashTime))
          ].join('')
        );
        opt._winfo = winfo;
        if(0===f.compare(currentFinfo, winfo)){
          D.attr(opt, 'selected', true);
        }
      });
    }
  }/*P.stashWidget*/;

  /**
     Keep track of how many in-flight AJAX requests there are so we
     can disable input elements while any are pending. For
     simplicity's sake we simply disable ALL OF IT while any AJAX is
     pending, rather than disabling operation-specific UI elements,
     which would be a huge maintenance hassle.
................................................................................
        preview: E('#wikiedit-tab-preview'),
        diff: E('#wikiedit-tab-diff'),
        misc: E('#wikiedit-tab-misc')
        //commit: E('#wikiedit-tab-commit')
      }
    };
    P.tabs = new fossil.TabManager(D.clearElement(P.e.tabContainer));

    P.tabs.e.container.insertBefore(
      /* Move the status bar between the tab buttons and
         tab panels. Seems to be the best fit in terms of
         functionality and visibility. */
      E('#fossil-status-bar'), P.tabs.e.tabs
    );
    P.tabs.e.container.insertBefore(P.e.editStatus, P.tabs.e.tabs);
    P.tabs.addEventListener(
      /* Set up some before-switch-to tab event tasks... */
      'before-switch-to', function(ev){
        const theTab = ev.detail, btnSlot = theTab.querySelector('.save-button-slot');
        if(btnSlot){
          /* Several places make sense for a save button, so we'll
             move that button around to those tabs where it makes sense. */
................................................................................
        }else{
          WikiList.removeEntry(w.name);
          delete P.winfo;
          P.updatePageTitle();
          F.message("Discarded new page ["+w.name+"].");
        }
      },
      ticks: F.config.confirmerButtonTicks
    });
    F.confirmer(P.e.btnSave, {
      confirmText: "Really save changes?",
      onconfirm: function(e){
        const w = P.winfo;
        if(!w){
          F.error("No page loaded.");
          return;
        }
        P.save();
      },
      ticks: F.config.confirmerButtonTicks
    });

    P.e.taEditor.addEventListener(
      'change', ()=>P.stashContentChange(), false
    );
    
    P.selectMimetype(false, true);
................................................................................
      // Clear certain views after a save
      'wiki-saved',
      (e)=>{
        D.clearElement(P.e.diffTarget, P.e.previewTarget);
        // TODO: replace preview with new content
      }
    );
    P.addEventListener('wiki-stash-updated',function(){
      /* MUST come before WikiList.init() and P.stashWidget.init() so
         that interwoven event handlers get called in the right
         order. */
      if(P.winfo && !P.winfo.version && !$stash.getWinfo(P.winfo)){
        // New local page was removed.
        delete P.winfo;
        P.wikiContent('');
        P.updatePageTitle();
      }
      P.updateSaveButton();
    }).updatePageTitle().updateSaveButton();

    P.addEventListener(
      // Update various state on wiki page load
      'wiki-page-loaded',
      function(ev){

        const winfo = ev.detail;
        P.winfo = winfo;
        P.previewNeedsUpdate = true;
        P.e.selectMimetype.value = winfo.mimetype;
        P.tabs.switchToTab(P.e.tabs.content);
        P.wikiContent(winfo.content || '');
        WikiList.e.select.value = winfo.name;
................................................................................
        if(!winfo.version && winfo.type!=='sandbox'){
          F.message('You are editing a new, unsaved page:',winfo.name);
        }
        P.updatePageTitle();
      },
      false
    );
    /* These init()s need to come after P's event handlers are registered */
    WikiList.init( P.e.tabs.pageList.firstElementChild );
    P.stashWidget.init(P.e.tabs.content.lastElementChild);
  }/*F.onPageLoad()*/);

  /**
     Returns true if fossil.page.winfo is set, indicating that a page
     has been loaded, else it reports an error and returns false.

     If passed a truthy value any error message about not having
................................................................................
    const wi = this.winfo;
    D.clearElement(f.eName, f.eLinks);
    if(!wi){
      D.append(f.eName, '(no page loaded)');
      return;
    }
    var marker = getEditMarker(wi, false);
    D.append(f.eName,marker,wi.name);
    if(wi.version){
      D.append(
        f.eLinks,
        D.a(F.repoUrl('whistory',{name:wi.name}),'history'),
        D.a(F.repoUrl('attachlist',{page:wi.name}),"attachments"),
        D.a(F.repoUrl('attachadd',{page:wi.name,from: F.repoUrl('wikiedit',{name: wi.name})}), "attach")
      );
    }
  };

  /**
     Update the page title and header based on the state of
     this.winfo. A no-op if this.winfo is not set. Returns this.
................................................................................
      if(!affirmPageLoaded()) return this;
      name = this.winfo.name;
    }else if(1===arguments.length && 'string' !== typeof name){
      /* Assume winfo-like object */
      const arg = arguments[0];
      name = arg.name;
    }
    const onload = (r)=>{
      this.dispatchEvent('wiki-page-loaded', r);
    };
    const stashWinfo = this.getStashedWinfo({name: name});
    if(stashWinfo){ // fake a response from the stash...
      F.message("Fetched from the local-edit storage:", stashWinfo.name);
      onload({
        name: stashWinfo.name,
        mimetype: stashWinfo.mimetype,
        type: stashWinfo.type,

Changes to src/glob.c.

161
162
163
164
165
166
167


















168
169
170
171
172
173
174
*/
void glob_free(Glob *pGlob){
  if( pGlob ){
    fossil_free(pGlob->azPattern);
    fossil_free(pGlob);
  }
}



















/*
** COMMAND: test-glob
**
** Usage:  %fossil test-glob PATTERN STRING...
**
** PATTERN is a comma- and whitespace-separated list of optionally







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
*/
void glob_free(Glob *pGlob){
  if( pGlob ){
    fossil_free(pGlob->azPattern);
    fossil_free(pGlob);
  }
}

/*
** Appends the given glob to the given buffer in the form of a
** JS/JSON-compatible array. It requires that pDest have been
** initialized. If pGlob is NULL or empty it emits [] (an empty
** array).
*/
void glob_render_as_json(Glob *pGlob, Blob *pDest){
  int i = 0;
  blob_append(pDest, "[", 1);
  for( ; pGlob && i < pGlob->nPattern; ++i ){
    if(i){
      blob_append(pDest, ",", 1);
    }
    blob_appendf(pDest, "%!j", pGlob->azPattern[i]);
  }
  blob_append(pDest, "]", 1);
}

/*
** COMMAND: test-glob
**
** Usage:  %fossil test-glob PATTERN STRING...
**
** PATTERN is a comma- and whitespace-separated list of optionally

Changes to src/style.c.

1446
1447
1448
1449
1450
1451
1452
1453

1454
1455






1456
1457
1458
1459
1460
1461
1462
       "if(!window.fossil) window.fossil={};\n"
       "window.fossil.version = %!j;\n"
    /* fossil.rootPath is the top-most CGI/server path,
    ** including a trailing slash. */
       "window.fossil.rootPath = %!j+'/';\n",
       get_version(), g.zTop);
    /* fossil.config = {...various config-level options...} */
    CX("window.fossil.config = {"

       "hashDigits: %d, hashDigitsUrl: %d"
       "};\n", hash_digits(0), hash_digits(1));






#if 0
    /* Is it safe to emit the CSRF token here? Some pages add it
    ** as a hidden form field. */
    if(g.zCsrfToken[0]!=0){
      CX("window.fossil.csrfToken = %!j;\n",
         g.zCsrfToken);
    }







|
>
|
|
>
>
>
>
>
>







1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
       "if(!window.fossil) window.fossil={};\n"
       "window.fossil.version = %!j;\n"
    /* fossil.rootPath is the top-most CGI/server path,
    ** including a trailing slash. */
       "window.fossil.rootPath = %!j+'/';\n",
       get_version(), g.zTop);
    /* fossil.config = {...various config-level options...} */
    CX("window.fossil.config = {");
    CX("/* Length of UUID hashes for display purposes. */");
    CX("hashDigits: %d, hashDigitsUrl: %d,\n",
       hash_digits(0), hash_digits(1));
    CX("editStateMarkers: {"
       "/*Symbolic markers to denote certain edit states.*/"
       "isNew:'[+]', isModified:'[*]'},\n");
    CX("confirmerButtonTicks: 3 "
       "/*default fossil.confirmer tick count.*/\n");
    CX("};\n"/* fossil.config */);
#if 0
    /* Is it safe to emit the CSRF token here? Some pages add it
    ** as a hidden form field. */
    if(g.zCsrfToken[0]!=0){
      CX("window.fossil.csrfToken = %!j;\n",
         g.zCsrfToken);
    }

Changes to src/style.fileedit.css.

124
125
126
127
128
129
130



131
132
133
134
135
136
137
...
181
182
183
184
185
186
187































  margin: 0;
  min-height: 12em;
}
body.fileedit #fileedit-file-selector select {
  margin: 0 0 0.5em 0;
  height: initial;
  font-family: monospace;



}
body.fileedit select:focus {
  border: none;
}
body.fileedit option:focus {
  border: none;
}
................................................................................
  flex-direction: column;
  align-items: stretch;
  width: initial;
}
body.fileedit .sbsdiffcols div.difftxtcol pre {
  max-width: 44em;
}






































>
>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
...
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
  margin: 0;
  min-height: 12em;
}
body.fileedit #fileedit-file-selector select {
  margin: 0 0 0.5em 0;
  height: initial;
  font-family: monospace;
}
body.fileedit #fileedit-file-selector select option {
  margin: 0 0 0.5em 0.55em;
}
body.fileedit select:focus {
  border: none;
}
body.fileedit option:focus {
  border: none;
}
................................................................................
  flex-direction: column;
  align-items: stretch;
  width: initial;
}
body.fileedit .sbsdiffcols div.difftxtcol pre {
  max-width: 44em;
}

body.fileedit #fileedit-edit-status {
  border-radius: 0.25em 0.25em 0 0;
  margin: 0;
  padding: 0;
  width: 100%;
  cursor: initial;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: space-between;
  font-family: monospace;
  font-size: 1.2em;
}
body.fileedit #fileedit-edit-status > span {
  display: block;
}
body.fileedit #fileedit-file-selector span.is-new,
body.fileedit #fileedit-file-selector span.is-modified {
  font-family: monospace;
}
body.fileedit #fileedit-edit-status span.links > * {
  margin: 0 0.25em;
  white-space: nowrap;
}
body.fileedit #fileedit-edit-status span.links > *::before {
  content: "[";
}
body.fileedit #fileedit-edit-status span.links > *::after {
  content: "]";
}

Changes to src/style.wikiedit.css.

136
137
138
139
140
141
142
143
144

145




















body.wikiedit #wikiedit-edit-status > span {
  display: block;
}
body.wikiedit .WikiList span.is-new,
body.wikiedit .WikiList span.is-modified {
  font-family: monospace;
}
body.wikiedit #wikiedit-edit-status > span.links > a {
  margin: 0 0.25em;

}



























|

>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
body.wikiedit #wikiedit-edit-status > span {
  display: block;
}
body.wikiedit .WikiList span.is-new,
body.wikiedit .WikiList span.is-modified {
  font-family: monospace;
}
body.wikiedit #wikiedit-edit-status span.links > a {
  margin: 0 0.25em;
  white-space: nowrap;
}
body.wikiedit #wikiedit-edit-status span.links > a::before {
  content: "[";
}
body.wikiedit #wikiedit-edit-status span.links > a::after {
  content: "]";
}
body.wikiedit #wikiedit-stash-selector {
  margin: 0.25em;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: baseline;
}
body.wikiedit #wikiedit-stash-selector select {
  margin: 0 1em;
  height: initial;
  font-family: monospace;
  flex: 10 1 auto;
}