Fossil SCM

Add a Discard button to the post editor. Start hooking into Reply/Edit buttons in thread views but it's currently disabled.

stephan 2026-06-07 15:53 UTC forum-editor-2026
Commit 44aafa6117f4c08ddbf1edd1d10f44c53bba6a0b017a9b68742b0983cfd6a7a8
+2 -3
--- src/forum.c
+++ src/forum.c
@@ -1122,14 +1122,13 @@
11221122
@ <div id='forum%d(p->fpid)' class='forumTime\
11231123
@ %s(bSelect ? " forumSel" : "")\
11241124
@ %s(iClosed ? " forumClosed" : "")\
11251125
@ %s(p->pEditTail ? " forumObs" : "")' \
11261126
if( iIndent && iIndentScale ){
1127
- @ style='margin-left:%d(iIndent*iIndentScale)ex;'>
1128
- }else{
1129
- @ >
1127
+ @ style='margin-left:%d(iIndent*iIndentScale)ex;' \
11301128
}
1129
+ @ data-fpid="%s(p->zUuid)">
11311130
11321131
/* If this is the first post (or an edit thereof), emit the thread title. */
11331132
if( pManifest->zThreadTitle ){
11341133
@ <h1>%h(pManifest->zThreadTitle)</h1>
11351134
}
11361135
--- src/forum.c
+++ src/forum.c
@@ -1122,14 +1122,13 @@
1122 @ <div id='forum%d(p->fpid)' class='forumTime\
1123 @ %s(bSelect ? " forumSel" : "")\
1124 @ %s(iClosed ? " forumClosed" : "")\
1125 @ %s(p->pEditTail ? " forumObs" : "")' \
1126 if( iIndent && iIndentScale ){
1127 @ style='margin-left:%d(iIndent*iIndentScale)ex;'>
1128 }else{
1129 @ >
1130 }
 
1131
1132 /* If this is the first post (or an edit thereof), emit the thread title. */
1133 if( pManifest->zThreadTitle ){
1134 @ <h1>%h(pManifest->zThreadTitle)</h1>
1135 }
1136
--- src/forum.c
+++ src/forum.c
@@ -1122,14 +1122,13 @@
1122 @ <div id='forum%d(p->fpid)' class='forumTime\
1123 @ %s(bSelect ? " forumSel" : "")\
1124 @ %s(iClosed ? " forumClosed" : "")\
1125 @ %s(p->pEditTail ? " forumObs" : "")' \
1126 if( iIndent && iIndentScale ){
1127 @ style='margin-left:%d(iIndent*iIndentScale)ex;' \
 
 
1128 }
1129 @ data-fpid="%s(p->zUuid)">
1130
1131 /* If this is the first post (or an edit thereof), emit the thread title. */
1132 if( pManifest->zThreadTitle ){
1133 @ <h1>%h(pManifest->zThreadTitle)</h1>
1134 }
1135
--- src/fossil.page.forumpost.js
+++ src/fossil.page.forumpost.js
@@ -43,11 +43,11 @@
4343
*/
4444
constructor(opt){
4545
opt = this.#opt = F.nu({
4646
// todo: defaults once we determine the options
4747
// replyTo: hash
48
- // edit: hash
48
+ // fpid: hash
4949
draftKey: undefined
5050
}, opt);
5151
opt.isNewThread = !opt.replyTo && !opt.edit;
5252
if( opt.draftKey ){
5353
this.#draft = F.nu(F.storage.getJSON(opt.draftKey, {}));
@@ -113,15 +113,24 @@
113113
114114
e.buttons = D.addClass(D.div(), 'buttons');
115115
{ /* Preview/submit buttons... */
116116
e.button.preview = D.button("Preview", e=>this.#preview());
117117
e.button.submit = D.button("Submit");
118
+ if( opt.ondiscard instanceof Function ){
119
+ e.button.discard = D.button('Discard');
120
+ }
118121
if( 1 ){
119122
F.confirmer(e.button.submit, {
120123
confirmText: "Confirm submit...",
121124
onconfirm: ()=>this.#submit()
122125
});
126
+ if( e.button.discard ){
127
+ F.confirmer(e.button.discard, {
128
+ confirmText: "Really discard?",
129
+ onconfirm: ()=>this.discard()
130
+ });
131
+ }
123132
}else{
124133
e.button.submit.addEventListener('click', ()=>this.#submit());
125134
}
126135
e.button.submit.setAttribute('disabled', '');
127136
wrapper.append(e.buttons);
@@ -253,10 +262,14 @@
253262
this.#tabs.addTab(e.tabAttach);
254263
/* Reminder: we don't currently have a way to disable/enable
255264
an Attacher's controls during ajax traffic. */
256265
}
257266
e.buttons.append(e.button.preview, e.button.submit);
267
+ if( e.button.discard ){
268
+ e.buttons.append(e.button.discard);
269
+ this.#toDisable.push(e.button.discard);
270
+ }
258271
this.#toDisable.push(e.button.preview);
259272
260273
e.help = D.attr(D.div(), 'id', idPrefix+'-help');
261274
e.help.$needsInit = true;
262275
e.help.dataset.tabLabel = 'Help';
@@ -331,10 +344,21 @@
331344
});
332345
e.buttons.append(e.button.toggleHeader);
333346
}
334347
335348
}/*constructor*/
349
+
350
+ discard(){
351
+ this.#clearDraft();
352
+ const e = this.#e.widget;
353
+ if( e.parentNode ){
354
+ e.remove();
355
+ if( this.#opt.ondiscard instanceof Function ){
356
+ this.#opt.ondiscard();
357
+ }
358
+ }
359
+ }
336360
337361
/** This widget's top-most DOM element. */
338362
get widget(){
339363
return this.#e.widget;
340364
}
@@ -416,10 +440,20 @@
416440
417441
#initAttacherTab(){
418442
this.#att = new F.Attacher({
419443
reverse: true
420444
});
445
+ if( this.#opt.fpid ){
446
+ const eNote = D.append(
447
+ D.div(),
448
+ "Tip: attachments can be added to posts without editing them",
449
+ "by visiting ",
450
+ D.a(F.repoUrl('attachadd?target='+this.#opt.fpid), '/attachadd'),
451
+ ".",
452
+ );
453
+ this.#e.tabAttach.append(eNote);
454
+ }
421455
this.#e.tabAttach.append(this.#att.widget);
422456
}
423457
424458
#newFormData(addThisContent){
425459
const fd = new FormData;
@@ -773,14 +807,52 @@
773807
if( eForumNew ){
774808
/* /forumnew */
775809
const fpe = new fossil.ForumPostEditor({
776810
draftKey: 'forumnew',
777811
hiddenFields: eForumNew.querySelectorAll('input[type=hidden]'),
778
- captcha: eForumNew.querySelector('.captcha-for-js')
812
+ captcha: eForumNew.querySelector('.captcha-for-js'),
813
+ ondiscard: ()=>{
814
+ window.location = F.repoUrl('forum');
815
+ }
779816
//mimetype: 'text/plain'
780817
});
781818
eForumNew.parentElement.insertBefore(fpe.widget, eForumNew);
782819
eForumNew.remove();
783820
fossil.page.fpe = fpe /* for testing via the console */;
784821
}/*eForumNew*/
822
+ else if( 0 && (document.body.classList.contains('cpage-forumpost')
823
+ || document.body.classList.contains('cpage-forumthread'))){
824
+ /* /forumpost and /forumthread */
825
+ const replyClicked = (replyButton, fpid)=>{
826
+ F.toast.error("Reply is TODO. fpid="+fpid);
827
+ /*
828
+ TODOs include:
829
+
830
+ - Hide replyButton
831
+
832
+ - Pop up a ForumPostEditor. It needs a Cancel button.
833
+
834
+ - When cancelled or submitted, restore the reply button.
835
+ */
836
+ };
837
+ document.body.querySelectorAll(
838
+ '.forumpost-single-controls > form'
839
+ ).forEach(form=>{
840
+ const eReplyTo = form.parentElement.parentElement;
841
+ const fpid = eReplyTo?.dataset?.fpid;
842
+ if( !fpid ){
843
+ console.warn("Unexpected non-fpid", form, eReplyTo);
844
+ return;
845
+ }
846
+ const rb = form.querySelector('input[type=submit][name=reply]');
847
+ if( rb ){
848
+ console.debug("hacking Reply button", rb);
849
+ const b = D.button("Reply", ()=>replyClicked(b, fpid));
850
+ b.type = 'button'/*keep container form from submitting*/;
851
+ rb.parentElement.insertBefore(b, rb);
852
+ rb.remove();
853
+ }
854
+ });
855
+ }
856
+
785857
})/*F.onPageLoad callback*/;
786858
})(window.fossil);
787859
--- src/fossil.page.forumpost.js
+++ src/fossil.page.forumpost.js
@@ -43,11 +43,11 @@
43 */
44 constructor(opt){
45 opt = this.#opt = F.nu({
46 // todo: defaults once we determine the options
47 // replyTo: hash
48 // edit: hash
49 draftKey: undefined
50 }, opt);
51 opt.isNewThread = !opt.replyTo && !opt.edit;
52 if( opt.draftKey ){
53 this.#draft = F.nu(F.storage.getJSON(opt.draftKey, {}));
@@ -113,15 +113,24 @@
113
114 e.buttons = D.addClass(D.div(), 'buttons');
115 { /* Preview/submit buttons... */
116 e.button.preview = D.button("Preview", e=>this.#preview());
117 e.button.submit = D.button("Submit");
 
 
 
118 if( 1 ){
119 F.confirmer(e.button.submit, {
120 confirmText: "Confirm submit...",
121 onconfirm: ()=>this.#submit()
122 });
 
 
 
 
 
 
123 }else{
124 e.button.submit.addEventListener('click', ()=>this.#submit());
125 }
126 e.button.submit.setAttribute('disabled', '');
127 wrapper.append(e.buttons);
@@ -253,10 +262,14 @@
253 this.#tabs.addTab(e.tabAttach);
254 /* Reminder: we don't currently have a way to disable/enable
255 an Attacher's controls during ajax traffic. */
256 }
257 e.buttons.append(e.button.preview, e.button.submit);
 
 
 
 
258 this.#toDisable.push(e.button.preview);
259
260 e.help = D.attr(D.div(), 'id', idPrefix+'-help');
261 e.help.$needsInit = true;
262 e.help.dataset.tabLabel = 'Help';
@@ -331,10 +344,21 @@
331 });
332 e.buttons.append(e.button.toggleHeader);
333 }
334
335 }/*constructor*/
 
 
 
 
 
 
 
 
 
 
 
336
337 /** This widget's top-most DOM element. */
338 get widget(){
339 return this.#e.widget;
340 }
@@ -416,10 +440,20 @@
416
417 #initAttacherTab(){
418 this.#att = new F.Attacher({
419 reverse: true
420 });
 
 
 
 
 
 
 
 
 
 
421 this.#e.tabAttach.append(this.#att.widget);
422 }
423
424 #newFormData(addThisContent){
425 const fd = new FormData;
@@ -773,14 +807,52 @@
773 if( eForumNew ){
774 /* /forumnew */
775 const fpe = new fossil.ForumPostEditor({
776 draftKey: 'forumnew',
777 hiddenFields: eForumNew.querySelectorAll('input[type=hidden]'),
778 captcha: eForumNew.querySelector('.captcha-for-js')
 
 
 
779 //mimetype: 'text/plain'
780 });
781 eForumNew.parentElement.insertBefore(fpe.widget, eForumNew);
782 eForumNew.remove();
783 fossil.page.fpe = fpe /* for testing via the console */;
784 }/*eForumNew*/
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
785 })/*F.onPageLoad callback*/;
786 })(window.fossil);
787
--- src/fossil.page.forumpost.js
+++ src/fossil.page.forumpost.js
@@ -43,11 +43,11 @@
43 */
44 constructor(opt){
45 opt = this.#opt = F.nu({
46 // todo: defaults once we determine the options
47 // replyTo: hash
48 // fpid: hash
49 draftKey: undefined
50 }, opt);
51 opt.isNewThread = !opt.replyTo && !opt.edit;
52 if( opt.draftKey ){
53 this.#draft = F.nu(F.storage.getJSON(opt.draftKey, {}));
@@ -113,15 +113,24 @@
113
114 e.buttons = D.addClass(D.div(), 'buttons');
115 { /* Preview/submit buttons... */
116 e.button.preview = D.button("Preview", e=>this.#preview());
117 e.button.submit = D.button("Submit");
118 if( opt.ondiscard instanceof Function ){
119 e.button.discard = D.button('Discard');
120 }
121 if( 1 ){
122 F.confirmer(e.button.submit, {
123 confirmText: "Confirm submit...",
124 onconfirm: ()=>this.#submit()
125 });
126 if( e.button.discard ){
127 F.confirmer(e.button.discard, {
128 confirmText: "Really discard?",
129 onconfirm: ()=>this.discard()
130 });
131 }
132 }else{
133 e.button.submit.addEventListener('click', ()=>this.#submit());
134 }
135 e.button.submit.setAttribute('disabled', '');
136 wrapper.append(e.buttons);
@@ -253,10 +262,14 @@
262 this.#tabs.addTab(e.tabAttach);
263 /* Reminder: we don't currently have a way to disable/enable
264 an Attacher's controls during ajax traffic. */
265 }
266 e.buttons.append(e.button.preview, e.button.submit);
267 if( e.button.discard ){
268 e.buttons.append(e.button.discard);
269 this.#toDisable.push(e.button.discard);
270 }
271 this.#toDisable.push(e.button.preview);
272
273 e.help = D.attr(D.div(), 'id', idPrefix+'-help');
274 e.help.$needsInit = true;
275 e.help.dataset.tabLabel = 'Help';
@@ -331,10 +344,21 @@
344 });
345 e.buttons.append(e.button.toggleHeader);
346 }
347
348 }/*constructor*/
349
350 discard(){
351 this.#clearDraft();
352 const e = this.#e.widget;
353 if( e.parentNode ){
354 e.remove();
355 if( this.#opt.ondiscard instanceof Function ){
356 this.#opt.ondiscard();
357 }
358 }
359 }
360
361 /** This widget's top-most DOM element. */
362 get widget(){
363 return this.#e.widget;
364 }
@@ -416,10 +440,20 @@
440
441 #initAttacherTab(){
442 this.#att = new F.Attacher({
443 reverse: true
444 });
445 if( this.#opt.fpid ){
446 const eNote = D.append(
447 D.div(),
448 "Tip: attachments can be added to posts without editing them",
449 "by visiting ",
450 D.a(F.repoUrl('attachadd?target='+this.#opt.fpid), '/attachadd'),
451 ".",
452 );
453 this.#e.tabAttach.append(eNote);
454 }
455 this.#e.tabAttach.append(this.#att.widget);
456 }
457
458 #newFormData(addThisContent){
459 const fd = new FormData;
@@ -773,14 +807,52 @@
807 if( eForumNew ){
808 /* /forumnew */
809 const fpe = new fossil.ForumPostEditor({
810 draftKey: 'forumnew',
811 hiddenFields: eForumNew.querySelectorAll('input[type=hidden]'),
812 captcha: eForumNew.querySelector('.captcha-for-js'),
813 ondiscard: ()=>{
814 window.location = F.repoUrl('forum');
815 }
816 //mimetype: 'text/plain'
817 });
818 eForumNew.parentElement.insertBefore(fpe.widget, eForumNew);
819 eForumNew.remove();
820 fossil.page.fpe = fpe /* for testing via the console */;
821 }/*eForumNew*/
822 else if( 0 && (document.body.classList.contains('cpage-forumpost')
823 || document.body.classList.contains('cpage-forumthread'))){
824 /* /forumpost and /forumthread */
825 const replyClicked = (replyButton, fpid)=>{
826 F.toast.error("Reply is TODO. fpid="+fpid);
827 /*
828 TODOs include:
829
830 - Hide replyButton
831
832 - Pop up a ForumPostEditor. It needs a Cancel button.
833
834 - When cancelled or submitted, restore the reply button.
835 */
836 };
837 document.body.querySelectorAll(
838 '.forumpost-single-controls > form'
839 ).forEach(form=>{
840 const eReplyTo = form.parentElement.parentElement;
841 const fpid = eReplyTo?.dataset?.fpid;
842 if( !fpid ){
843 console.warn("Unexpected non-fpid", form, eReplyTo);
844 return;
845 }
846 const rb = form.querySelector('input[type=submit][name=reply]');
847 if( rb ){
848 console.debug("hacking Reply button", rb);
849 const b = D.button("Reply", ()=>replyClicked(b, fpid));
850 b.type = 'button'/*keep container form from submitting*/;
851 rb.parentElement.insertBefore(b, rb);
852 rb.remove();
853 }
854 });
855 }
856
857 })/*F.onPageLoad callback*/;
858 })(window.fossil);
859

Keyboard Shortcuts

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