Fossil SCM

Correct handling of the passing-on of debug-related flags when saving a forum post with the new editor. It now honors the no-notifications, require-moderation, and dry-run flags and unqueues notifications for the associated attachments if needed.

stephan 2026-06-07 14:56 UTC forum-editor-2026
Commit 4bf65a5a9038f175e8839c60fb5c73c4142137be70ead3a23465f14d5e95626b
+32 -26
--- src/forum.c
+++ src/forum.c
@@ -2953,11 +2953,11 @@
29532953
**
29542954
** Response JSON:
29552955
**
29562956
** { uuid: hash, ...tbd }
29572957
*/
2958
-void forum_ajax_save(void){
2958
+void forum_ajax_save_page(void){
29592959
const char *zFpid;
29602960
const char *zTitle;
29612961
const char *zIrt;
29622962
const char *zMimetype;
29632963
const char *zContent;
@@ -2967,12 +2967,12 @@
29672967
int goodCaptcha = 1;
29682968
int iIrt = 0; /* In-reply-to rid or 0 */
29692969
int iEditRid = 0; /* Post rid being edited or 0 */
29702970
int rc = 0;
29712971
int nrid = 0;
2972
- const int iPostFlags = forum_post_flags();
2973
- int bRollback = (FPOST_DRYRUN & iPostFlags); /* True = roll back. */
2972
+ int iPostFlags;
2973
+ int bRollback = 1; /* True = roll back. */
29742974
29752975
if( !ajax_route_bootstrap(0, 1) ){
29762976
return;
29772977
}else if( !g.perm.WrForum
29782978
|| (bHasAttachment && !g.perm.AttachForum) ){
@@ -2984,35 +2984,19 @@
29842984
}else if( 0==(goodCaptcha = captcha_is_correct(0)) ){
29852985
ajax_route_error_captcha();
29862986
return;
29872987
}
29882988
2989
+ iPostFlags = forum_post_flags();
2990
+ bRollback = (FPOST_DRYRUN & iPostFlags);
2991
+ zFpid = P("fpid");
29892992
zTitle = P("title");
29902993
zIrt = P("firt");
29912994
zMimetype = P("mimetype");
29922995
zContent = P("content");
29932996
zStatus = P("status");
29942997
db_begin_transaction();
2995
- /*
2996
- ** TODOs include:
2997
- **
2998
- ** - Permissions and sanity checks, of course.
2999
- **
3000
- ** - Fork forum_post() into an AJAX-friendly form. It currently
3001
- ** assumes HTML output.
3002
- **
3003
- ** - If zFpid then this is an edit. Else...
3004
- **
3005
- ** - If zIrt then this is a new response.
3006
- **
3007
- ** - zTitle is only honored if !zIrt, i.e. zFpid is the root post.
3008
- **
3009
- ** - attachments_ajax_from_POST()
3010
- **
3011
- ** - Allow status change only if permissions allow.
3012
- */
3013
-
30142998
if( zFpid ){
30152999
iEditRid = symbolic_name_to_rid(zFpid, "f");
30163000
if( iEditRid<0 ){
30173001
rc = -ajax_route_error(400, "Ambiguous forum ID.");
30183002
goto ajax_save_end;
@@ -3031,11 +3015,13 @@
30313015
goto ajax_save_end;
30323016
}
30333017
}
30343018
30353019
if( 0 ){
3036
- rc = -ajax_route_error(400, "Save is TODO");
3020
+ rc = -ajax_route_error(400, "Save is TODO. "
3021
+ "iPostFlags=%d debug=%d",
3022
+ iPostFlags, g.perm.Debug);
30373023
goto ajax_save_end;
30383024
}
30393025
30403026
nrid = forum_post_ajax(zTitle, iIrt, iEditRid, 0, zMimetype,
30413027
zContent, iPostFlags);
@@ -3045,11 +3031,12 @@
30453031
}else if( nrid==0 ){
30463032
if( 0==(FPOST_DRYRUN & iPostFlags) ){
30473033
bRollback = 1;
30483034
CX("{\"message\": \"No saving needed.\"}\n");
30493035
}else{
3050
- CX("{\"message\": \"Rolled back for dry-run.\"}\n");
3036
+ CX("{\"message\": \"Rolled back for dry-run.\","
3037
+ "\"iPostFlags\":%d}\n", iPostFlags);
30513038
}
30523039
goto ajax_save_end;
30533040
}
30543041
if( nrid>0 ){
30553042
zNewUuid = rid_to_uuid(nrid);
@@ -3063,10 +3050,29 @@
30633050
attachments_ajax_from_POST(zNewUuid, forum_need_moderation());
30643051
if( atRc<0 ){
30653052
rc = atRc;
30663053
goto ajax_save_end;
30673054
}
3055
+ if( atRc>0
3056
+ && (iPostFlags & FPOST_NO_ALERT)!=0
3057
+ && db_table_exists("repository","pending_alert") ){
3058
+ /* Unqueue any alerts for these attachments. Recall that
3059
+ ** they're attached to the first version of the post, which
3060
+ ** means we actually risk cancelling _other_ pending
3061
+ ** notifications for attachments on this same post. */
3062
+ const int fpRoot = forumpost_head_rid(nrid);
3063
+ db_multi_exec(
3064
+ "WITH x(id) AS (\n"
3065
+ " SELECT 'f%d'\n"
3066
+ " UNION ALL\n"
3067
+ " SELECT 'f'||a.attachid FROM blob b, attachment a\n"
3068
+ " WHERE b.rid=%d\n"
3069
+ " AND b.uuid=a.target\n"
3070
+ ") DELETE FROM pending_alert WHERE eventid IN x",
3071
+ fpRoot, fpRoot
3072
+ );
3073
+ }
30683074
}
30693075
}
30703076
if( zStatus!=0 && zStatus[0]!=0
30713077
&& forum_may_set_status(nrid)
30723078
&& forumpost_tag(nrid, 1, "status", zStatus)<0 ){
@@ -3075,12 +3081,12 @@
30753081
}
30763082
}
30773083
30783084
assert( 0==rc );
30793085
assert( zNewUuid );
3080
- CX("{\"uuid\": %!j, \"dryrun\": %s}\n",
3081
- zNewUuid, bRollback ? "true" : "false");
3086
+ CX("{\"uuid\": %!j, \"dryrun\": %s, \"iPostFlags\":%d}\n",
3087
+ zNewUuid, bRollback ? "true" : "false", iPostFlags);
30823088
30833089
ajax_save_end:
30843090
fossil_free(zNewUuid);
30853091
db_end_transaction(rc || bRollback);
30863092
}
30873093
--- src/forum.c
+++ src/forum.c
@@ -2953,11 +2953,11 @@
2953 **
2954 ** Response JSON:
2955 **
2956 ** { uuid: hash, ...tbd }
2957 */
2958 void forum_ajax_save(void){
2959 const char *zFpid;
2960 const char *zTitle;
2961 const char *zIrt;
2962 const char *zMimetype;
2963 const char *zContent;
@@ -2967,12 +2967,12 @@
2967 int goodCaptcha = 1;
2968 int iIrt = 0; /* In-reply-to rid or 0 */
2969 int iEditRid = 0; /* Post rid being edited or 0 */
2970 int rc = 0;
2971 int nrid = 0;
2972 const int iPostFlags = forum_post_flags();
2973 int bRollback = (FPOST_DRYRUN & iPostFlags); /* True = roll back. */
2974
2975 if( !ajax_route_bootstrap(0, 1) ){
2976 return;
2977 }else if( !g.perm.WrForum
2978 || (bHasAttachment && !g.perm.AttachForum) ){
@@ -2984,35 +2984,19 @@
2984 }else if( 0==(goodCaptcha = captcha_is_correct(0)) ){
2985 ajax_route_error_captcha();
2986 return;
2987 }
2988
 
 
 
2989 zTitle = P("title");
2990 zIrt = P("firt");
2991 zMimetype = P("mimetype");
2992 zContent = P("content");
2993 zStatus = P("status");
2994 db_begin_transaction();
2995 /*
2996 ** TODOs include:
2997 **
2998 ** - Permissions and sanity checks, of course.
2999 **
3000 ** - Fork forum_post() into an AJAX-friendly form. It currently
3001 ** assumes HTML output.
3002 **
3003 ** - If zFpid then this is an edit. Else...
3004 **
3005 ** - If zIrt then this is a new response.
3006 **
3007 ** - zTitle is only honored if !zIrt, i.e. zFpid is the root post.
3008 **
3009 ** - attachments_ajax_from_POST()
3010 **
3011 ** - Allow status change only if permissions allow.
3012 */
3013
3014 if( zFpid ){
3015 iEditRid = symbolic_name_to_rid(zFpid, "f");
3016 if( iEditRid<0 ){
3017 rc = -ajax_route_error(400, "Ambiguous forum ID.");
3018 goto ajax_save_end;
@@ -3031,11 +3015,13 @@
3031 goto ajax_save_end;
3032 }
3033 }
3034
3035 if( 0 ){
3036 rc = -ajax_route_error(400, "Save is TODO");
 
 
3037 goto ajax_save_end;
3038 }
3039
3040 nrid = forum_post_ajax(zTitle, iIrt, iEditRid, 0, zMimetype,
3041 zContent, iPostFlags);
@@ -3045,11 +3031,12 @@
3045 }else if( nrid==0 ){
3046 if( 0==(FPOST_DRYRUN & iPostFlags) ){
3047 bRollback = 1;
3048 CX("{\"message\": \"No saving needed.\"}\n");
3049 }else{
3050 CX("{\"message\": \"Rolled back for dry-run.\"}\n");
 
3051 }
3052 goto ajax_save_end;
3053 }
3054 if( nrid>0 ){
3055 zNewUuid = rid_to_uuid(nrid);
@@ -3063,10 +3050,29 @@
3063 attachments_ajax_from_POST(zNewUuid, forum_need_moderation());
3064 if( atRc<0 ){
3065 rc = atRc;
3066 goto ajax_save_end;
3067 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3068 }
3069 }
3070 if( zStatus!=0 && zStatus[0]!=0
3071 && forum_may_set_status(nrid)
3072 && forumpost_tag(nrid, 1, "status", zStatus)<0 ){
@@ -3075,12 +3081,12 @@
3075 }
3076 }
3077
3078 assert( 0==rc );
3079 assert( zNewUuid );
3080 CX("{\"uuid\": %!j, \"dryrun\": %s}\n",
3081 zNewUuid, bRollback ? "true" : "false");
3082
3083 ajax_save_end:
3084 fossil_free(zNewUuid);
3085 db_end_transaction(rc || bRollback);
3086 }
3087
--- src/forum.c
+++ src/forum.c
@@ -2953,11 +2953,11 @@
2953 **
2954 ** Response JSON:
2955 **
2956 ** { uuid: hash, ...tbd }
2957 */
2958 void forum_ajax_save_page(void){
2959 const char *zFpid;
2960 const char *zTitle;
2961 const char *zIrt;
2962 const char *zMimetype;
2963 const char *zContent;
@@ -2967,12 +2967,12 @@
2967 int goodCaptcha = 1;
2968 int iIrt = 0; /* In-reply-to rid or 0 */
2969 int iEditRid = 0; /* Post rid being edited or 0 */
2970 int rc = 0;
2971 int nrid = 0;
2972 int iPostFlags;
2973 int bRollback = 1; /* True = roll back. */
2974
2975 if( !ajax_route_bootstrap(0, 1) ){
2976 return;
2977 }else if( !g.perm.WrForum
2978 || (bHasAttachment && !g.perm.AttachForum) ){
@@ -2984,35 +2984,19 @@
2984 }else if( 0==(goodCaptcha = captcha_is_correct(0)) ){
2985 ajax_route_error_captcha();
2986 return;
2987 }
2988
2989 iPostFlags = forum_post_flags();
2990 bRollback = (FPOST_DRYRUN & iPostFlags);
2991 zFpid = P("fpid");
2992 zTitle = P("title");
2993 zIrt = P("firt");
2994 zMimetype = P("mimetype");
2995 zContent = P("content");
2996 zStatus = P("status");
2997 db_begin_transaction();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2998 if( zFpid ){
2999 iEditRid = symbolic_name_to_rid(zFpid, "f");
3000 if( iEditRid<0 ){
3001 rc = -ajax_route_error(400, "Ambiguous forum ID.");
3002 goto ajax_save_end;
@@ -3031,11 +3015,13 @@
3015 goto ajax_save_end;
3016 }
3017 }
3018
3019 if( 0 ){
3020 rc = -ajax_route_error(400, "Save is TODO. "
3021 "iPostFlags=%d debug=%d",
3022 iPostFlags, g.perm.Debug);
3023 goto ajax_save_end;
3024 }
3025
3026 nrid = forum_post_ajax(zTitle, iIrt, iEditRid, 0, zMimetype,
3027 zContent, iPostFlags);
@@ -3045,11 +3031,12 @@
3031 }else if( nrid==0 ){
3032 if( 0==(FPOST_DRYRUN & iPostFlags) ){
3033 bRollback = 1;
3034 CX("{\"message\": \"No saving needed.\"}\n");
3035 }else{
3036 CX("{\"message\": \"Rolled back for dry-run.\","
3037 "\"iPostFlags\":%d}\n", iPostFlags);
3038 }
3039 goto ajax_save_end;
3040 }
3041 if( nrid>0 ){
3042 zNewUuid = rid_to_uuid(nrid);
@@ -3063,10 +3050,29 @@
3050 attachments_ajax_from_POST(zNewUuid, forum_need_moderation());
3051 if( atRc<0 ){
3052 rc = atRc;
3053 goto ajax_save_end;
3054 }
3055 if( atRc>0
3056 && (iPostFlags & FPOST_NO_ALERT)!=0
3057 && db_table_exists("repository","pending_alert") ){
3058 /* Unqueue any alerts for these attachments. Recall that
3059 ** they're attached to the first version of the post, which
3060 ** means we actually risk cancelling _other_ pending
3061 ** notifications for attachments on this same post. */
3062 const int fpRoot = forumpost_head_rid(nrid);
3063 db_multi_exec(
3064 "WITH x(id) AS (\n"
3065 " SELECT 'f%d'\n"
3066 " UNION ALL\n"
3067 " SELECT 'f'||a.attachid FROM blob b, attachment a\n"
3068 " WHERE b.rid=%d\n"
3069 " AND b.uuid=a.target\n"
3070 ") DELETE FROM pending_alert WHERE eventid IN x",
3071 fpRoot, fpRoot
3072 );
3073 }
3074 }
3075 }
3076 if( zStatus!=0 && zStatus[0]!=0
3077 && forum_may_set_status(nrid)
3078 && forumpost_tag(nrid, 1, "status", zStatus)<0 ){
@@ -3075,12 +3081,12 @@
3081 }
3082 }
3083
3084 assert( 0==rc );
3085 assert( zNewUuid );
3086 CX("{\"uuid\": %!j, \"dryrun\": %s, \"iPostFlags\":%d}\n",
3087 zNewUuid, bRollback ? "true" : "false", iPostFlags);
3088
3089 ajax_save_end:
3090 fossil_free(zNewUuid);
3091 db_end_transaction(rc || bRollback);
3092 }
3093
--- src/fossil.page.forumpost.js
+++ src/fossil.page.forumpost.js
@@ -152,11 +152,11 @@
152152
D.addClass(D.div(), 'tab-container'),
153153
'id', idPrefix+'-tabs'
154154
);
155155
this.#tabs = new F.TabManager(e.tabs);
156156
this.#tabs.addEventListener('before-switch-to', (ev)=>{
157
- console.debug("Switching to tab",ev.detail);
157
+ //console.debug("Switching to tab",ev.detail);
158158
switch( (this.#activeTab = ev.detail) ){
159159
case e.preview:
160160
this.#e.button.preview.click();
161161
break;
162162
case e.help:
@@ -202,11 +202,11 @@
202202
e.debug.dataset.tabLabel = 'Debug';
203203
e.debug.setAttribute('id', idPrefix+'-debug');
204204
for(const [k,v] of Object.entries({
205205
dryrun: 'Dry run',
206206
domod: 'Require moderation approval',
207
- showqp: 'Show query parameters',
207
+ //showqp: 'Show query parameters',
208208
fpsilent: 'Do not send notification emails'
209209
})){
210210
const lbl = D.label(false, v);
211211
lbl.prepend(D.checkbox(k));
212212
e.debug.append(lbl);
@@ -518,13 +518,10 @@
518518
if( !this.#validate() ) return;
519519
this.#isWaiting = true;
520520
const e = this.#e;
521521
D.disable(e.button.submit);
522522
const fd = this.#newFormData();
523
- if( this.#att ){
524
- this.#att.populateFormData(fd);
525
- }
526523
if( this.#e.status ){
527524
/* Send the status only if it was modified, otherwise we may
528525
add a superfluous tag. */
529526
const v = this.#e.status.value;
530527
if( this.#e.status.dataset.originalValue !== v ){
@@ -531,14 +528,19 @@
531528
fd.append( "status", v );
532529
}
533530
}
534531
if( e.debug ){
535532
e.debug.querySelectorAll('input[type=checkbox]').forEach(cb=>{
536
- console.debug("Debug option:",cb);
537
- if( cb.checked ) fd.append(cb.value, 1);
533
+ if( cb.checked ){
534
+ fd.append(cb.value, 1);
535
+ console.debug("Debug option:",cb);
536
+ }
538537
});
539538
}
539
+ if( this.#att ){
540
+ this.#att.populateFormData(fd);
541
+ }
540542
console.warn("Ready to submit",fd);
541543
if( 0 ){
542544
this.#isWaiting = false;
543545
return;
544546
}
545547
--- src/fossil.page.forumpost.js
+++ src/fossil.page.forumpost.js
@@ -152,11 +152,11 @@
152 D.addClass(D.div(), 'tab-container'),
153 'id', idPrefix+'-tabs'
154 );
155 this.#tabs = new F.TabManager(e.tabs);
156 this.#tabs.addEventListener('before-switch-to', (ev)=>{
157 console.debug("Switching to tab",ev.detail);
158 switch( (this.#activeTab = ev.detail) ){
159 case e.preview:
160 this.#e.button.preview.click();
161 break;
162 case e.help:
@@ -202,11 +202,11 @@
202 e.debug.dataset.tabLabel = 'Debug';
203 e.debug.setAttribute('id', idPrefix+'-debug');
204 for(const [k,v] of Object.entries({
205 dryrun: 'Dry run',
206 domod: 'Require moderation approval',
207 showqp: 'Show query parameters',
208 fpsilent: 'Do not send notification emails'
209 })){
210 const lbl = D.label(false, v);
211 lbl.prepend(D.checkbox(k));
212 e.debug.append(lbl);
@@ -518,13 +518,10 @@
518 if( !this.#validate() ) return;
519 this.#isWaiting = true;
520 const e = this.#e;
521 D.disable(e.button.submit);
522 const fd = this.#newFormData();
523 if( this.#att ){
524 this.#att.populateFormData(fd);
525 }
526 if( this.#e.status ){
527 /* Send the status only if it was modified, otherwise we may
528 add a superfluous tag. */
529 const v = this.#e.status.value;
530 if( this.#e.status.dataset.originalValue !== v ){
@@ -531,14 +528,19 @@
531 fd.append( "status", v );
532 }
533 }
534 if( e.debug ){
535 e.debug.querySelectorAll('input[type=checkbox]').forEach(cb=>{
536 console.debug("Debug option:",cb);
537 if( cb.checked ) fd.append(cb.value, 1);
 
 
538 });
539 }
 
 
 
540 console.warn("Ready to submit",fd);
541 if( 0 ){
542 this.#isWaiting = false;
543 return;
544 }
545
--- src/fossil.page.forumpost.js
+++ src/fossil.page.forumpost.js
@@ -152,11 +152,11 @@
152 D.addClass(D.div(), 'tab-container'),
153 'id', idPrefix+'-tabs'
154 );
155 this.#tabs = new F.TabManager(e.tabs);
156 this.#tabs.addEventListener('before-switch-to', (ev)=>{
157 //console.debug("Switching to tab",ev.detail);
158 switch( (this.#activeTab = ev.detail) ){
159 case e.preview:
160 this.#e.button.preview.click();
161 break;
162 case e.help:
@@ -202,11 +202,11 @@
202 e.debug.dataset.tabLabel = 'Debug';
203 e.debug.setAttribute('id', idPrefix+'-debug');
204 for(const [k,v] of Object.entries({
205 dryrun: 'Dry run',
206 domod: 'Require moderation approval',
207 //showqp: 'Show query parameters',
208 fpsilent: 'Do not send notification emails'
209 })){
210 const lbl = D.label(false, v);
211 lbl.prepend(D.checkbox(k));
212 e.debug.append(lbl);
@@ -518,13 +518,10 @@
518 if( !this.#validate() ) return;
519 this.#isWaiting = true;
520 const e = this.#e;
521 D.disable(e.button.submit);
522 const fd = this.#newFormData();
 
 
 
523 if( this.#e.status ){
524 /* Send the status only if it was modified, otherwise we may
525 add a superfluous tag. */
526 const v = this.#e.status.value;
527 if( this.#e.status.dataset.originalValue !== v ){
@@ -531,14 +528,19 @@
528 fd.append( "status", v );
529 }
530 }
531 if( e.debug ){
532 e.debug.querySelectorAll('input[type=checkbox]').forEach(cb=>{
533 if( cb.checked ){
534 fd.append(cb.value, 1);
535 console.debug("Debug option:",cb);
536 }
537 });
538 }
539 if( this.#att ){
540 this.#att.populateFormData(fd);
541 }
542 console.warn("Ready to submit",fd);
543 if( 0 ){
544 this.#isWaiting = false;
545 return;
546 }
547

Keyboard Shortcuts

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