Fossil SCM

Get replying basically working. Attachments to responses are being saved but are not showing up in the attachment list later, which is weird. Just now noticing that forum threads can be rendered under /info, in which case the Edit/Reply buttons do not get hijacked for the new editor because this JS isn't loaded in that page. Whether that's a feature or bug is TBD.

stephan 2026-06-08 11:58 UTC forum-editor-2026
Commit 6269e5d70004f6f9b090757d41fd10973b1886631c2f577d4d2f3305cb6bc579
--- src/fossil.page.forumpost.js
+++ src/fossil.page.forumpost.js
@@ -77,14 +77,15 @@
7777
}
7878
const e = this.#e = F.nu({
7979
mimetype: F.nu(),
8080
button: F.nu()
8181
});
82
+ console.debug("Setting up FPE opt =",opt);
8283
const wrapper = e.widget = D.addClass(D.div(), 'ForumPostEditor');
8384
D.clearElement(wrapper);
8485
85
- if( !opt.inReplyTo && !opt.hideTitle ){
86
+ if( !opt.inReplyTo ){
8687
/* Title... */
8788
e.titleBar = D.addClass(D.div(),'titlebar');
8889
e.title = D.attr(
8990
D.addClass(D.input('text'), 'title'),
9091
'placeholder',
@@ -279,11 +280,11 @@
279280
this.#draft.status = v;
280281
this.#storeDraft();
281282
}
282283
});
283284
}
284
- }
285
+ }/*e.status*/
285286
286287
if( F.user.mayAttachForum ){
287288
//e.buttons.append( e.button.addAttach = this.#att.takeAddButton() );
288289
e.tabAttach = D.div();
289290
e.tabAttach.setAttribute('id', idPrefix+'-attach');
@@ -426,10 +427,11 @@
426427
collecting, e.g., the CSRF token and an initial page title.
427428
*/
428429
addHiddenFields(list){
429430
this.#extraFields ??= [];
430431
for( const f of list ){
432
+ if( !f ) continue;
431433
if( 'title'===f.name && this.#e.title ){
432434
if( f.value && this.#opt.isNewThread && !this.#e.title.value ){
433435
this.#e.title.value = f.value;
434436
}
435437
}else{
@@ -604,11 +606,11 @@
604606
}
605607
if( e.debug ){
606608
e.debug.querySelectorAll('input[type=checkbox]').forEach(cb=>{
607609
if( cb.checked ){
608610
fd.append(cb.value, 1);
609
- console.debug("Debug option:",cb);
611
+ //console.debug("Forum post debug option:",cb);
610612
}
611613
});
612614
}
613615
if( this.#att ){
614616
this.#att.populateFormData(fd);
@@ -616,16 +618,10 @@
616618
console.warn("Ready to submit",fd);
617619
if( 0 ){
618620
this.#isWaiting = false;
619621
return;
620622
}
621
- /*
622
- TODO: save it, set #isWaiting=false, then handle error or
623
- redirect to the post (if this is a new post) or, if replying
624
- inline, replace this object with a static rendering from the
625
- response.
626
- */
627623
const resp = window.fetch(F.repoUrl('forumajax_save'), {
628624
method: 'POST',
629625
body: fd
630626
}).then(r=>r.json())
631627
.then(j=>{
@@ -939,55 +935,45 @@
939935
D.enable(eToDisable);
940936
};
941937
942938
const replyClicked = (form, ePost, eBtnReply, eToDisable)=>{
943939
const fpid = setupEditReplyElement(ePost, eBtnReply, eToDisable);
944
- const firt = ePost.dataset.firt;
945940
const fEditHead = ePost.dataset.fedithead;
946941
eBtnReply.innerText = "Replying...";
947
- F.toast.error("Reply is TODO. fpid="+fpid);
948
- /*
949
- TODOs include:
950
-
951
- - Hide replyButton
952
-
953
- - Shift ePost to the left edge.
954
-
955
- - Fetch /ajax/artifact.json?uuid=fpid
956
-
957
- - Pop up a ForumPostEditor immediately under ePost. It needs
958
- a Cancel button.
959
-
960
- - When cancelled or submitted, restore the reply button and
961
- ePost position.
962
- */
963
- fetchPost(fpid)
964
- .then(artifact=>{
965
- const ondone = (fpe)=>{
966
- restoreEditReplyElement(ePost, eBtnReply, eToDisable);
967
- //console.debug("ondiscard/onsubmit", fpe, eToDisable);
968
- if( fpe/*onsubmit*/ ){
969
- if( fpe.widget.parentNode ){
970
- fpe.widget.remove();
971
- }
972
- }
973
- };
974
- const fpe = new F.ForumPostEditor({
975
- hiddenFields: form.querySelectorAll('input[type=hidden]'),
976
- ondiscard: ondone,
977
- onsubmit: ondone,
978
- draftKey: 'draft-forumedit-'+(fEditHead || fpid).substr(0,12),
979
- hideTitle: true/*fixme: only show if this is the root post*/,
980
- edit: artifact,
981
- inReplyTo: firt
982
- });
983
- const w = fpe.widget;
984
- w.style.borderTop = '2px dotted';
985
- /* Adding an "Editing..." <h3> here adds way too much space */
986
- ePost.append(w);
987
- w.scrollIntoView();
988
- });
942
+ const ondone = (fpe)=>{
943
+ restoreEditReplyElement(ePost, eBtnReply, eToDisable);
944
+ //console.debug("ondiscard/onsubmit", fpe, eToDisable);
945
+ if( fpe/*onsubmit*/ ){
946
+ if( fpe.widget.parentNode ){
947
+ fpe.widget.remove();
948
+ }
949
+ }
950
+ };
951
+ const fpe = new F.ForumPostEditor({
952
+ hiddenFields: form.querySelectorAll(
953
+ 'input[type=hidden][name=csrf]'
954
+ /* Do not inherit the fpid field, else this will become
955
+ an edit to that post rather than a response. */
956
+ ),
957
+ ondiscard: ondone,
958
+ onsubmit: ondone,
959
+ inReplyTo: fpid,
960
+ draftKey: 'draft-reply-'+(
961
+ fEditHead
962
+ /* The problem with firt as a key is that firt is not
963
+ necessarily the root edit of that post, which is
964
+ what we really want as a draft key so that the
965
+ draft does not disapper if firt is later edited
966
+ (giving us a new firt value here). */
967
+ || fpid
968
+ ).substr(0,12)
969
+ });
970
+ const w = fpe.widget;
971
+ w.style.borderTop = '2px dotted';
972
+ /* Adding an "Editing..." <h3> here adds way too much space */
973
+ ePost.append(w);
974
+ w.scrollIntoView();
989975
}/*replyClicked()*/;
990976
991977
const editClicked = (form, ePost, eBtnEdit, eToDisable)=>{
992978
const fpid = setupEditReplyElement(ePost, eBtnEdit, eToDisable);
993979
const firt = ePost.dataset.firt;
@@ -1017,13 +1003,15 @@
10171003
status: eStatusSelect?.value,
10181004
inReplyTo: firt
10191005
});
10201006
const w = fpe.widget;
10211007
w.style.borderTop = '2px dotted';
1008
+ //w.style.height = '0px';
10221009
/* Adding an "Editing..." <h3> here adds way too much space */
10231010
ePost.append(w);
10241011
w.scrollIntoView();
1012
+ //w.style.height = '';
10251013
});
10261014
}/*editClicked()*/;
10271015
10281016
document.body.querySelectorAll(
10291017
'.forumpost-single-controls > form'
10301018
--- src/fossil.page.forumpost.js
+++ src/fossil.page.forumpost.js
@@ -77,14 +77,15 @@
77 }
78 const e = this.#e = F.nu({
79 mimetype: F.nu(),
80 button: F.nu()
81 });
 
82 const wrapper = e.widget = D.addClass(D.div(), 'ForumPostEditor');
83 D.clearElement(wrapper);
84
85 if( !opt.inReplyTo && !opt.hideTitle ){
86 /* Title... */
87 e.titleBar = D.addClass(D.div(),'titlebar');
88 e.title = D.attr(
89 D.addClass(D.input('text'), 'title'),
90 'placeholder',
@@ -279,11 +280,11 @@
279 this.#draft.status = v;
280 this.#storeDraft();
281 }
282 });
283 }
284 }
285
286 if( F.user.mayAttachForum ){
287 //e.buttons.append( e.button.addAttach = this.#att.takeAddButton() );
288 e.tabAttach = D.div();
289 e.tabAttach.setAttribute('id', idPrefix+'-attach');
@@ -426,10 +427,11 @@
426 collecting, e.g., the CSRF token and an initial page title.
427 */
428 addHiddenFields(list){
429 this.#extraFields ??= [];
430 for( const f of list ){
 
431 if( 'title'===f.name && this.#e.title ){
432 if( f.value && this.#opt.isNewThread && !this.#e.title.value ){
433 this.#e.title.value = f.value;
434 }
435 }else{
@@ -604,11 +606,11 @@
604 }
605 if( e.debug ){
606 e.debug.querySelectorAll('input[type=checkbox]').forEach(cb=>{
607 if( cb.checked ){
608 fd.append(cb.value, 1);
609 console.debug("Debug option:",cb);
610 }
611 });
612 }
613 if( this.#att ){
614 this.#att.populateFormData(fd);
@@ -616,16 +618,10 @@
616 console.warn("Ready to submit",fd);
617 if( 0 ){
618 this.#isWaiting = false;
619 return;
620 }
621 /*
622 TODO: save it, set #isWaiting=false, then handle error or
623 redirect to the post (if this is a new post) or, if replying
624 inline, replace this object with a static rendering from the
625 response.
626 */
627 const resp = window.fetch(F.repoUrl('forumajax_save'), {
628 method: 'POST',
629 body: fd
630 }).then(r=>r.json())
631 .then(j=>{
@@ -939,55 +935,45 @@
939 D.enable(eToDisable);
940 };
941
942 const replyClicked = (form, ePost, eBtnReply, eToDisable)=>{
943 const fpid = setupEditReplyElement(ePost, eBtnReply, eToDisable);
944 const firt = ePost.dataset.firt;
945 const fEditHead = ePost.dataset.fedithead;
946 eBtnReply.innerText = "Replying...";
947 F.toast.error("Reply is TODO. fpid="+fpid);
948 /*
949 TODOs include:
950
951 - Hide replyButton
952
953 - Shift ePost to the left edge.
954
955 - Fetch /ajax/artifact.json?uuid=fpid
956
957 - Pop up a ForumPostEditor immediately under ePost. It needs
958 a Cancel button.
959
960 - When cancelled or submitted, restore the reply button and
961 ePost position.
962 */
963 fetchPost(fpid)
964 .then(artifact=>{
965 const ondone = (fpe)=>{
966 restoreEditReplyElement(ePost, eBtnReply, eToDisable);
967 //console.debug("ondiscard/onsubmit", fpe, eToDisable);
968 if( fpe/*onsubmit*/ ){
969 if( fpe.widget.parentNode ){
970 fpe.widget.remove();
971 }
972 }
973 };
974 const fpe = new F.ForumPostEditor({
975 hiddenFields: form.querySelectorAll('input[type=hidden]'),
976 ondiscard: ondone,
977 onsubmit: ondone,
978 draftKey: 'draft-forumedit-'+(fEditHead || fpid).substr(0,12),
979 hideTitle: true/*fixme: only show if this is the root post*/,
980 edit: artifact,
981 inReplyTo: firt
982 });
983 const w = fpe.widget;
984 w.style.borderTop = '2px dotted';
985 /* Adding an "Editing..." <h3> here adds way too much space */
986 ePost.append(w);
987 w.scrollIntoView();
988 });
989 }/*replyClicked()*/;
990
991 const editClicked = (form, ePost, eBtnEdit, eToDisable)=>{
992 const fpid = setupEditReplyElement(ePost, eBtnEdit, eToDisable);
993 const firt = ePost.dataset.firt;
@@ -1017,13 +1003,15 @@
1017 status: eStatusSelect?.value,
1018 inReplyTo: firt
1019 });
1020 const w = fpe.widget;
1021 w.style.borderTop = '2px dotted';
 
1022 /* Adding an "Editing..." <h3> here adds way too much space */
1023 ePost.append(w);
1024 w.scrollIntoView();
 
1025 });
1026 }/*editClicked()*/;
1027
1028 document.body.querySelectorAll(
1029 '.forumpost-single-controls > form'
1030
--- src/fossil.page.forumpost.js
+++ src/fossil.page.forumpost.js
@@ -77,14 +77,15 @@
77 }
78 const e = this.#e = F.nu({
79 mimetype: F.nu(),
80 button: F.nu()
81 });
82 console.debug("Setting up FPE opt =",opt);
83 const wrapper = e.widget = D.addClass(D.div(), 'ForumPostEditor');
84 D.clearElement(wrapper);
85
86 if( !opt.inReplyTo ){
87 /* Title... */
88 e.titleBar = D.addClass(D.div(),'titlebar');
89 e.title = D.attr(
90 D.addClass(D.input('text'), 'title'),
91 'placeholder',
@@ -279,11 +280,11 @@
280 this.#draft.status = v;
281 this.#storeDraft();
282 }
283 });
284 }
285 }/*e.status*/
286
287 if( F.user.mayAttachForum ){
288 //e.buttons.append( e.button.addAttach = this.#att.takeAddButton() );
289 e.tabAttach = D.div();
290 e.tabAttach.setAttribute('id', idPrefix+'-attach');
@@ -426,10 +427,11 @@
427 collecting, e.g., the CSRF token and an initial page title.
428 */
429 addHiddenFields(list){
430 this.#extraFields ??= [];
431 for( const f of list ){
432 if( !f ) continue;
433 if( 'title'===f.name && this.#e.title ){
434 if( f.value && this.#opt.isNewThread && !this.#e.title.value ){
435 this.#e.title.value = f.value;
436 }
437 }else{
@@ -604,11 +606,11 @@
606 }
607 if( e.debug ){
608 e.debug.querySelectorAll('input[type=checkbox]').forEach(cb=>{
609 if( cb.checked ){
610 fd.append(cb.value, 1);
611 //console.debug("Forum post debug option:",cb);
612 }
613 });
614 }
615 if( this.#att ){
616 this.#att.populateFormData(fd);
@@ -616,16 +618,10 @@
618 console.warn("Ready to submit",fd);
619 if( 0 ){
620 this.#isWaiting = false;
621 return;
622 }
 
 
 
 
 
 
623 const resp = window.fetch(F.repoUrl('forumajax_save'), {
624 method: 'POST',
625 body: fd
626 }).then(r=>r.json())
627 .then(j=>{
@@ -939,55 +935,45 @@
935 D.enable(eToDisable);
936 };
937
938 const replyClicked = (form, ePost, eBtnReply, eToDisable)=>{
939 const fpid = setupEditReplyElement(ePost, eBtnReply, eToDisable);
 
940 const fEditHead = ePost.dataset.fedithead;
941 eBtnReply.innerText = "Replying...";
942 const ondone = (fpe)=>{
943 restoreEditReplyElement(ePost, eBtnReply, eToDisable);
944 //console.debug("ondiscard/onsubmit", fpe, eToDisable);
945 if( fpe/*onsubmit*/ ){
946 if( fpe.widget.parentNode ){
947 fpe.widget.remove();
948 }
949 }
950 };
951 const fpe = new F.ForumPostEditor({
952 hiddenFields: form.querySelectorAll(
953 'input[type=hidden][name=csrf]'
954 /* Do not inherit the fpid field, else this will become
955 an edit to that post rather than a response. */
956 ),
957 ondiscard: ondone,
958 onsubmit: ondone,
959 inReplyTo: fpid,
960 draftKey: 'draft-reply-'+(
961 fEditHead
962 /* The problem with firt as a key is that firt is not
963 necessarily the root edit of that post, which is
964 what we really want as a draft key so that the
965 draft does not disapper if firt is later edited
966 (giving us a new firt value here). */
967 || fpid
968 ).substr(0,12)
969 });
970 const w = fpe.widget;
971 w.style.borderTop = '2px dotted';
972 /* Adding an "Editing..." <h3> here adds way too much space */
973 ePost.append(w);
974 w.scrollIntoView();
 
 
 
 
 
 
 
 
 
975 }/*replyClicked()*/;
976
977 const editClicked = (form, ePost, eBtnEdit, eToDisable)=>{
978 const fpid = setupEditReplyElement(ePost, eBtnEdit, eToDisable);
979 const firt = ePost.dataset.firt;
@@ -1017,13 +1003,15 @@
1003 status: eStatusSelect?.value,
1004 inReplyTo: firt
1005 });
1006 const w = fpe.widget;
1007 w.style.borderTop = '2px dotted';
1008 //w.style.height = '0px';
1009 /* Adding an "Editing..." <h3> here adds way too much space */
1010 ePost.append(w);
1011 w.scrollIntoView();
1012 //w.style.height = '';
1013 });
1014 }/*editClicked()*/;
1015
1016 document.body.querySelectorAll(
1017 '.forumpost-single-controls > form'
1018
--- src/style.forum.css
+++ src/style.forum.css
@@ -9,10 +9,13 @@
99
.ForumPostEditor {
1010
display: flex;
1111
flex-direction: column;
1212
gap: 1em;
1313
padding: 0.5em;
14
+ /* Anyone know how to make these fade/slide in gracefully?
15
+ transition: display 0.5s ease-in-out;
16
+ transition: height 0.5s ease-in-out; */
1417
}
1518
1619
.ForumPostEditor > .tab-bar{
1720
}
1821
1922
--- src/style.forum.css
+++ src/style.forum.css
@@ -9,10 +9,13 @@
9 .ForumPostEditor {
10 display: flex;
11 flex-direction: column;
12 gap: 1em;
13 padding: 0.5em;
 
 
 
14 }
15
16 .ForumPostEditor > .tab-bar{
17 }
18
19
--- src/style.forum.css
+++ src/style.forum.css
@@ -9,10 +9,13 @@
9 .ForumPostEditor {
10 display: flex;
11 flex-direction: column;
12 gap: 1em;
13 padding: 0.5em;
14 /* Anyone know how to make these fade/slide in gracefully?
15 transition: display 0.5s ease-in-out;
16 transition: height 0.5s ease-in-out; */
17 }
18
19 .ForumPostEditor > .tab-bar{
20 }
21
22

Keyboard Shortcuts

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