Fossil SCM

Ported several features between wikiedit and fileedit, improving them both a bit.

stephan 2020-08-01 21:31 trunk
Commit 0d5006bed5f8c8f22a93a70311c76826beadb12dfa14f0bbef4409746f7a23fa
+10 -7
--- src/fileedit.c
+++ src/fileedit.c
@@ -1626,10 +1626,15 @@
16261626
/* Status bar */
16271627
CX("<div id='fossil-status-bar' "
16281628
"title='Status message area. Double-click to clear them.'>"
16291629
"Status messages will go here.</div>\n"
16301630
/* will be moved into the tab container via JS */);
1631
+
1632
+ CX("<div id='fileedit-edit-status'>"
1633
+ "<span class='name'>(no file loaded)</span>"
1634
+ "<span class='links'></span>"
1635
+ "</div>");
16311636
16321637
/* Main tab container... */
16331638
CX("<div id='fileedit-tabs' class='tab-container'></div>");
16341639
16351640
/* The .hidden class on the following tab elements is to help lessen
@@ -1637,17 +1642,13 @@
16371642
16381643
/***** File/version info tab *****/
16391644
{
16401645
CX("<div id='fileedit-tab-fileselect' "
16411646
"data-tab-parent='fileedit-tabs' "
1642
- "data-tab-label='File Info &amp; Selection' "
1647
+ "data-tab-label='File Selection' "
16431648
"class='hidden'"
16441649
">");
1645
- CX("<fieldset id='file-version-details'>"
1646
- "<legend>File/Version</legend>"
1647
- "<div>No file loaded.</div>"
1648
- "</fieldset>");
16491650
CX("<h1>Select a file to edit:</h1>");
16501651
CX("<div id='fileedit-file-selector'></div>");
16511652
CX("</div>"/*#fileedit-tab-fileselect*/);
16521653
}
16531654
@@ -1935,12 +1936,14 @@
19351936
19361937
{
19371938
/* Dynamically populate the editor, display any error in the err
19381939
** blob, and/or switch to tab #0, where the file selector
19391940
** lives... */
1940
- blob_appendf(&endScript,
1941
- "fossil.onPageLoad(");
1941
+ blob_appendf(&endScript, "fossil.config['fileedit-glob'] = ");
1942
+ glob_render_as_json(fileedit_glob(), &endScript);
1943
+ blob_append(&endScript, ";\n", 2);
1944
+ blob_append(&endScript, "fossil.onPageLoad(", -1);
19421945
if(zRev && zFilename){
19431946
assert(0==blob_size(&err));
19441947
blob_appendf(&endScript,
19451948
"()=>fossil.page.loadFile(%!j,%!j)",
19461949
zFilename, cimi.zParentUuid);
19471950
--- src/fileedit.c
+++ src/fileedit.c
@@ -1626,10 +1626,15 @@
1626 /* Status bar */
1627 CX("<div id='fossil-status-bar' "
1628 "title='Status message area. Double-click to clear them.'>"
1629 "Status messages will go here.</div>\n"
1630 /* will be moved into the tab container via JS */);
 
 
 
 
 
1631
1632 /* Main tab container... */
1633 CX("<div id='fileedit-tabs' class='tab-container'></div>");
1634
1635 /* The .hidden class on the following tab elements is to help lessen
@@ -1637,17 +1642,13 @@
1637
1638 /***** File/version info tab *****/
1639 {
1640 CX("<div id='fileedit-tab-fileselect' "
1641 "data-tab-parent='fileedit-tabs' "
1642 "data-tab-label='File Info &amp; Selection' "
1643 "class='hidden'"
1644 ">");
1645 CX("<fieldset id='file-version-details'>"
1646 "<legend>File/Version</legend>"
1647 "<div>No file loaded.</div>"
1648 "</fieldset>");
1649 CX("<h1>Select a file to edit:</h1>");
1650 CX("<div id='fileedit-file-selector'></div>");
1651 CX("</div>"/*#fileedit-tab-fileselect*/);
1652 }
1653
@@ -1935,12 +1936,14 @@
1935
1936 {
1937 /* Dynamically populate the editor, display any error in the err
1938 ** blob, and/or switch to tab #0, where the file selector
1939 ** lives... */
1940 blob_appendf(&endScript,
1941 "fossil.onPageLoad(");
 
 
1942 if(zRev && zFilename){
1943 assert(0==blob_size(&err));
1944 blob_appendf(&endScript,
1945 "()=>fossil.page.loadFile(%!j,%!j)",
1946 zFilename, cimi.zParentUuid);
1947
--- src/fileedit.c
+++ src/fileedit.c
@@ -1626,10 +1626,15 @@
1626 /* Status bar */
1627 CX("<div id='fossil-status-bar' "
1628 "title='Status message area. Double-click to clear them.'>"
1629 "Status messages will go here.</div>\n"
1630 /* will be moved into the tab container via JS */);
1631
1632 CX("<div id='fileedit-edit-status'>"
1633 "<span class='name'>(no file loaded)</span>"
1634 "<span class='links'></span>"
1635 "</div>");
1636
1637 /* Main tab container... */
1638 CX("<div id='fileedit-tabs' class='tab-container'></div>");
1639
1640 /* The .hidden class on the following tab elements is to help lessen
@@ -1637,17 +1642,13 @@
1642
1643 /***** File/version info tab *****/
1644 {
1645 CX("<div id='fileedit-tab-fileselect' "
1646 "data-tab-parent='fileedit-tabs' "
1647 "data-tab-label='File Selection' "
1648 "class='hidden'"
1649 ">");
 
 
 
 
1650 CX("<h1>Select a file to edit:</h1>");
1651 CX("<div id='fileedit-file-selector'></div>");
1652 CX("</div>"/*#fileedit-tab-fileselect*/);
1653 }
1654
@@ -1935,12 +1936,14 @@
1936
1937 {
1938 /* Dynamically populate the editor, display any error in the err
1939 ** blob, and/or switch to tab #0, where the file selector
1940 ** lives... */
1941 blob_appendf(&endScript, "fossil.config['fileedit-glob'] = ");
1942 glob_render_as_json(fileedit_glob(), &endScript);
1943 blob_append(&endScript, ";\n", 2);
1944 blob_append(&endScript, "fossil.onPageLoad(", -1);
1945 if(zRev && zFilename){
1946 assert(0==blob_size(&err));
1947 blob_appendf(&endScript,
1948 "()=>fossil.page.loadFile(%!j,%!j)",
1949 zFilename, cimi.zParentUuid);
1950
--- src/fossil.confirmer.js
+++ src/fossil.confirmer.js
@@ -268,11 +268,12 @@
268268
};
269269
/**
270270
The default options for initConfirmer(). Tweak them to set the
271271
defaults. A couple of them (initialText and confirmText) are
272272
dynamically-generated, and can't reasonably be set in the
273
- defaults.
273
+ defaults. Some, like ticks, cannot be set here because that would
274
+ end up indirectly replacing non-tick timeouts with ticks.
274275
*/
275276
F.confirmer.defaultOpts = {
276277
timeout:3000,
277278
ticks: undefined,
278279
ticktime: 998/*not *quite* 1000*/,
279280
--- src/fossil.confirmer.js
+++ src/fossil.confirmer.js
@@ -268,11 +268,12 @@
268 };
269 /**
270 The default options for initConfirmer(). Tweak them to set the
271 defaults. A couple of them (initialText and confirmText) are
272 dynamically-generated, and can't reasonably be set in the
273 defaults.
 
274 */
275 F.confirmer.defaultOpts = {
276 timeout:3000,
277 ticks: undefined,
278 ticktime: 998/*not *quite* 1000*/,
279
--- src/fossil.confirmer.js
+++ src/fossil.confirmer.js
@@ -268,11 +268,12 @@
268 };
269 /**
270 The default options for initConfirmer(). Tweak them to set the
271 defaults. A couple of them (initialText and confirmText) are
272 dynamically-generated, and can't reasonably be set in the
273 defaults. Some, like ticks, cannot be set here because that would
274 end up indirectly replacing non-tick timeouts with ticks.
275 */
276 F.confirmer.defaultOpts = {
277 timeout:3000,
278 ticks: undefined,
279 ticktime: 998/*not *quite* 1000*/,
280
--- src/fossil.page.fileedit.js
+++ src/fossil.page.fileedit.js
@@ -407,22 +407,34 @@
407407
selFiles,
408408
/* Use a wrapper for btnLoad so that the button itself does not
409409
stretch to fill the parent width: */
410410
D.append(D.addClass(D.div(), 'flex-shrink'), btnLoad)
411411
);
412
+ if(F.config['fileedit-glob']){
413
+ D.append(
414
+ this.e.container,
415
+ D.append(
416
+ D.div(),
417
+ D.append(D.code(),"fileedit-glob"),
418
+ " config setting = ",
419
+ D.append(D.code(), JSON.stringify(F.config['fileedit-glob']))
420
+ )
421
+ );
422
+ }
423
+
412424
this.loadLeaves();
413425
selCi.addEventListener(
414426
'change', (e)=>this.loadFiles(e.target.value), false
415427
);
416
- btnLoad.addEventListener(
417
- 'click', (e)=>{
418
- this.finfo.filename = selFiles.value;
419
- if(this.finfo.filename){
420
- P.loadFile(this.finfo.filename, this.finfo.checkin);
421
- }
422
- }, false
423
- );
428
+ const doLoad = (e)=>{
429
+ this.finfo.filename = selFiles.value;
430
+ if(this.finfo.filename){
431
+ P.loadFile(this.finfo.filename, this.finfo.checkin);
432
+ }
433
+ };
434
+ btnLoad.addEventListener('click', doLoad, false);
435
+ selFiles.addEventListener('dblclick', doLoad, false);
424436
btnReload.addEventListener(
425437
'click', (e)=>this.loadLeaves(), false
426438
);
427439
delete this.init;
428440
}
@@ -459,12 +471,12 @@
459471
const opt = this.selectedOptions[0];
460472
if(opt && opt._finfo) P.loadFile(opt._finfo);
461473
});
462474
F.confirmer(btnClear, {
463475
confirmText: "REALLY delete ALL local edits?",
464
- onconfirm: (e)=>P.clearStash().loadFile(/*in case P.finfo() was in the stash*/),
465
- ticks: 3
476
+ onconfirm: (e)=>P.clearStash().loadFile(/*in case P.finfo was in the stash*/),
477
+ ticks: F.config.confirmerButtonTicks
466478
});
467479
if(F.storage.isTransient()){/*Warn if our storage is particularly transient...*/
468480
D.append(wrapper, D.append(
469481
D.addClass(D.span(),'warning'),
470482
"Warning: persistent storage is not available, "+
@@ -622,11 +634,11 @@
622634
previewTarget: E('#fileedit-tab-preview-wrapper'),
623635
manifestTarget: E('#fileedit-manifest'),
624636
diffTarget: E('#fileedit-tab-diff-wrapper'),
625637
cbIsExe: E('input[type=checkbox][name=exec_bit]'),
626638
cbManifest: E('input[type=checkbox][name=include_manifest]'),
627
- fsFileVersionDetails: E('#file-version-details'),
639
+ editStatus: E('#fileedit-edit-status'),
628640
tabs:{
629641
content: E('#fileedit-tab-content'),
630642
preview: E('#fileedit-tab-preview'),
631643
diff: E('#fileedit-tab-diff'),
632644
commit: E('#fileedit-tab-commit'),
@@ -644,24 +656,24 @@
644656
}else{
645657
P.e.taComment = P.e.taCommentSmall;
646658
D.addClass(P.e.taCommentBig, 'hidden');
647659
}
648660
D.removeClass(P.e.taComment, 'hidden');
649
-
650661
P.tabs.e.container.insertBefore(
651662
/* Move the status bar between the tab buttons and
652663
tab panels. Seems to be the best fit in terms of
653664
functionality and visibility. */
654665
E('#fossil-status-bar'), P.tabs.e.tabs
655666
);
667
+ P.tabs.e.container.insertBefore(P.e.editStatus, P.tabs.e.tabs);
656668
657669
P.tabs.addEventListener(
658670
/* Set up auto-refresh of the preview tab... */
659671
'before-switch-to', function(ev){
660672
if(ev.detail===P.e.tabs.preview){
661673
P.baseHrefForFile();
662
- if(P.e.cbAutoPreview.checked) P.preview();
674
+ if(P.previewNeedsUpdate && P.e.cbAutoPreview.checked) P.preview();
663675
}else if(ev.detail===P.e.tabs.diff){
664676
/* Work around a weird bug where the page gets wider than
665677
the window when the diff tab is NOT in view and the
666678
current SBS diff widget is wider than the window. When
667679
the diff IS in view then CSS overflow magically reduces
@@ -702,11 +714,11 @@
702714
"click",(e)=>P.commit(), false
703715
);
704716
F.confirmer(P.e.btnReload, {
705717
confirmText: "Really reload, losing edits?",
706718
onconfirm: (e)=>P.unstashContent().loadFile(),
707
- ticks: 3
719
+ ticks: F.config.confirmerButtonTicks
708720
});
709721
E('#comment-toggle').addEventListener(
710722
"click",(e)=>P.toggleCommentMode(), false
711723
);
712724
@@ -759,11 +771,14 @@
759771
}
760772
761773
P.addEventListener(
762774
// Clear certain views when new content is loaded/set
763775
'fileedit-content-replaced',
764
- ()=>D.clearElement(P.e.diffTarget, P.e.previewTarget, P.e.manifestTarget)
776
+ ()=>{
777
+ P.previewNeedsUpdate = true;
778
+ D.clearElement(P.e.diffTarget, P.e.previewTarget, P.e.manifestTarget);
779
+ }
765780
);
766781
P.addEventListener(
767782
// Clear certain views after a non-dry-run commit
768783
'fileedit-committed',
769784
(e)=>{
@@ -774,11 +789,10 @@
774789
);
775790
776791
P.fileSelectWidget.init();
777792
P.stashWidget.init(
778793
P.e.tabs.content.lastElementChild
779
- //P.e.tabs.fileSelect.querySelector("h1")
780794
);
781795
}/*F.onPageLoad()*/);
782796
783797
/**
784798
Getter (if called with no args) or setter (if passed an arg) for
@@ -914,63 +928,55 @@
914928
updateVersion() updates the filename and version in various UI
915929
elements...
916930
917931
Returns this object.
918932
*/
919
- P.updateVersion = function(file,rev){
933
+ P.updateVersion = function f(file,rev){
934
+ if(!f.eLinks){
935
+ f.eName = P.e.editStatus.querySelector('span.name');
936
+ f.eLinks = P.e.editStatus.querySelector('span.links');
937
+ }
920938
if(1===arguments.length){/*assume object*/
921939
this.finfo = arguments[0];
922940
file = this.finfo.filename;
923941
rev = this.finfo.checkin;
924942
}else if(0===arguments.length){
925
- if(!affirmHasFile()) return this;
926
- file = this.finfo.filename;
927
- rev = this.finfo.checkin;
943
+ if(affirmHasFile()){
944
+ file = this.finfo.filename;
945
+ rev = this.finfo.checkin;
946
+ }
928947
}else{
929948
this.finfo = {filename:file,checkin:rev};
930949
}
931
- const eTgt = this.e.fsFileVersionDetails.querySelector('div'),
932
- rHuman = F.hashDigits(rev),
933
- rUrl = F.hashDigits(rev,true);
934
- D.clearElement(eTgt);
935
- D.append(
936
- eTgt, "File: ",
937
- D.append(D.code(),
938
- D.a(F.repoUrl('finfo',{name:file, m:rUrl}), file)),
939
- D.br()
940
- );
941
- D.append(
942
- eTgt, "Checkin: ",
943
- D.append(D.code(), D.a(F.repoUrl('info/'+rUrl), rHuman)),
944
- " [",D.a(F.repoUrl('timeline',{m:rUrl}), "timeline"),"]",
945
- D.br()
946
- );
947
- D.append(
948
- eTgt, "Mimetype: ",
949
- D.append(D.code(), this.finfo.mimetype||'???'),
950
- D.br()
951
- );
952
- D.append(
953
- eTgt,
954
- D.append(D.code(), "[",
955
- D.a(F.repoUrl('annotate',{filename:file, checkin:rUrl}),
956
- 'annotate'), "]"),
957
- D.append(D.code(), "[",
958
- D.a(F.repoUrl('blame',{filename:file, checkin:rUrl}),
959
- 'blame'), "]")
950
+ const fi = this.finfo;
951
+ D.clearElement(f.eName, f.eLinks);
952
+ if(!fi){
953
+ D.append(f.eName, '(no file loaded)');
954
+ return this;
955
+ }
956
+ const rHuman = F.hashDigits(rev),
957
+ rUrl = F.hashDigits(rev,true);
958
+
959
+ //TODO? port over is-edited marker from /wikiedit
960
+ //var marker = getEditMarker(wi, false);
961
+ D.append(f.eName/*,marker*/,D.a(F.repoUrl('finfo',{name:file, m:rUrl}), file));
962
+
963
+ D.append(
964
+ f.eLinks,
965
+ D.append(D.span(), "mimetype "+(fi.mimetype||'???')),
966
+ D.a(F.repoUrl('info/'+rUrl), rHuman),
967
+ D.a(F.repoUrl('timeline',{m:rUrl}), "timeline"),
968
+ D.a(F.repoUrl('annotate',{filename:file, checkin:rUrl}),'annotate'),
969
+ D.a(F.repoUrl('blame',{filename:file, checkin:rUrl}),'blame')
960970
);
961971
const purlArgs = F.encodeUrlArgs({
962972
filename: this.finfo.filename,
963973
checkin: rUrl
964974
},false,true);
965975
const purl = F.repoUrl('fileedit',purlArgs);
966
- D.append(
967
- eTgt,
968
- D.append(D.code(),
969
- "[",D.a(purl,"Editor permalink"),"]")
970
- );
971
- this.setPageTitle("Edit: "+this.finfo.filename);
976
+ D.append( f.eLinks, D.a(purl,"editor permalink") );
977
+ this.setPageTitle("Edit: "+fi.filename);
972978
return this;
973979
};
974980
975981
/**
976982
loadFile() loads (file,checkinVersion) and updates the relevant
@@ -1017,10 +1023,11 @@
10171023
mimetype: headers['content-type'].split(';').shift()
10181024
});
10191025
self.tabs.switchToTab(self.e.tabs.content);
10201026
self.e.cbIsExe.checked = self.finfo.isExe;
10211027
self.fileContent(r);
1028
+ P.previewNeedsUpdate = true;
10221029
self.dispatchEvent('fileedit-file-loaded', self.finfo);
10231030
};
10241031
const semiFinfo = {filename: file, checkin: rev};
10251032
const stashFinfo = this.getStashedFinfo(semiFinfo);
10261033
if(stashFinfo){ // fake a response from the stash...
@@ -1100,10 +1107,11 @@
11001107
P.selectPreviewMode(P.previewModes[header]);
11011108
if('wiki'===header) P.baseHrefForFile();
11021109
else P.baseHrefRestore();
11031110
callback(r);
11041111
F.message('Updated preview.');
1112
+ P.previewNeedsUpdate = false;
11051113
P.dispatchEvent('fileedit-preview-updated',{
11061114
previewMode: P.previewModes.current,
11071115
mimetype: P.finfo.mimetype,
11081116
element: P.e.previewTarget
11091117
});
@@ -1275,10 +1283,11 @@
12751283
}else{
12761284
$stash.updateFile(fi, P.fileContent());
12771285
}
12781286
F.message("Stashed change to",F.hashDigits(fi.checkin),fi.filename);
12791287
$stash.prune();
1288
+ this.previewNeedsUpdate = true;
12801289
}
12811290
return this;
12821291
};
12831292
12841293
/**
@@ -1286,10 +1295,11 @@
12861295
F.storage. Returns this.
12871296
*/
12881297
P.unstashContent = function(){
12891298
const finfo = arguments[0] || this.finfo;
12901299
if(finfo){
1300
+ this.previewNeedsUpdate = true;
12911301
$stash.unstash(finfo);
12921302
//console.debug("Unstashed",finfo);
12931303
F.message("Unstashed",F.hashDigits(finfo.checkin),finfo.filename);
12941304
}
12951305
return this;
12961306
--- src/fossil.page.fileedit.js
+++ src/fossil.page.fileedit.js
@@ -407,22 +407,34 @@
407 selFiles,
408 /* Use a wrapper for btnLoad so that the button itself does not
409 stretch to fill the parent width: */
410 D.append(D.addClass(D.div(), 'flex-shrink'), btnLoad)
411 );
 
 
 
 
 
 
 
 
 
 
 
 
412 this.loadLeaves();
413 selCi.addEventListener(
414 'change', (e)=>this.loadFiles(e.target.value), false
415 );
416 btnLoad.addEventListener(
417 'click', (e)=>{
418 this.finfo.filename = selFiles.value;
419 if(this.finfo.filename){
420 P.loadFile(this.finfo.filename, this.finfo.checkin);
421 }
422 }, false
423 );
424 btnReload.addEventListener(
425 'click', (e)=>this.loadLeaves(), false
426 );
427 delete this.init;
428 }
@@ -459,12 +471,12 @@
459 const opt = this.selectedOptions[0];
460 if(opt && opt._finfo) P.loadFile(opt._finfo);
461 });
462 F.confirmer(btnClear, {
463 confirmText: "REALLY delete ALL local edits?",
464 onconfirm: (e)=>P.clearStash().loadFile(/*in case P.finfo() was in the stash*/),
465 ticks: 3
466 });
467 if(F.storage.isTransient()){/*Warn if our storage is particularly transient...*/
468 D.append(wrapper, D.append(
469 D.addClass(D.span(),'warning'),
470 "Warning: persistent storage is not available, "+
@@ -622,11 +634,11 @@
622 previewTarget: E('#fileedit-tab-preview-wrapper'),
623 manifestTarget: E('#fileedit-manifest'),
624 diffTarget: E('#fileedit-tab-diff-wrapper'),
625 cbIsExe: E('input[type=checkbox][name=exec_bit]'),
626 cbManifest: E('input[type=checkbox][name=include_manifest]'),
627 fsFileVersionDetails: E('#file-version-details'),
628 tabs:{
629 content: E('#fileedit-tab-content'),
630 preview: E('#fileedit-tab-preview'),
631 diff: E('#fileedit-tab-diff'),
632 commit: E('#fileedit-tab-commit'),
@@ -644,24 +656,24 @@
644 }else{
645 P.e.taComment = P.e.taCommentSmall;
646 D.addClass(P.e.taCommentBig, 'hidden');
647 }
648 D.removeClass(P.e.taComment, 'hidden');
649
650 P.tabs.e.container.insertBefore(
651 /* Move the status bar between the tab buttons and
652 tab panels. Seems to be the best fit in terms of
653 functionality and visibility. */
654 E('#fossil-status-bar'), P.tabs.e.tabs
655 );
 
656
657 P.tabs.addEventListener(
658 /* Set up auto-refresh of the preview tab... */
659 'before-switch-to', function(ev){
660 if(ev.detail===P.e.tabs.preview){
661 P.baseHrefForFile();
662 if(P.e.cbAutoPreview.checked) P.preview();
663 }else if(ev.detail===P.e.tabs.diff){
664 /* Work around a weird bug where the page gets wider than
665 the window when the diff tab is NOT in view and the
666 current SBS diff widget is wider than the window. When
667 the diff IS in view then CSS overflow magically reduces
@@ -702,11 +714,11 @@
702 "click",(e)=>P.commit(), false
703 );
704 F.confirmer(P.e.btnReload, {
705 confirmText: "Really reload, losing edits?",
706 onconfirm: (e)=>P.unstashContent().loadFile(),
707 ticks: 3
708 });
709 E('#comment-toggle').addEventListener(
710 "click",(e)=>P.toggleCommentMode(), false
711 );
712
@@ -759,11 +771,14 @@
759 }
760
761 P.addEventListener(
762 // Clear certain views when new content is loaded/set
763 'fileedit-content-replaced',
764 ()=>D.clearElement(P.e.diffTarget, P.e.previewTarget, P.e.manifestTarget)
 
 
 
765 );
766 P.addEventListener(
767 // Clear certain views after a non-dry-run commit
768 'fileedit-committed',
769 (e)=>{
@@ -774,11 +789,10 @@
774 );
775
776 P.fileSelectWidget.init();
777 P.stashWidget.init(
778 P.e.tabs.content.lastElementChild
779 //P.e.tabs.fileSelect.querySelector("h1")
780 );
781 }/*F.onPageLoad()*/);
782
783 /**
784 Getter (if called with no args) or setter (if passed an arg) for
@@ -914,63 +928,55 @@
914 updateVersion() updates the filename and version in various UI
915 elements...
916
917 Returns this object.
918 */
919 P.updateVersion = function(file,rev){
 
 
 
 
920 if(1===arguments.length){/*assume object*/
921 this.finfo = arguments[0];
922 file = this.finfo.filename;
923 rev = this.finfo.checkin;
924 }else if(0===arguments.length){
925 if(!affirmHasFile()) return this;
926 file = this.finfo.filename;
927 rev = this.finfo.checkin;
 
928 }else{
929 this.finfo = {filename:file,checkin:rev};
930 }
931 const eTgt = this.e.fsFileVersionDetails.querySelector('div'),
932 rHuman = F.hashDigits(rev),
933 rUrl = F.hashDigits(rev,true);
934 D.clearElement(eTgt);
935 D.append(
936 eTgt, "File: ",
937 D.append(D.code(),
938 D.a(F.repoUrl('finfo',{name:file, m:rUrl}), file)),
939 D.br()
940 );
941 D.append(
942 eTgt, "Checkin: ",
943 D.append(D.code(), D.a(F.repoUrl('info/'+rUrl), rHuman)),
944 " [",D.a(F.repoUrl('timeline',{m:rUrl}), "timeline"),"]",
945 D.br()
946 );
947 D.append(
948 eTgt, "Mimetype: ",
949 D.append(D.code(), this.finfo.mimetype||'???'),
950 D.br()
951 );
952 D.append(
953 eTgt,
954 D.append(D.code(), "[",
955 D.a(F.repoUrl('annotate',{filename:file, checkin:rUrl}),
956 'annotate'), "]"),
957 D.append(D.code(), "[",
958 D.a(F.repoUrl('blame',{filename:file, checkin:rUrl}),
959 'blame'), "]")
960 );
961 const purlArgs = F.encodeUrlArgs({
962 filename: this.finfo.filename,
963 checkin: rUrl
964 },false,true);
965 const purl = F.repoUrl('fileedit',purlArgs);
966 D.append(
967 eTgt,
968 D.append(D.code(),
969 "[",D.a(purl,"Editor permalink"),"]")
970 );
971 this.setPageTitle("Edit: "+this.finfo.filename);
972 return this;
973 };
974
975 /**
976 loadFile() loads (file,checkinVersion) and updates the relevant
@@ -1017,10 +1023,11 @@
1017 mimetype: headers['content-type'].split(';').shift()
1018 });
1019 self.tabs.switchToTab(self.e.tabs.content);
1020 self.e.cbIsExe.checked = self.finfo.isExe;
1021 self.fileContent(r);
 
1022 self.dispatchEvent('fileedit-file-loaded', self.finfo);
1023 };
1024 const semiFinfo = {filename: file, checkin: rev};
1025 const stashFinfo = this.getStashedFinfo(semiFinfo);
1026 if(stashFinfo){ // fake a response from the stash...
@@ -1100,10 +1107,11 @@
1100 P.selectPreviewMode(P.previewModes[header]);
1101 if('wiki'===header) P.baseHrefForFile();
1102 else P.baseHrefRestore();
1103 callback(r);
1104 F.message('Updated preview.');
 
1105 P.dispatchEvent('fileedit-preview-updated',{
1106 previewMode: P.previewModes.current,
1107 mimetype: P.finfo.mimetype,
1108 element: P.e.previewTarget
1109 });
@@ -1275,10 +1283,11 @@
1275 }else{
1276 $stash.updateFile(fi, P.fileContent());
1277 }
1278 F.message("Stashed change to",F.hashDigits(fi.checkin),fi.filename);
1279 $stash.prune();
 
1280 }
1281 return this;
1282 };
1283
1284 /**
@@ -1286,10 +1295,11 @@
1286 F.storage. Returns this.
1287 */
1288 P.unstashContent = function(){
1289 const finfo = arguments[0] || this.finfo;
1290 if(finfo){
 
1291 $stash.unstash(finfo);
1292 //console.debug("Unstashed",finfo);
1293 F.message("Unstashed",F.hashDigits(finfo.checkin),finfo.filename);
1294 }
1295 return this;
1296
--- src/fossil.page.fileedit.js
+++ src/fossil.page.fileedit.js
@@ -407,22 +407,34 @@
407 selFiles,
408 /* Use a wrapper for btnLoad so that the button itself does not
409 stretch to fill the parent width: */
410 D.append(D.addClass(D.div(), 'flex-shrink'), btnLoad)
411 );
412 if(F.config['fileedit-glob']){
413 D.append(
414 this.e.container,
415 D.append(
416 D.div(),
417 D.append(D.code(),"fileedit-glob"),
418 " config setting = ",
419 D.append(D.code(), JSON.stringify(F.config['fileedit-glob']))
420 )
421 );
422 }
423
424 this.loadLeaves();
425 selCi.addEventListener(
426 'change', (e)=>this.loadFiles(e.target.value), false
427 );
428 const doLoad = (e)=>{
429 this.finfo.filename = selFiles.value;
430 if(this.finfo.filename){
431 P.loadFile(this.finfo.filename, this.finfo.checkin);
432 }
433 };
434 btnLoad.addEventListener('click', doLoad, false);
435 selFiles.addEventListener('dblclick', doLoad, false);
436 btnReload.addEventListener(
437 'click', (e)=>this.loadLeaves(), false
438 );
439 delete this.init;
440 }
@@ -459,12 +471,12 @@
471 const opt = this.selectedOptions[0];
472 if(opt && opt._finfo) P.loadFile(opt._finfo);
473 });
474 F.confirmer(btnClear, {
475 confirmText: "REALLY delete ALL local edits?",
476 onconfirm: (e)=>P.clearStash().loadFile(/*in case P.finfo was in the stash*/),
477 ticks: F.config.confirmerButtonTicks
478 });
479 if(F.storage.isTransient()){/*Warn if our storage is particularly transient...*/
480 D.append(wrapper, D.append(
481 D.addClass(D.span(),'warning'),
482 "Warning: persistent storage is not available, "+
@@ -622,11 +634,11 @@
634 previewTarget: E('#fileedit-tab-preview-wrapper'),
635 manifestTarget: E('#fileedit-manifest'),
636 diffTarget: E('#fileedit-tab-diff-wrapper'),
637 cbIsExe: E('input[type=checkbox][name=exec_bit]'),
638 cbManifest: E('input[type=checkbox][name=include_manifest]'),
639 editStatus: E('#fileedit-edit-status'),
640 tabs:{
641 content: E('#fileedit-tab-content'),
642 preview: E('#fileedit-tab-preview'),
643 diff: E('#fileedit-tab-diff'),
644 commit: E('#fileedit-tab-commit'),
@@ -644,24 +656,24 @@
656 }else{
657 P.e.taComment = P.e.taCommentSmall;
658 D.addClass(P.e.taCommentBig, 'hidden');
659 }
660 D.removeClass(P.e.taComment, 'hidden');
 
661 P.tabs.e.container.insertBefore(
662 /* Move the status bar between the tab buttons and
663 tab panels. Seems to be the best fit in terms of
664 functionality and visibility. */
665 E('#fossil-status-bar'), P.tabs.e.tabs
666 );
667 P.tabs.e.container.insertBefore(P.e.editStatus, P.tabs.e.tabs);
668
669 P.tabs.addEventListener(
670 /* Set up auto-refresh of the preview tab... */
671 'before-switch-to', function(ev){
672 if(ev.detail===P.e.tabs.preview){
673 P.baseHrefForFile();
674 if(P.previewNeedsUpdate && P.e.cbAutoPreview.checked) P.preview();
675 }else if(ev.detail===P.e.tabs.diff){
676 /* Work around a weird bug where the page gets wider than
677 the window when the diff tab is NOT in view and the
678 current SBS diff widget is wider than the window. When
679 the diff IS in view then CSS overflow magically reduces
@@ -702,11 +714,11 @@
714 "click",(e)=>P.commit(), false
715 );
716 F.confirmer(P.e.btnReload, {
717 confirmText: "Really reload, losing edits?",
718 onconfirm: (e)=>P.unstashContent().loadFile(),
719 ticks: F.config.confirmerButtonTicks
720 });
721 E('#comment-toggle').addEventListener(
722 "click",(e)=>P.toggleCommentMode(), false
723 );
724
@@ -759,11 +771,14 @@
771 }
772
773 P.addEventListener(
774 // Clear certain views when new content is loaded/set
775 'fileedit-content-replaced',
776 ()=>{
777 P.previewNeedsUpdate = true;
778 D.clearElement(P.e.diffTarget, P.e.previewTarget, P.e.manifestTarget);
779 }
780 );
781 P.addEventListener(
782 // Clear certain views after a non-dry-run commit
783 'fileedit-committed',
784 (e)=>{
@@ -774,11 +789,10 @@
789 );
790
791 P.fileSelectWidget.init();
792 P.stashWidget.init(
793 P.e.tabs.content.lastElementChild
 
794 );
795 }/*F.onPageLoad()*/);
796
797 /**
798 Getter (if called with no args) or setter (if passed an arg) for
@@ -914,63 +928,55 @@
928 updateVersion() updates the filename and version in various UI
929 elements...
930
931 Returns this object.
932 */
933 P.updateVersion = function f(file,rev){
934 if(!f.eLinks){
935 f.eName = P.e.editStatus.querySelector('span.name');
936 f.eLinks = P.e.editStatus.querySelector('span.links');
937 }
938 if(1===arguments.length){/*assume object*/
939 this.finfo = arguments[0];
940 file = this.finfo.filename;
941 rev = this.finfo.checkin;
942 }else if(0===arguments.length){
943 if(affirmHasFile()){
944 file = this.finfo.filename;
945 rev = this.finfo.checkin;
946 }
947 }else{
948 this.finfo = {filename:file,checkin:rev};
949 }
950 const fi = this.finfo;
951 D.clearElement(f.eName, f.eLinks);
952 if(!fi){
953 D.append(f.eName, '(no file loaded)');
954 return this;
955 }
956 const rHuman = F.hashDigits(rev),
957 rUrl = F.hashDigits(rev,true);
958
959 //TODO? port over is-edited marker from /wikiedit
960 //var marker = getEditMarker(wi, false);
961 D.append(f.eName/*,marker*/,D.a(F.repoUrl('finfo',{name:file, m:rUrl}), file));
962
963 D.append(
964 f.eLinks,
965 D.append(D.span(), "mimetype "+(fi.mimetype||'???')),
966 D.a(F.repoUrl('info/'+rUrl), rHuman),
967 D.a(F.repoUrl('timeline',{m:rUrl}), "timeline"),
968 D.a(F.repoUrl('annotate',{filename:file, checkin:rUrl}),'annotate'),
969 D.a(F.repoUrl('blame',{filename:file, checkin:rUrl}),'blame')
 
 
 
 
 
 
 
 
 
970 );
971 const purlArgs = F.encodeUrlArgs({
972 filename: this.finfo.filename,
973 checkin: rUrl
974 },false,true);
975 const purl = F.repoUrl('fileedit',purlArgs);
976 D.append( f.eLinks, D.a(purl,"editor permalink") );
977 this.setPageTitle("Edit: "+fi.filename);
 
 
 
 
978 return this;
979 };
980
981 /**
982 loadFile() loads (file,checkinVersion) and updates the relevant
@@ -1017,10 +1023,11 @@
1023 mimetype: headers['content-type'].split(';').shift()
1024 });
1025 self.tabs.switchToTab(self.e.tabs.content);
1026 self.e.cbIsExe.checked = self.finfo.isExe;
1027 self.fileContent(r);
1028 P.previewNeedsUpdate = true;
1029 self.dispatchEvent('fileedit-file-loaded', self.finfo);
1030 };
1031 const semiFinfo = {filename: file, checkin: rev};
1032 const stashFinfo = this.getStashedFinfo(semiFinfo);
1033 if(stashFinfo){ // fake a response from the stash...
@@ -1100,10 +1107,11 @@
1107 P.selectPreviewMode(P.previewModes[header]);
1108 if('wiki'===header) P.baseHrefForFile();
1109 else P.baseHrefRestore();
1110 callback(r);
1111 F.message('Updated preview.');
1112 P.previewNeedsUpdate = false;
1113 P.dispatchEvent('fileedit-preview-updated',{
1114 previewMode: P.previewModes.current,
1115 mimetype: P.finfo.mimetype,
1116 element: P.e.previewTarget
1117 });
@@ -1275,10 +1283,11 @@
1283 }else{
1284 $stash.updateFile(fi, P.fileContent());
1285 }
1286 F.message("Stashed change to",F.hashDigits(fi.checkin),fi.filename);
1287 $stash.prune();
1288 this.previewNeedsUpdate = true;
1289 }
1290 return this;
1291 };
1292
1293 /**
@@ -1286,10 +1295,11 @@
1295 F.storage. Returns this.
1296 */
1297 P.unstashContent = function(){
1298 const finfo = arguments[0] || this.finfo;
1299 if(finfo){
1300 this.previewNeedsUpdate = true;
1301 $stash.unstash(finfo);
1302 //console.debug("Unstashed",finfo);
1303 F.message("Unstashed",F.hashDigits(finfo.checkin),finfo.filename);
1304 }
1305 return this;
1306
--- src/fossil.page.wikiedit.js
+++ src/fossil.page.wikiedit.js
@@ -27,11 +27,11 @@
2727
2828
The fossil.page.wikiContent() method gets or sets the current
2929
file content for the page.
3030
3131
- Event 'wiki-saved': is fired when a commit completes,
32
- passing on the same info as fileedit-file-loaded.
32
+ passing on the same info as wiki-page-loaded.
3333
3434
- Event 'wiki-content-replaced': when the editor's content is
3535
replaced, as opposed to it being edited via user
3636
interaction. This normally happens via selecting a file to
3737
load. The event detail is the fossil.page object, not the current
@@ -63,17 +63,14 @@
6363
);
6464
*/
6565
const E = (s)=>document.querySelector(s),
6666
D = F.dom,
6767
P = F.page;
68
-
6968
P.config = {
70
- /* Symbolic markers to denote certain edit state. */
71
- editStateMarkers: {
72
- isNew: '[+]',
73
- isModified: '[*]'
74
- }
69
+ /* Max number of locally-edited pages to stash, after which we
70
+ drop the least-recently used. */
71
+ defaultMaxStashSize: 10
7572
};
7673
7774
/**
7875
$stash is an internal-use-only object for managing "stashed"
7976
local edits, to help avoid that users accidentally lose content
@@ -269,11 +266,11 @@
269266
}
270267
};
271268
272269
/** Internal helper to get an edit status indicator for the given winfo object. */
273270
const getEditMarker = function f(winfo, textOnly){
274
- const esm = P.config.editStateMarkers;
271
+ const esm = F.config.editStateMarkers;
275272
if(1===winfo){ /* force is-new */
276273
return textOnly ? esm.isNew :
277274
D.addClass(D.append(D.span(),esm.isNew), 'is-new');
278275
}else if(2===winfo){ /* force is-modified */
279276
return textOnly ? esm.isModified :
@@ -569,14 +566,120 @@
569566
btn.addEventListener('click', ()=>this.loadList(), false);
570567
this.loadList();
571568
const onSelect = (e)=>P.loadPage(e.target.value);
572569
sel.addEventListener('change', onSelect, false);
573570
sel.addEventListener('dblclick', onSelect, false);
574
- F.page.addEventListener('wiki-stash-updated', ()=>this._refreshStashMarks());
571
+ F.page.addEventListener('wiki-stash-updated', ()=>{
572
+ if(P.winfo) this._refreshStashMarks();
573
+ else this._rebuildList();
574
+ });
575
+ delete this.init;
576
+ }
577
+ };
578
+
579
+ /**
580
+ Widget for listing and selecting $stash entries.
581
+ */
582
+ P.stashWidget = {
583
+ e:{/*DOM element(s)*/},
584
+ init: function(domInsertPoint/*insert widget BEFORE this element*/){
585
+ const wrapper = D.addClass(
586
+ D.attr(D.div(),'id','wikiedit-stash-selector'),
587
+ 'input-with-label'
588
+ );
589
+ const sel = this.e.select = D.select();
590
+ const btnClear = this.e.btnClear
591
+ = D.addClass(D.button("Clear"),'hidden');
592
+ D.append(wrapper, "Local edits (",
593
+ D.append(D.code(),
594
+ F.storage.storageImplName()),
595
+ "):",
596
+ sel, btnClear);
597
+ D.attr(wrapper, "title", [
598
+ 'Locally-edited wiki pages. Timestamps are the last local edit time.',
599
+ 'Only the',P.config.defaultMaxStashSize,'most recent pages',
600
+ 'are retained. Saving or reloading a file removes it from this list.'
601
+ ].join(' '));
602
+ D.option(D.disable(sel), "(empty)");
603
+ P.addEventListener('wiki-stash-updated',(e)=>this.updateList(e.detail));
604
+ P.addEventListener('wiki-page-loaded',(e)=>this.updateList($stash, e.detail));
605
+ sel.addEventListener('change',function(e){
606
+ const opt = this.selectedOptions[0];
607
+ if(opt && opt._winfo) P.loadPage(opt._winfo);
608
+ });
609
+ F.confirmer(btnClear, {
610
+ confirmText: "REALLY delete ALL local edits?",
611
+ onconfirm: (e)=>P.clearStash(),
612
+ ticks: F.config.confirmerButtonTicks
613
+ });
614
+ if(F.storage.isTransient()){/*Warn if our storage is particularly transient...*/
615
+ D.append(wrapper, D.append(
616
+ D.addClass(D.span(),'warning'),
617
+ "Warning: persistent storage is not available, "+
618
+ "so uncomitted edits will not survive a page reload."
619
+ ));
620
+ }
621
+ domInsertPoint.parentNode.insertBefore(wrapper, domInsertPoint);
622
+ $stash._fireStashEvent(/*read the page-load-time stash*/);
575623
delete this.init;
624
+ },
625
+ /**
626
+ Regenerates the edit selection list.
627
+ */
628
+ updateList: function f(stasher,theWinfo){
629
+ console.debug("updateList()",arguments);
630
+ if(!f.compare){
631
+ const cmpBase = (l,r)=>l<r ? -1 : (l===r ? 0 : 1);
632
+ f.compare = (l,r)=>cmpBase(l.name, r.name);
633
+ f.rxZ = /\.\d+Z$/ /* ms and 'Z' part of date string */;
634
+ const pad=(x)=>(''+x).length>1 ? x : '0'+x;
635
+ f.timestring = function ff(d){
636
+ return [
637
+ d.getFullYear(),'-',pad(d.getMonth()+1/*sigh*/),'-',pad(d.getDate()),
638
+ '@',pad(d.getHours()),':',pad(d.getMinutes())
639
+ ].join('');
640
+ };
641
+ }
642
+ const index = stasher.getIndex(), ilist = [];
643
+ Object.keys(index).forEach((winfo)=>{
644
+ ilist.push(index[winfo]);
645
+ });
646
+ const self = this;
647
+ D.clearElement(this.e.select);
648
+ if(0===ilist.length){
649
+ D.addClass(this.e.btnClear, 'hidden');
650
+ D.option(D.disable(this.e.select),"No local edits");
651
+ return;
652
+ }
653
+ D.enable(this.e.select);
654
+ if(false){
655
+ /* The problem with this Clear button is that it allows the user
656
+ to nuke a non-empty newly-added page without the failsafe confirmation
657
+ we have if they use P.e.btnReload. Not yet sure how best to resolve that,
658
+ so we'll leave the button hidden for the time being. */
659
+ D.removeClass(this.e.btnClear, 'hidden');
660
+ }
661
+ D.disable(D.option(this.e.select,0,"Select a local edit..."));
662
+ const currentFinfo = theWinfo || P.winfo || {};
663
+ ilist.sort(f.compare).forEach(function(winfo,n){
664
+ const key = stasher.indexKey(winfo),
665
+ rev = winfo.version || '';
666
+ const opt = D.option(
667
+ self.e.select, n+1/*value is (almost) irrelevant*/,
668
+ [winfo.name,
669
+ rev ? ' ['+F.hashDigits(rev, 6)+']' : ' [new/local]',
670
+ ' @ ',
671
+ f.timestring(new Date(winfo.stashTime))
672
+ ].join('')
673
+ );
674
+ opt._winfo = winfo;
675
+ if(0===f.compare(currentFinfo, winfo)){
676
+ D.attr(opt, 'selected', true);
677
+ }
678
+ });
576679
}
577
- };
680
+ }/*P.stashWidget*/;
578681
579682
/**
580683
Keep track of how many in-flight AJAX requests there are so we
581684
can disable input elements while any are pending. For
582685
simplicity's sake we simply disable ALL OF IT while any AJAX is
@@ -644,17 +747,17 @@
644747
misc: E('#wikiedit-tab-misc')
645748
//commit: E('#wikiedit-tab-commit')
646749
}
647750
};
648751
P.tabs = new fossil.TabManager(D.clearElement(P.e.tabContainer));
649
- P.tabs.e.container.insertBefore(P.e.editStatus, P.tabs.e.tabs);
650752
P.tabs.e.container.insertBefore(
651753
/* Move the status bar between the tab buttons and
652754
tab panels. Seems to be the best fit in terms of
653755
functionality and visibility. */
654756
E('#fossil-status-bar'), P.tabs.e.tabs
655757
);
758
+ P.tabs.e.container.insertBefore(P.e.editStatus, P.tabs.e.tabs);
656759
P.tabs.addEventListener(
657760
/* Set up some before-switch-to tab event tasks... */
658761
'before-switch-to', function(ev){
659762
const theTab = ev.detail, btnSlot = theTab.querySelector('.save-button-slot');
660763
if(btnSlot){
@@ -733,11 +836,11 @@
733836
delete P.winfo;
734837
P.updatePageTitle();
735838
F.message("Discarded new page ["+w.name+"].");
736839
}
737840
},
738
- ticks: 3
841
+ ticks: F.config.confirmerButtonTicks
739842
});
740843
F.confirmer(P.e.btnSave, {
741844
confirmText: "Really save changes?",
742845
onconfirm: function(e){
743846
const w = P.winfo;
@@ -745,11 +848,11 @@
745848
F.error("No page loaded.");
746849
return;
747850
}
748851
P.save();
749852
},
750
- ticks: 3
853
+ ticks: F.config.confirmerButtonTicks
751854
});
752855
753856
P.e.taEditor.addEventListener(
754857
'change', ()=>P.stashContentChange(), false
755858
);
@@ -796,16 +899,27 @@
796899
(e)=>{
797900
D.clearElement(P.e.diffTarget, P.e.previewTarget);
798901
// TODO: replace preview with new content
799902
}
800903
);
801
- WikiList.init( P.e.tabs.pageList.firstElementChild );
904
+ P.addEventListener('wiki-stash-updated',function(){
905
+ /* MUST come before WikiList.init() and P.stashWidget.init() so
906
+ that interwoven event handlers get called in the right
907
+ order. */
908
+ if(P.winfo && !P.winfo.version && !$stash.getWinfo(P.winfo)){
909
+ // New local page was removed.
910
+ delete P.winfo;
911
+ P.wikiContent('');
912
+ P.updatePageTitle();
913
+ }
914
+ P.updateSaveButton();
915
+ }).updatePageTitle().updateSaveButton();
916
+
802917
P.addEventListener(
803918
// Update various state on wiki page load
804919
'wiki-page-loaded',
805920
function(ev){
806
- delete P.winfo;
807921
const winfo = ev.detail;
808922
P.winfo = winfo;
809923
P.previewNeedsUpdate = true;
810924
P.e.selectMimetype.value = winfo.mimetype;
811925
P.tabs.switchToTab(P.e.tabs.content);
@@ -816,12 +930,13 @@
816930
}
817931
P.updatePageTitle();
818932
},
819933
false
820934
);
821
- P.addEventListener('wiki-stash-updated', ()=>P.updateSaveButton())
822
- .updatePageTitle().updateSaveButton();
935
+ /* These init()s need to come after P's event handlers are registered */
936
+ WikiList.init( P.e.tabs.pageList.firstElementChild );
937
+ P.stashWidget.init(P.e.tabs.content.lastElementChild);
823938
}/*F.onPageLoad()*/);
824939
825940
/**
826941
Returns true if fossil.page.winfo is set, indicating that a page
827942
has been loaded, else it reports an error and returns false.
@@ -845,17 +960,17 @@
845960
if(!wi){
846961
D.append(f.eName, '(no page loaded)');
847962
return;
848963
}
849964
var marker = getEditMarker(wi, false);
850
- D.append(f.eName,marker,wi.name,);
965
+ D.append(f.eName,marker,wi.name);
851966
if(wi.version){
852967
D.append(
853968
f.eLinks,
854
- D.a(F.repoUrl('whistory',{name:wi.name}),'[history]'),
855
- D.a(F.repoUrl('attachlist',{page:wi.name}),"[attachments]"),
856
- D.a(F.repoUrl('attachadd',{page:wi.name,from: F.repoUrl('wikiedit',{name: wi.name})}), "[attach]")
969
+ D.a(F.repoUrl('whistory',{name:wi.name}),'history'),
970
+ D.a(F.repoUrl('attachlist',{page:wi.name}),"attachments"),
971
+ D.a(F.repoUrl('attachadd',{page:wi.name,from: F.repoUrl('wikiedit',{name: wi.name})}), "attach")
857972
);
858973
}
859974
};
860975
861976
/**
@@ -978,11 +1093,13 @@
9781093
}else if(1===arguments.length && 'string' !== typeof name){
9791094
/* Assume winfo-like object */
9801095
const arg = arguments[0];
9811096
name = arg.name;
9821097
}
983
- const onload = (r)=>this.dispatchEvent('wiki-page-loaded', r);
1098
+ const onload = (r)=>{
1099
+ this.dispatchEvent('wiki-page-loaded', r);
1100
+ };
9841101
const stashWinfo = this.getStashedWinfo({name: name});
9851102
if(stashWinfo){ // fake a response from the stash...
9861103
F.message("Fetched from the local-edit storage:", stashWinfo.name);
9871104
onload({
9881105
name: stashWinfo.name,
9891106
--- src/fossil.page.wikiedit.js
+++ src/fossil.page.wikiedit.js
@@ -27,11 +27,11 @@
27
28 The fossil.page.wikiContent() method gets or sets the current
29 file content for the page.
30
31 - Event 'wiki-saved': is fired when a commit completes,
32 passing on the same info as fileedit-file-loaded.
33
34 - Event 'wiki-content-replaced': when the editor's content is
35 replaced, as opposed to it being edited via user
36 interaction. This normally happens via selecting a file to
37 load. The event detail is the fossil.page object, not the current
@@ -63,17 +63,14 @@
63 );
64 */
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 };
76
77 /**
78 $stash is an internal-use-only object for managing "stashed"
79 local edits, to help avoid that users accidentally lose content
@@ -269,11 +266,11 @@
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 :
@@ -569,14 +566,120 @@
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 /**
580 Keep track of how many in-flight AJAX requests there are so we
581 can disable input elements while any are pending. For
582 simplicity's sake we simply disable ALL OF IT while any AJAX is
@@ -644,17 +747,17 @@
644 misc: E('#wikiedit-tab-misc')
645 //commit: E('#wikiedit-tab-commit')
646 }
647 };
648 P.tabs = new fossil.TabManager(D.clearElement(P.e.tabContainer));
649 P.tabs.e.container.insertBefore(P.e.editStatus, P.tabs.e.tabs);
650 P.tabs.e.container.insertBefore(
651 /* Move the status bar between the tab buttons and
652 tab panels. Seems to be the best fit in terms of
653 functionality and visibility. */
654 E('#fossil-status-bar'), P.tabs.e.tabs
655 );
 
656 P.tabs.addEventListener(
657 /* Set up some before-switch-to tab event tasks... */
658 'before-switch-to', function(ev){
659 const theTab = ev.detail, btnSlot = theTab.querySelector('.save-button-slot');
660 if(btnSlot){
@@ -733,11 +836,11 @@
733 delete P.winfo;
734 P.updatePageTitle();
735 F.message("Discarded new page ["+w.name+"].");
736 }
737 },
738 ticks: 3
739 });
740 F.confirmer(P.e.btnSave, {
741 confirmText: "Really save changes?",
742 onconfirm: function(e){
743 const w = P.winfo;
@@ -745,11 +848,11 @@
745 F.error("No page loaded.");
746 return;
747 }
748 P.save();
749 },
750 ticks: 3
751 });
752
753 P.e.taEditor.addEventListener(
754 'change', ()=>P.stashContentChange(), false
755 );
@@ -796,16 +899,27 @@
796 (e)=>{
797 D.clearElement(P.e.diffTarget, P.e.previewTarget);
798 // TODO: replace preview with new content
799 }
800 );
801 WikiList.init( P.e.tabs.pageList.firstElementChild );
 
 
 
 
 
 
 
 
 
 
 
 
802 P.addEventListener(
803 // Update various state on wiki page load
804 'wiki-page-loaded',
805 function(ev){
806 delete P.winfo;
807 const winfo = ev.detail;
808 P.winfo = winfo;
809 P.previewNeedsUpdate = true;
810 P.e.selectMimetype.value = winfo.mimetype;
811 P.tabs.switchToTab(P.e.tabs.content);
@@ -816,12 +930,13 @@
816 }
817 P.updatePageTitle();
818 },
819 false
820 );
821 P.addEventListener('wiki-stash-updated', ()=>P.updateSaveButton())
822 .updatePageTitle().updateSaveButton();
 
823 }/*F.onPageLoad()*/);
824
825 /**
826 Returns true if fossil.page.winfo is set, indicating that a page
827 has been loaded, else it reports an error and returns false.
@@ -845,17 +960,17 @@
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]'),
855 D.a(F.repoUrl('attachlist',{page:wi.name}),"[attachments]"),
856 D.a(F.repoUrl('attachadd',{page:wi.name,from: F.repoUrl('wikiedit',{name: wi.name})}), "[attach]")
857 );
858 }
859 };
860
861 /**
@@ -978,11 +1093,13 @@
978 }else if(1===arguments.length && 'string' !== typeof name){
979 /* Assume winfo-like object */
980 const arg = arguments[0];
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
--- src/fossil.page.wikiedit.js
+++ src/fossil.page.wikiedit.js
@@ -27,11 +27,11 @@
27
28 The fossil.page.wikiContent() method gets or sets the current
29 file content for the page.
30
31 - Event 'wiki-saved': is fired when a commit completes,
32 passing on the same info as wiki-page-loaded.
33
34 - Event 'wiki-content-replaced': when the editor's content is
35 replaced, as opposed to it being edited via user
36 interaction. This normally happens via selecting a file to
37 load. The event detail is the fossil.page object, not the current
@@ -63,17 +63,14 @@
63 );
64 */
65 const E = (s)=>document.querySelector(s),
66 D = F.dom,
67 P = F.page;
 
68 P.config = {
69 /* Max number of locally-edited pages to stash, after which we
70 drop the least-recently used. */
71 defaultMaxStashSize: 10
 
 
72 };
73
74 /**
75 $stash is an internal-use-only object for managing "stashed"
76 local edits, to help avoid that users accidentally lose content
@@ -269,11 +266,11 @@
266 }
267 };
268
269 /** Internal helper to get an edit status indicator for the given winfo object. */
270 const getEditMarker = function f(winfo, textOnly){
271 const esm = F.config.editStateMarkers;
272 if(1===winfo){ /* force is-new */
273 return textOnly ? esm.isNew :
274 D.addClass(D.append(D.span(),esm.isNew), 'is-new');
275 }else if(2===winfo){ /* force is-modified */
276 return textOnly ? esm.isModified :
@@ -569,14 +566,120 @@
566 btn.addEventListener('click', ()=>this.loadList(), false);
567 this.loadList();
568 const onSelect = (e)=>P.loadPage(e.target.value);
569 sel.addEventListener('change', onSelect, false);
570 sel.addEventListener('dblclick', onSelect, false);
571 F.page.addEventListener('wiki-stash-updated', ()=>{
572 if(P.winfo) this._refreshStashMarks();
573 else this._rebuildList();
574 });
575 delete this.init;
576 }
577 };
578
579 /**
580 Widget for listing and selecting $stash entries.
581 */
582 P.stashWidget = {
583 e:{/*DOM element(s)*/},
584 init: function(domInsertPoint/*insert widget BEFORE this element*/){
585 const wrapper = D.addClass(
586 D.attr(D.div(),'id','wikiedit-stash-selector'),
587 'input-with-label'
588 );
589 const sel = this.e.select = D.select();
590 const btnClear = this.e.btnClear
591 = D.addClass(D.button("Clear"),'hidden');
592 D.append(wrapper, "Local edits (",
593 D.append(D.code(),
594 F.storage.storageImplName()),
595 "):",
596 sel, btnClear);
597 D.attr(wrapper, "title", [
598 'Locally-edited wiki pages. Timestamps are the last local edit time.',
599 'Only the',P.config.defaultMaxStashSize,'most recent pages',
600 'are retained. Saving or reloading a file removes it from this list.'
601 ].join(' '));
602 D.option(D.disable(sel), "(empty)");
603 P.addEventListener('wiki-stash-updated',(e)=>this.updateList(e.detail));
604 P.addEventListener('wiki-page-loaded',(e)=>this.updateList($stash, e.detail));
605 sel.addEventListener('change',function(e){
606 const opt = this.selectedOptions[0];
607 if(opt && opt._winfo) P.loadPage(opt._winfo);
608 });
609 F.confirmer(btnClear, {
610 confirmText: "REALLY delete ALL local edits?",
611 onconfirm: (e)=>P.clearStash(),
612 ticks: F.config.confirmerButtonTicks
613 });
614 if(F.storage.isTransient()){/*Warn if our storage is particularly transient...*/
615 D.append(wrapper, D.append(
616 D.addClass(D.span(),'warning'),
617 "Warning: persistent storage is not available, "+
618 "so uncomitted edits will not survive a page reload."
619 ));
620 }
621 domInsertPoint.parentNode.insertBefore(wrapper, domInsertPoint);
622 $stash._fireStashEvent(/*read the page-load-time stash*/);
623 delete this.init;
624 },
625 /**
626 Regenerates the edit selection list.
627 */
628 updateList: function f(stasher,theWinfo){
629 console.debug("updateList()",arguments);
630 if(!f.compare){
631 const cmpBase = (l,r)=>l<r ? -1 : (l===r ? 0 : 1);
632 f.compare = (l,r)=>cmpBase(l.name, r.name);
633 f.rxZ = /\.\d+Z$/ /* ms and 'Z' part of date string */;
634 const pad=(x)=>(''+x).length>1 ? x : '0'+x;
635 f.timestring = function ff(d){
636 return [
637 d.getFullYear(),'-',pad(d.getMonth()+1/*sigh*/),'-',pad(d.getDate()),
638 '@',pad(d.getHours()),':',pad(d.getMinutes())
639 ].join('');
640 };
641 }
642 const index = stasher.getIndex(), ilist = [];
643 Object.keys(index).forEach((winfo)=>{
644 ilist.push(index[winfo]);
645 });
646 const self = this;
647 D.clearElement(this.e.select);
648 if(0===ilist.length){
649 D.addClass(this.e.btnClear, 'hidden');
650 D.option(D.disable(this.e.select),"No local edits");
651 return;
652 }
653 D.enable(this.e.select);
654 if(false){
655 /* The problem with this Clear button is that it allows the user
656 to nuke a non-empty newly-added page without the failsafe confirmation
657 we have if they use P.e.btnReload. Not yet sure how best to resolve that,
658 so we'll leave the button hidden for the time being. */
659 D.removeClass(this.e.btnClear, 'hidden');
660 }
661 D.disable(D.option(this.e.select,0,"Select a local edit..."));
662 const currentFinfo = theWinfo || P.winfo || {};
663 ilist.sort(f.compare).forEach(function(winfo,n){
664 const key = stasher.indexKey(winfo),
665 rev = winfo.version || '';
666 const opt = D.option(
667 self.e.select, n+1/*value is (almost) irrelevant*/,
668 [winfo.name,
669 rev ? ' ['+F.hashDigits(rev, 6)+']' : ' [new/local]',
670 ' @ ',
671 f.timestring(new Date(winfo.stashTime))
672 ].join('')
673 );
674 opt._winfo = winfo;
675 if(0===f.compare(currentFinfo, winfo)){
676 D.attr(opt, 'selected', true);
677 }
678 });
679 }
680 }/*P.stashWidget*/;
681
682 /**
683 Keep track of how many in-flight AJAX requests there are so we
684 can disable input elements while any are pending. For
685 simplicity's sake we simply disable ALL OF IT while any AJAX is
@@ -644,17 +747,17 @@
747 misc: E('#wikiedit-tab-misc')
748 //commit: E('#wikiedit-tab-commit')
749 }
750 };
751 P.tabs = new fossil.TabManager(D.clearElement(P.e.tabContainer));
 
752 P.tabs.e.container.insertBefore(
753 /* Move the status bar between the tab buttons and
754 tab panels. Seems to be the best fit in terms of
755 functionality and visibility. */
756 E('#fossil-status-bar'), P.tabs.e.tabs
757 );
758 P.tabs.e.container.insertBefore(P.e.editStatus, P.tabs.e.tabs);
759 P.tabs.addEventListener(
760 /* Set up some before-switch-to tab event tasks... */
761 'before-switch-to', function(ev){
762 const theTab = ev.detail, btnSlot = theTab.querySelector('.save-button-slot');
763 if(btnSlot){
@@ -733,11 +836,11 @@
836 delete P.winfo;
837 P.updatePageTitle();
838 F.message("Discarded new page ["+w.name+"].");
839 }
840 },
841 ticks: F.config.confirmerButtonTicks
842 });
843 F.confirmer(P.e.btnSave, {
844 confirmText: "Really save changes?",
845 onconfirm: function(e){
846 const w = P.winfo;
@@ -745,11 +848,11 @@
848 F.error("No page loaded.");
849 return;
850 }
851 P.save();
852 },
853 ticks: F.config.confirmerButtonTicks
854 });
855
856 P.e.taEditor.addEventListener(
857 'change', ()=>P.stashContentChange(), false
858 );
@@ -796,16 +899,27 @@
899 (e)=>{
900 D.clearElement(P.e.diffTarget, P.e.previewTarget);
901 // TODO: replace preview with new content
902 }
903 );
904 P.addEventListener('wiki-stash-updated',function(){
905 /* MUST come before WikiList.init() and P.stashWidget.init() so
906 that interwoven event handlers get called in the right
907 order. */
908 if(P.winfo && !P.winfo.version && !$stash.getWinfo(P.winfo)){
909 // New local page was removed.
910 delete P.winfo;
911 P.wikiContent('');
912 P.updatePageTitle();
913 }
914 P.updateSaveButton();
915 }).updatePageTitle().updateSaveButton();
916
917 P.addEventListener(
918 // Update various state on wiki page load
919 'wiki-page-loaded',
920 function(ev){
 
921 const winfo = ev.detail;
922 P.winfo = winfo;
923 P.previewNeedsUpdate = true;
924 P.e.selectMimetype.value = winfo.mimetype;
925 P.tabs.switchToTab(P.e.tabs.content);
@@ -816,12 +930,13 @@
930 }
931 P.updatePageTitle();
932 },
933 false
934 );
935 /* These init()s need to come after P's event handlers are registered */
936 WikiList.init( P.e.tabs.pageList.firstElementChild );
937 P.stashWidget.init(P.e.tabs.content.lastElementChild);
938 }/*F.onPageLoad()*/);
939
940 /**
941 Returns true if fossil.page.winfo is set, indicating that a page
942 has been loaded, else it reports an error and returns false.
@@ -845,17 +960,17 @@
960 if(!wi){
961 D.append(f.eName, '(no page loaded)');
962 return;
963 }
964 var marker = getEditMarker(wi, false);
965 D.append(f.eName,marker,wi.name);
966 if(wi.version){
967 D.append(
968 f.eLinks,
969 D.a(F.repoUrl('whistory',{name:wi.name}),'history'),
970 D.a(F.repoUrl('attachlist',{page:wi.name}),"attachments"),
971 D.a(F.repoUrl('attachadd',{page:wi.name,from: F.repoUrl('wikiedit',{name: wi.name})}), "attach")
972 );
973 }
974 };
975
976 /**
@@ -978,11 +1093,13 @@
1093 }else if(1===arguments.length && 'string' !== typeof name){
1094 /* Assume winfo-like object */
1095 const arg = arguments[0];
1096 name = arg.name;
1097 }
1098 const onload = (r)=>{
1099 this.dispatchEvent('wiki-page-loaded', r);
1100 };
1101 const stashWinfo = this.getStashedWinfo({name: name});
1102 if(stashWinfo){ // fake a response from the stash...
1103 F.message("Fetched from the local-edit storage:", stashWinfo.name);
1104 onload({
1105 name: stashWinfo.name,
1106
+18
--- src/glob.c
+++ src/glob.c
@@ -163,10 +163,28 @@
163163
if( pGlob ){
164164
fossil_free(pGlob->azPattern);
165165
fossil_free(pGlob);
166166
}
167167
}
168
+
169
+/*
170
+** Appends the given glob to the given buffer in the form of a
171
+** JS/JSON-compatible array. It requires that pDest have been
172
+** initialized. If pGlob is NULL or empty it emits [] (an empty
173
+** array).
174
+*/
175
+void glob_render_as_json(Glob *pGlob, Blob *pDest){
176
+ int i = 0;
177
+ blob_append(pDest, "[", 1);
178
+ for( ; pGlob && i < pGlob->nPattern; ++i ){
179
+ if(i){
180
+ blob_append(pDest, ",", 1);
181
+ }
182
+ blob_appendf(pDest, "%!j", pGlob->azPattern[i]);
183
+ }
184
+ blob_append(pDest, "]", 1);
185
+}
168186
169187
/*
170188
** COMMAND: test-glob
171189
**
172190
** Usage: %fossil test-glob PATTERN STRING...
173191
--- src/glob.c
+++ src/glob.c
@@ -163,10 +163,28 @@
163 if( pGlob ){
164 fossil_free(pGlob->azPattern);
165 fossil_free(pGlob);
166 }
167 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
169 /*
170 ** COMMAND: test-glob
171 **
172 ** Usage: %fossil test-glob PATTERN STRING...
173
--- src/glob.c
+++ src/glob.c
@@ -163,10 +163,28 @@
163 if( pGlob ){
164 fossil_free(pGlob->azPattern);
165 fossil_free(pGlob);
166 }
167 }
168
169 /*
170 ** Appends the given glob to the given buffer in the form of a
171 ** JS/JSON-compatible array. It requires that pDest have been
172 ** initialized. If pGlob is NULL or empty it emits [] (an empty
173 ** array).
174 */
175 void glob_render_as_json(Glob *pGlob, Blob *pDest){
176 int i = 0;
177 blob_append(pDest, "[", 1);
178 for( ; pGlob && i < pGlob->nPattern; ++i ){
179 if(i){
180 blob_append(pDest, ",", 1);
181 }
182 blob_appendf(pDest, "%!j", pGlob->azPattern[i]);
183 }
184 blob_append(pDest, "]", 1);
185 }
186
187 /*
188 ** COMMAND: test-glob
189 **
190 ** Usage: %fossil test-glob PATTERN STRING...
191
+10 -3
--- src/style.c
+++ src/style.c
@@ -1448,13 +1448,20 @@
14481448
/* fossil.rootPath is the top-most CGI/server path,
14491449
** including a trailing slash. */
14501450
"window.fossil.rootPath = %!j+'/';\n",
14511451
get_version(), g.zTop);
14521452
/* fossil.config = {...various config-level options...} */
1453
- CX("window.fossil.config = {"
1454
- "hashDigits: %d, hashDigitsUrl: %d"
1455
- "};\n", hash_digits(0), hash_digits(1));
1453
+ CX("window.fossil.config = {");
1454
+ CX("/* Length of UUID hashes for display purposes. */");
1455
+ CX("hashDigits: %d, hashDigitsUrl: %d,\n",
1456
+ hash_digits(0), hash_digits(1));
1457
+ CX("editStateMarkers: {"
1458
+ "/*Symbolic markers to denote certain edit states.*/"
1459
+ "isNew:'[+]', isModified:'[*]'},\n");
1460
+ CX("confirmerButtonTicks: 3 "
1461
+ "/*default fossil.confirmer tick count.*/\n");
1462
+ CX("};\n"/* fossil.config */);
14561463
#if 0
14571464
/* Is it safe to emit the CSRF token here? Some pages add it
14581465
** as a hidden form field. */
14591466
if(g.zCsrfToken[0]!=0){
14601467
CX("window.fossil.csrfToken = %!j;\n",
14611468
--- src/style.c
+++ src/style.c
@@ -1448,13 +1448,20 @@
1448 /* fossil.rootPath is the top-most CGI/server path,
1449 ** including a trailing slash. */
1450 "window.fossil.rootPath = %!j+'/';\n",
1451 get_version(), g.zTop);
1452 /* fossil.config = {...various config-level options...} */
1453 CX("window.fossil.config = {"
1454 "hashDigits: %d, hashDigitsUrl: %d"
1455 "};\n", hash_digits(0), hash_digits(1));
 
 
 
 
 
 
 
1456 #if 0
1457 /* Is it safe to emit the CSRF token here? Some pages add it
1458 ** as a hidden form field. */
1459 if(g.zCsrfToken[0]!=0){
1460 CX("window.fossil.csrfToken = %!j;\n",
1461
--- src/style.c
+++ src/style.c
@@ -1448,13 +1448,20 @@
1448 /* fossil.rootPath is the top-most CGI/server path,
1449 ** including a trailing slash. */
1450 "window.fossil.rootPath = %!j+'/';\n",
1451 get_version(), g.zTop);
1452 /* fossil.config = {...various config-level options...} */
1453 CX("window.fossil.config = {");
1454 CX("/* Length of UUID hashes for display purposes. */");
1455 CX("hashDigits: %d, hashDigitsUrl: %d,\n",
1456 hash_digits(0), hash_digits(1));
1457 CX("editStateMarkers: {"
1458 "/*Symbolic markers to denote certain edit states.*/"
1459 "isNew:'[+]', isModified:'[*]'},\n");
1460 CX("confirmerButtonTicks: 3 "
1461 "/*default fossil.confirmer tick count.*/\n");
1462 CX("};\n"/* fossil.config */);
1463 #if 0
1464 /* Is it safe to emit the CSRF token here? Some pages add it
1465 ** as a hidden form field. */
1466 if(g.zCsrfToken[0]!=0){
1467 CX("window.fossil.csrfToken = %!j;\n",
1468
--- src/style.fileedit.css
+++ src/style.fileedit.css
@@ -126,10 +126,13 @@
126126
}
127127
body.fileedit #fileedit-file-selector select {
128128
margin: 0 0 0.5em 0;
129129
height: initial;
130130
font-family: monospace;
131
+}
132
+body.fileedit #fileedit-file-selector select option {
133
+ margin: 0 0 0.5em 0.55em;
131134
}
132135
body.fileedit select:focus {
133136
border: none;
134137
}
135138
body.fileedit option:focus {
@@ -183,5 +186,36 @@
183186
width: initial;
184187
}
185188
body.fileedit .sbsdiffcols div.difftxtcol pre {
186189
max-width: 44em;
187190
}
191
+
192
+body.fileedit #fileedit-edit-status {
193
+ border-radius: 0.25em 0.25em 0 0;
194
+ margin: 0;
195
+ padding: 0;
196
+ width: 100%;
197
+ cursor: initial;
198
+ display: flex;
199
+ flex-direction: row;
200
+ flex-wrap: wrap;
201
+ justify-content: space-between;
202
+ font-family: monospace;
203
+ font-size: 1.2em;
204
+}
205
+body.fileedit #fileedit-edit-status > span {
206
+ display: block;
207
+}
208
+body.fileedit #fileedit-file-selector span.is-new,
209
+body.fileedit #fileedit-file-selector span.is-modified {
210
+ font-family: monospace;
211
+}
212
+body.fileedit #fileedit-edit-status span.links > * {
213
+ margin: 0 0.25em;
214
+ white-space: nowrap;
215
+}
216
+body.fileedit #fileedit-edit-status span.links > *::before {
217
+ content: "[";
218
+}
219
+body.fileedit #fileedit-edit-status span.links > *::after {
220
+ content: "]";
221
+}
188222
--- src/style.fileedit.css
+++ src/style.fileedit.css
@@ -126,10 +126,13 @@
126 }
127 body.fileedit #fileedit-file-selector select {
128 margin: 0 0 0.5em 0;
129 height: initial;
130 font-family: monospace;
 
 
 
131 }
132 body.fileedit select:focus {
133 border: none;
134 }
135 body.fileedit option:focus {
@@ -183,5 +186,36 @@
183 width: initial;
184 }
185 body.fileedit .sbsdiffcols div.difftxtcol pre {
186 max-width: 44em;
187 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
--- src/style.fileedit.css
+++ src/style.fileedit.css
@@ -126,10 +126,13 @@
126 }
127 body.fileedit #fileedit-file-selector select {
128 margin: 0 0 0.5em 0;
129 height: initial;
130 font-family: monospace;
131 }
132 body.fileedit #fileedit-file-selector select option {
133 margin: 0 0 0.5em 0.55em;
134 }
135 body.fileedit select:focus {
136 border: none;
137 }
138 body.fileedit option:focus {
@@ -183,5 +186,36 @@
186 width: initial;
187 }
188 body.fileedit .sbsdiffcols div.difftxtcol pre {
189 max-width: 44em;
190 }
191
192 body.fileedit #fileedit-edit-status {
193 border-radius: 0.25em 0.25em 0 0;
194 margin: 0;
195 padding: 0;
196 width: 100%;
197 cursor: initial;
198 display: flex;
199 flex-direction: row;
200 flex-wrap: wrap;
201 justify-content: space-between;
202 font-family: monospace;
203 font-size: 1.2em;
204 }
205 body.fileedit #fileedit-edit-status > span {
206 display: block;
207 }
208 body.fileedit #fileedit-file-selector span.is-new,
209 body.fileedit #fileedit-file-selector span.is-modified {
210 font-family: monospace;
211 }
212 body.fileedit #fileedit-edit-status span.links > * {
213 margin: 0 0.25em;
214 white-space: nowrap;
215 }
216 body.fileedit #fileedit-edit-status span.links > *::before {
217 content: "[";
218 }
219 body.fileedit #fileedit-edit-status span.links > *::after {
220 content: "]";
221 }
222
--- src/style.wikiedit.css
+++ src/style.wikiedit.css
@@ -138,8 +138,29 @@
138138
}
139139
body.wikiedit .WikiList span.is-new,
140140
body.wikiedit .WikiList span.is-modified {
141141
font-family: monospace;
142142
}
143
-body.wikiedit #wikiedit-edit-status > span.links > a {
143
+body.wikiedit #wikiedit-edit-status span.links > a {
144144
margin: 0 0.25em;
145
+ white-space: nowrap;
146
+}
147
+body.wikiedit #wikiedit-edit-status span.links > a::before {
148
+ content: "[";
149
+}
150
+body.wikiedit #wikiedit-edit-status span.links > a::after {
151
+ content: "]";
152
+}
153
+body.wikiedit #wikiedit-stash-selector {
154
+ margin: 0.25em;
155
+ display: flex;
156
+ flex-direction: row;
157
+ flex-wrap: wrap;
158
+ align-items: baseline;
159
+}
160
+body.wikiedit #wikiedit-stash-selector select {
161
+ margin: 0 1em;
162
+ height: initial;
163
+ font-family: monospace;
164
+ flex: 10 1 auto;
145165
}
166
+
146167
--- src/style.wikiedit.css
+++ src/style.wikiedit.css
@@ -138,8 +138,29 @@
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
--- src/style.wikiedit.css
+++ src/style.wikiedit.css
@@ -138,8 +138,29 @@
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 white-space: nowrap;
146 }
147 body.wikiedit #wikiedit-edit-status span.links > a::before {
148 content: "[";
149 }
150 body.wikiedit #wikiedit-edit-status span.links > a::after {
151 content: "]";
152 }
153 body.wikiedit #wikiedit-stash-selector {
154 margin: 0.25em;
155 display: flex;
156 flex-direction: row;
157 flex-wrap: wrap;
158 align-items: baseline;
159 }
160 body.wikiedit #wikiedit-stash-selector select {
161 margin: 0 1em;
162 height: initial;
163 font-family: monospace;
164 flex: 10 1 auto;
165 }
166
167

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button