Fossil SCM

Docs and generic non-functional cleanups.

stephan 2026-06-10 20:26 UTC forum-editor-2026
Commit 9af97ebf35c0618ec20b098ba43f57d1fa846d25940dbe9e095e5655bce8c18c
1 file changed +53 -27
--- src/fossil.page.forumpost.js
+++ src/fossil.page.forumpost.js
@@ -1049,19 +1049,31 @@
10491049
10501050
const makeDraftKey = (prefix,uuid)=>{
10511051
return prefix+'-'+uuid.substr(0,12);
10521052
};
10531053
1054
+ /**
1055
+ Perform some init common to both Reply and Edit. ePost = the
1056
+ forum post DOM element. eButton = the Reply or Edit
1057
+ button. eToDisable = an array of DOM elements which must be
1058
+ disabled when the editor is active and re-enabled when it is
1059
+ closed.
1060
+ */
10541061
const setupEditReplyElement = (ePost, eButton, eToDisable)=>{
1055
- const fpid = ePost.dataset.fpid;
1062
+ /* Forum posts are indented in the main forum view to
1063
+ represent their place in the hierarchy. In order to gain
1064
+ some screen space, we shift the post to the left margin and
1065
+ arrange to shift it back when the editor is closed. We also
1066
+ record the original button label so that it can be
1067
+ restored on close. */
10561068
ePost.dataset.originalMarginLeft = ePost.style.marginLeft;
10571069
ePost.style.marginLeft = 'initial';
10581070
eButton.dataset.originalLabel = eButton.innerText;
10591071
D.disable(eToDisable);
1060
- return fpid;
10611072
};
10621073
1074
+ /** Undoes the damage done by setupEditReplyElement(). */
10631075
const restoreEditReplyElement = (ePost, eButton, eToDisable)=>{
10641076
if( ePost.dataset.originalMarginLeft ){
10651077
ePost.style.marginLeft = ePost.dataset.originalMarginLeft;
10661078
delete ePost.dataset.originalMarginLeft;
10671079
}
@@ -1085,29 +1097,35 @@
10851097
D.button("Clear error", ()=>e.remove())
10861098
);
10871099
ePost.append(e);
10881100
};
10891101
1102
+ /**
1103
+ Plug in an editor widget representing a reply to a post.
1104
+ form = a (.forum-post-single-controls > form) element. The
1105
+ final 3 arguments are as documented for
1106
+ setupEditReplyElement().
1107
+ */
10901108
const replyClicked = async (form, ePost, eBtnReply, eToDisable)=>{
10911109
const fpid = ePost.dataset.fpid;
10921110
const fEditHead = ePost.dataset.fedithead;
10931111
const draftKey = makeDraftKey(
10941112
'draft-reply', fEditHead
10951113
/* The problem with firt as a key is that firt is not
1096
- necessarily the root edit of that post, which is
1097
- what we really want as a draft key so that the
1098
- draft does not disapper if firt is later edited
1099
- (giving us a new firt value here). */
1114
+ necessarily the root edit of that post, which is what we
1115
+ really want as a draft key so that the draft does not
1116
+ disappear if firt is later edited (giving us a new firt
1117
+ value here). */
11001118
|| fpid
11011119
);
1102
- const lockName = 'fossil-'+draftKey;
11031120
let releaseLock;
1104
-
11051121
if( window.navigator.locks ){
11061122
releaseLock = await new Promise((resolve)=>{
11071123
window.navigator.locks.request(
1108
- lockName, { ifAvailable: true }, async (lock) => {
1124
+ 'fossil-'+draftKey,
1125
+ {ifAvailable: true},
1126
+ async (lock) => {
11091127
if( !lock ){
11101128
/*lock contention*/
11111129
resolve(null);
11121130
return;
11131131
}
@@ -1139,11 +1157,11 @@
11391157
window.location = F.repoUrl('forumpost/'+response.uuid);
11401158
fpe.close();
11411159
}else{/*ondiscard()*/
11421160
}
11431161
};
1144
- const fpe = new F.ForumPostEditor({
1162
+ const fpe = new F.ForumPostEditor(F.nu({
11451163
hiddenFields: form.querySelectorAll(
11461164
'input[type=hidden][name=csrf]'
11471165
/* Do not inherit the fpid field, else this will become
11481166
an edit to that post rather than a response. */
11491167
),
@@ -1155,33 +1173,41 @@
11551173
releaseLock = null;
11561174
}
11571175
},
11581176
inReplyTo: fpid,
11591177
draftKey
1160
- });
1178
+ }));
11611179
initFPEWidget(ePost, fpe);
11621180
}/*replyClicked()*/;
11631181
1182
+ /**
1183
+ Plug in an editor widget representing an edit to a post.
1184
+ form = a (.forum-post-single-controls > form) element. The
1185
+ final 3 arguments are as documented for
1186
+ setupEditReplyElement().
1187
+ */
11641188
const editClicked = async (form, ePost, eBtnEdit, eToDisable)=>{
11651189
const fpid = ePost.dataset.fpid;
11661190
const firt = ePost.dataset.firt;
11671191
const fEditHead = ePost.dataset.fedithead;
11681192
const draftKey = makeDraftKey('draft-forumedit', fEditHead || fpid);
1169
- const lockName = 'fossil-'+draftKey;
11701193
let releaseLock;
11711194
if( navigator.locks ){
11721195
releaseLock = await new Promise((resolve) => {
1173
- navigator.locks.request(lockName, {ifAvailable: true}, async (lock)=>{
1174
- if( !lock ){
1175
- resolve(null);
1176
- return;
1177
- }
1178
- let release;
1179
- const lockReleased = new Promise(res=>release=res);
1180
- resolve(release);
1181
- await lockReleased;
1182
- });
1196
+ navigator.locks.request(
1197
+ 'fossil-'+draftKey,
1198
+ {ifAvailable: true},
1199
+ async (lock)=>{
1200
+ if( !lock ){
1201
+ resolve(null);
1202
+ return;
1203
+ }
1204
+ let release;
1205
+ const lockReleased = new Promise(res=>release=res);
1206
+ resolve(release);
1207
+ await lockReleased;
1208
+ });
11831209
});
11841210
11851211
if( !releaseLock ){
11861212
reportFPEError(
11871213
ePost,
@@ -1216,11 +1242,11 @@
12161242
};
12171243
const eStatusSelect = ePost.querySelector(
12181244
':scope > fieldset.forum-status-selection select[name=status]'
12191245
);
12201246
1221
- const fpe = new F.ForumPostEditor({
1247
+ const fpe = new F.ForumPostEditor(F.nu({
12221248
hiddenFields: form.querySelectorAll('input[type=hidden]'),
12231249
ondiscard: ondone,
12241250
onsubmit: ondone,
12251251
onclose: ()=>{
12261252
if(releaseLock){
@@ -1230,11 +1256,11 @@
12301256
},
12311257
draftKey,
12321258
edit: artifact,
12331259
status: eStatusSelect?.value,
12341260
inReplyTo: firt
1235
- });
1261
+ }));
12361262
initFPEWidget(ePost, fpe);
12371263
})
12381264
.catch(err=>{
12391265
if( releaseLock ){
12401266
releaseLock();
@@ -1250,11 +1276,12 @@
12501276
'.forumpost-single-controls > form'
12511277
).forEach(form=>{
12521278
/* For each forum post... */
12531279
const eToDisable = [
12541280
/* List of non-editor DOM elements which need to be disabled
1255
- when the editor is active. */
1281
+ while the editor is active and re-enabled when it
1282
+ closes. */
12561283
];
12571284
const eThePost = form.parentElement.parentElement/*main post DOM element*/;
12581285
if( !eThePost?.dataset?.fpid ){
12591286
/* The server injects these. */
12601287
console.warn("Unexpected missing fpid", eThePost);
@@ -1293,13 +1320,12 @@
12931320
btnReply.parentElement.insertBefore(b, btnReply);
12941321
btnReply.remove();
12951322
}
12961323
const btnEdit = form.querySelector('input[type=submit][name=edit]');
12971324
if( btnEdit ){
1298
- //console.debug("hacking Edit button", btnEdit);
12991325
const b = D.button("Edit", ()=>editClicked(form, eThePost, b, eToDisable));
1300
- b.type = 'button';
1326
+ b.type = 'button'/*keep container form from submitting*/;
13011327
eToDisable.push(b);
13021328
checkButtonForDraft('draft-forumedit',b);
13031329
btnEdit.parentElement.insertBefore(b, btnEdit);
13041330
btnEdit.remove();
13051331
}
13061332
--- src/fossil.page.forumpost.js
+++ src/fossil.page.forumpost.js
@@ -1049,19 +1049,31 @@
1049
1050 const makeDraftKey = (prefix,uuid)=>{
1051 return prefix+'-'+uuid.substr(0,12);
1052 };
1053
 
 
 
 
 
 
 
1054 const setupEditReplyElement = (ePost, eButton, eToDisable)=>{
1055 const fpid = ePost.dataset.fpid;
 
 
 
 
 
1056 ePost.dataset.originalMarginLeft = ePost.style.marginLeft;
1057 ePost.style.marginLeft = 'initial';
1058 eButton.dataset.originalLabel = eButton.innerText;
1059 D.disable(eToDisable);
1060 return fpid;
1061 };
1062
 
1063 const restoreEditReplyElement = (ePost, eButton, eToDisable)=>{
1064 if( ePost.dataset.originalMarginLeft ){
1065 ePost.style.marginLeft = ePost.dataset.originalMarginLeft;
1066 delete ePost.dataset.originalMarginLeft;
1067 }
@@ -1085,29 +1097,35 @@
1085 D.button("Clear error", ()=>e.remove())
1086 );
1087 ePost.append(e);
1088 };
1089
 
 
 
 
 
 
1090 const replyClicked = async (form, ePost, eBtnReply, eToDisable)=>{
1091 const fpid = ePost.dataset.fpid;
1092 const fEditHead = ePost.dataset.fedithead;
1093 const draftKey = makeDraftKey(
1094 'draft-reply', fEditHead
1095 /* The problem with firt as a key is that firt is not
1096 necessarily the root edit of that post, which is
1097 what we really want as a draft key so that the
1098 draft does not disapper if firt is later edited
1099 (giving us a new firt value here). */
1100 || fpid
1101 );
1102 const lockName = 'fossil-'+draftKey;
1103 let releaseLock;
1104
1105 if( window.navigator.locks ){
1106 releaseLock = await new Promise((resolve)=>{
1107 window.navigator.locks.request(
1108 lockName, { ifAvailable: true }, async (lock) => {
 
 
1109 if( !lock ){
1110 /*lock contention*/
1111 resolve(null);
1112 return;
1113 }
@@ -1139,11 +1157,11 @@
1139 window.location = F.repoUrl('forumpost/'+response.uuid);
1140 fpe.close();
1141 }else{/*ondiscard()*/
1142 }
1143 };
1144 const fpe = new F.ForumPostEditor({
1145 hiddenFields: form.querySelectorAll(
1146 'input[type=hidden][name=csrf]'
1147 /* Do not inherit the fpid field, else this will become
1148 an edit to that post rather than a response. */
1149 ),
@@ -1155,33 +1173,41 @@
1155 releaseLock = null;
1156 }
1157 },
1158 inReplyTo: fpid,
1159 draftKey
1160 });
1161 initFPEWidget(ePost, fpe);
1162 }/*replyClicked()*/;
1163
 
 
 
 
 
 
1164 const editClicked = async (form, ePost, eBtnEdit, eToDisable)=>{
1165 const fpid = ePost.dataset.fpid;
1166 const firt = ePost.dataset.firt;
1167 const fEditHead = ePost.dataset.fedithead;
1168 const draftKey = makeDraftKey('draft-forumedit', fEditHead || fpid);
1169 const lockName = 'fossil-'+draftKey;
1170 let releaseLock;
1171 if( navigator.locks ){
1172 releaseLock = await new Promise((resolve) => {
1173 navigator.locks.request(lockName, {ifAvailable: true}, async (lock)=>{
1174 if( !lock ){
1175 resolve(null);
1176 return;
1177 }
1178 let release;
1179 const lockReleased = new Promise(res=>release=res);
1180 resolve(release);
1181 await lockReleased;
1182 });
 
 
 
1183 });
1184
1185 if( !releaseLock ){
1186 reportFPEError(
1187 ePost,
@@ -1216,11 +1242,11 @@
1216 };
1217 const eStatusSelect = ePost.querySelector(
1218 ':scope > fieldset.forum-status-selection select[name=status]'
1219 );
1220
1221 const fpe = new F.ForumPostEditor({
1222 hiddenFields: form.querySelectorAll('input[type=hidden]'),
1223 ondiscard: ondone,
1224 onsubmit: ondone,
1225 onclose: ()=>{
1226 if(releaseLock){
@@ -1230,11 +1256,11 @@
1230 },
1231 draftKey,
1232 edit: artifact,
1233 status: eStatusSelect?.value,
1234 inReplyTo: firt
1235 });
1236 initFPEWidget(ePost, fpe);
1237 })
1238 .catch(err=>{
1239 if( releaseLock ){
1240 releaseLock();
@@ -1250,11 +1276,12 @@
1250 '.forumpost-single-controls > form'
1251 ).forEach(form=>{
1252 /* For each forum post... */
1253 const eToDisable = [
1254 /* List of non-editor DOM elements which need to be disabled
1255 when the editor is active. */
 
1256 ];
1257 const eThePost = form.parentElement.parentElement/*main post DOM element*/;
1258 if( !eThePost?.dataset?.fpid ){
1259 /* The server injects these. */
1260 console.warn("Unexpected missing fpid", eThePost);
@@ -1293,13 +1320,12 @@
1293 btnReply.parentElement.insertBefore(b, btnReply);
1294 btnReply.remove();
1295 }
1296 const btnEdit = form.querySelector('input[type=submit][name=edit]');
1297 if( btnEdit ){
1298 //console.debug("hacking Edit button", btnEdit);
1299 const b = D.button("Edit", ()=>editClicked(form, eThePost, b, eToDisable));
1300 b.type = 'button';
1301 eToDisable.push(b);
1302 checkButtonForDraft('draft-forumedit',b);
1303 btnEdit.parentElement.insertBefore(b, btnEdit);
1304 btnEdit.remove();
1305 }
1306
--- src/fossil.page.forumpost.js
+++ src/fossil.page.forumpost.js
@@ -1049,19 +1049,31 @@
1049
1050 const makeDraftKey = (prefix,uuid)=>{
1051 return prefix+'-'+uuid.substr(0,12);
1052 };
1053
1054 /**
1055 Perform some init common to both Reply and Edit. ePost = the
1056 forum post DOM element. eButton = the Reply or Edit
1057 button. eToDisable = an array of DOM elements which must be
1058 disabled when the editor is active and re-enabled when it is
1059 closed.
1060 */
1061 const setupEditReplyElement = (ePost, eButton, eToDisable)=>{
1062 /* Forum posts are indented in the main forum view to
1063 represent their place in the hierarchy. In order to gain
1064 some screen space, we shift the post to the left margin and
1065 arrange to shift it back when the editor is closed. We also
1066 record the original button label so that it can be
1067 restored on close. */
1068 ePost.dataset.originalMarginLeft = ePost.style.marginLeft;
1069 ePost.style.marginLeft = 'initial';
1070 eButton.dataset.originalLabel = eButton.innerText;
1071 D.disable(eToDisable);
 
1072 };
1073
1074 /** Undoes the damage done by setupEditReplyElement(). */
1075 const restoreEditReplyElement = (ePost, eButton, eToDisable)=>{
1076 if( ePost.dataset.originalMarginLeft ){
1077 ePost.style.marginLeft = ePost.dataset.originalMarginLeft;
1078 delete ePost.dataset.originalMarginLeft;
1079 }
@@ -1085,29 +1097,35 @@
1097 D.button("Clear error", ()=>e.remove())
1098 );
1099 ePost.append(e);
1100 };
1101
1102 /**
1103 Plug in an editor widget representing a reply to a post.
1104 form = a (.forum-post-single-controls > form) element. The
1105 final 3 arguments are as documented for
1106 setupEditReplyElement().
1107 */
1108 const replyClicked = async (form, ePost, eBtnReply, eToDisable)=>{
1109 const fpid = ePost.dataset.fpid;
1110 const fEditHead = ePost.dataset.fedithead;
1111 const draftKey = makeDraftKey(
1112 'draft-reply', fEditHead
1113 /* The problem with firt as a key is that firt is not
1114 necessarily the root edit of that post, which is what we
1115 really want as a draft key so that the draft does not
1116 disappear if firt is later edited (giving us a new firt
1117 value here). */
1118 || fpid
1119 );
 
1120 let releaseLock;
 
1121 if( window.navigator.locks ){
1122 releaseLock = await new Promise((resolve)=>{
1123 window.navigator.locks.request(
1124 'fossil-'+draftKey,
1125 {ifAvailable: true},
1126 async (lock) => {
1127 if( !lock ){
1128 /*lock contention*/
1129 resolve(null);
1130 return;
1131 }
@@ -1139,11 +1157,11 @@
1157 window.location = F.repoUrl('forumpost/'+response.uuid);
1158 fpe.close();
1159 }else{/*ondiscard()*/
1160 }
1161 };
1162 const fpe = new F.ForumPostEditor(F.nu({
1163 hiddenFields: form.querySelectorAll(
1164 'input[type=hidden][name=csrf]'
1165 /* Do not inherit the fpid field, else this will become
1166 an edit to that post rather than a response. */
1167 ),
@@ -1155,33 +1173,41 @@
1173 releaseLock = null;
1174 }
1175 },
1176 inReplyTo: fpid,
1177 draftKey
1178 }));
1179 initFPEWidget(ePost, fpe);
1180 }/*replyClicked()*/;
1181
1182 /**
1183 Plug in an editor widget representing an edit to a post.
1184 form = a (.forum-post-single-controls > form) element. The
1185 final 3 arguments are as documented for
1186 setupEditReplyElement().
1187 */
1188 const editClicked = async (form, ePost, eBtnEdit, eToDisable)=>{
1189 const fpid = ePost.dataset.fpid;
1190 const firt = ePost.dataset.firt;
1191 const fEditHead = ePost.dataset.fedithead;
1192 const draftKey = makeDraftKey('draft-forumedit', fEditHead || fpid);
 
1193 let releaseLock;
1194 if( navigator.locks ){
1195 releaseLock = await new Promise((resolve) => {
1196 navigator.locks.request(
1197 'fossil-'+draftKey,
1198 {ifAvailable: true},
1199 async (lock)=>{
1200 if( !lock ){
1201 resolve(null);
1202 return;
1203 }
1204 let release;
1205 const lockReleased = new Promise(res=>release=res);
1206 resolve(release);
1207 await lockReleased;
1208 });
1209 });
1210
1211 if( !releaseLock ){
1212 reportFPEError(
1213 ePost,
@@ -1216,11 +1242,11 @@
1242 };
1243 const eStatusSelect = ePost.querySelector(
1244 ':scope > fieldset.forum-status-selection select[name=status]'
1245 );
1246
1247 const fpe = new F.ForumPostEditor(F.nu({
1248 hiddenFields: form.querySelectorAll('input[type=hidden]'),
1249 ondiscard: ondone,
1250 onsubmit: ondone,
1251 onclose: ()=>{
1252 if(releaseLock){
@@ -1230,11 +1256,11 @@
1256 },
1257 draftKey,
1258 edit: artifact,
1259 status: eStatusSelect?.value,
1260 inReplyTo: firt
1261 }));
1262 initFPEWidget(ePost, fpe);
1263 })
1264 .catch(err=>{
1265 if( releaseLock ){
1266 releaseLock();
@@ -1250,11 +1276,12 @@
1276 '.forumpost-single-controls > form'
1277 ).forEach(form=>{
1278 /* For each forum post... */
1279 const eToDisable = [
1280 /* List of non-editor DOM elements which need to be disabled
1281 while the editor is active and re-enabled when it
1282 closes. */
1283 ];
1284 const eThePost = form.parentElement.parentElement/*main post DOM element*/;
1285 if( !eThePost?.dataset?.fpid ){
1286 /* The server injects these. */
1287 console.warn("Unexpected missing fpid", eThePost);
@@ -1293,13 +1320,12 @@
1320 btnReply.parentElement.insertBefore(b, btnReply);
1321 btnReply.remove();
1322 }
1323 const btnEdit = form.querySelector('input[type=submit][name=edit]');
1324 if( btnEdit ){
 
1325 const b = D.button("Edit", ()=>editClicked(form, eThePost, b, eToDisable));
1326 b.type = 'button'/*keep container form from submitting*/;
1327 eToDisable.push(b);
1328 checkButtonForDraft('draft-forumedit',b);
1329 btnEdit.parentElement.insertBefore(b, btnEdit);
1330 btnEdit.remove();
1331 }
1332

Keyboard Shortcuts

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