Fossil SCM

Milestone: first new forum post with the new editor.

stephan 2026-06-07 13:09 UTC forum-editor-2026
Commit 46bb3a1255dc8793dff26766365b01d7098b53929c94dcb4cbae579ee28f96c8
+3 -6
--- src/attach.c
+++ src/attach.c
@@ -931,25 +931,22 @@
931931
}
932932
sqlite3_snprintf(sizeof(aKeySize), aKeySize, "%s:bytes",
933933
aKeyPrefix);
934934
szContent = atoi(PD(aKeySize,"-1"));
935935
if( szContent<=0 ){
936
- rc = -400;
937
- ajax_route_error(400,"Invalid file size: %d", szContent);
936
+ rc = -ajax_route_error(400,"Invalid file size: %d", szContent);
938937
break;
939938
}else if( szLimit>0 && szContent>szLimit ){
940
- rc = -413;
941
- ajax_route_error(413, "File size limit is %d bytes.", szLimit);
939
+ rc = -ajax_route_error(413, "File size limit is %d bytes.", szLimit);
942940
break;
943941
}else{
944942
sqlite3_snprintf(sizeof(aKeyName), aKeyName, "%s:filename",
945943
aKeyPrefix);
946944
sqlite3_snprintf(sizeof(aKeyDesc), aKeyDesc, "%s_desc",
947945
aKeyPrefix);
948946
if( 0==(zFilename=P(aKeyName)) ){
949
- rc = -400;
950
- ajax_route_error(400, "Missing filename.");
947
+ rc = -ajax_route_error(400, "Missing filename.");
951948
break;
952949
}
953950
attach_commit(zFilename, zTarget, zContent, szContent,
954951
bNeedsModeration, P(aKeyDesc));
955952
}
956953
--- src/attach.c
+++ src/attach.c
@@ -931,25 +931,22 @@
931 }
932 sqlite3_snprintf(sizeof(aKeySize), aKeySize, "%s:bytes",
933 aKeyPrefix);
934 szContent = atoi(PD(aKeySize,"-1"));
935 if( szContent<=0 ){
936 rc = -400;
937 ajax_route_error(400,"Invalid file size: %d", szContent);
938 break;
939 }else if( szLimit>0 && szContent>szLimit ){
940 rc = -413;
941 ajax_route_error(413, "File size limit is %d bytes.", szLimit);
942 break;
943 }else{
944 sqlite3_snprintf(sizeof(aKeyName), aKeyName, "%s:filename",
945 aKeyPrefix);
946 sqlite3_snprintf(sizeof(aKeyDesc), aKeyDesc, "%s_desc",
947 aKeyPrefix);
948 if( 0==(zFilename=P(aKeyName)) ){
949 rc = -400;
950 ajax_route_error(400, "Missing filename.");
951 break;
952 }
953 attach_commit(zFilename, zTarget, zContent, szContent,
954 bNeedsModeration, P(aKeyDesc));
955 }
956
--- src/attach.c
+++ src/attach.c
@@ -931,25 +931,22 @@
931 }
932 sqlite3_snprintf(sizeof(aKeySize), aKeySize, "%s:bytes",
933 aKeyPrefix);
934 szContent = atoi(PD(aKeySize,"-1"));
935 if( szContent<=0 ){
936 rc = -ajax_route_error(400,"Invalid file size: %d", szContent);
 
937 break;
938 }else if( szLimit>0 && szContent>szLimit ){
939 rc = -ajax_route_error(413, "File size limit is %d bytes.", szLimit);
 
940 break;
941 }else{
942 sqlite3_snprintf(sizeof(aKeyName), aKeyName, "%s:filename",
943 aKeyPrefix);
944 sqlite3_snprintf(sizeof(aKeyDesc), aKeyDesc, "%s_desc",
945 aKeyPrefix);
946 if( 0==(zFilename=P(aKeyName)) ){
947 rc = -ajax_route_error(400, "Missing filename.");
 
948 break;
949 }
950 attach_commit(zFilename, zTarget, zContent, szContent,
951 bNeedsModeration, P(aKeyDesc));
952 }
953
+90 -55
--- src/forum.c
+++ src/forum.c
@@ -416,12 +416,13 @@
416416
** (not accounting for an inherited closed tag), this is a no-op.
417417
**
418418
** If bCheckIrt is true then the forum post IRT hierarchy is searched
419419
** for the tag, otherwise only the given RID is checked.
420420
**
421
-** Returns true if it actually creates a new tag, else false. Fails
422
-** fatally on error.
421
+** Returns a positive value (a new tag.tagid value) if it actually
422
+** creates a new tag, else 0. On error it returns a negative alue
423
+** and g.zErrMsg "should" contain details.
423424
**
424425
** If it returns true then state from previously-loaded posts may be
425426
** invalidated if they refer to the amended post or a response to it.
426427
** e.g. if zTagName is "closed" then ForumPost::iClosed values may be
427428
** stale.
@@ -484,22 +485,22 @@
484485
md5sum_blob(&artifact, &cksum);
485486
blob_appendf(&artifact, "Z %b\n", &cksum);
486487
blob_reset(&cksum);
487488
trid = content_put_ex(&artifact, 0, 0, 0, 0);
488489
if( trid==0 ){
489
- fossil_fatal("Error saving tag artifact: %s", g.zErrMsg);
490
+ return -1;
490491
}
491492
if( manifest_crosslink(trid, &artifact, MC_NONE)==0 ){
492
- fossil_fatal("%s", g.zErrMsg);
493
+ return -2;
493494
}
494495
assert( blob_is_reset(&artifact) );
495496
db_add_unsent(trid);
496497
admin_log("Tag forum post %S with %c%s",
497498
zUuid, addTag ? '*' : '-', zTagName);
498499
fossil_free(zUuid);
499500
db_end_transaction(0);
500
- return 1;
501
+ return trid;
501502
}
502503
503504
/*
504505
** Returns true if the forum-close-policy setting is true, else false,
505506
** caching the result for subsequent calls.
@@ -1658,11 +1659,11 @@
16581659
return z[0]==0;
16591660
}
16601661
16611662
/* Flags for use with forum_post() */
16621663
#define FPOST_NO_ALERT 1 /* do not send any alerts */
1663
-#define FPOST_DRY_RUN 2 /* do not save the artifact */
1664
+#define FPOST_DRYRUN 2 /* do not save the artifact */
16641665
16651666
/*
16661667
** Return a flags value for use with the final argument to
16671668
** forum_post(), extracted from the CGI environment.
16681669
*/
@@ -1670,11 +1671,11 @@
16701671
int iPostFlags = 0;
16711672
if( g.perm.Debug && P("fpsilent")!=0 ){
16721673
iPostFlags |= FPOST_NO_ALERT;
16731674
}
16741675
if( P("dryrun")!=0 ){
1675
- iPostFlags |= FPOST_DRY_RUN;
1676
+ iPostFlags |= FPOST_DRYRUN;
16761677
}
16771678
return iPostFlags;
16781679
}
16791680
16801681
/*
@@ -1768,11 +1769,11 @@
17681769
webpage_error("malformed forum post artifact - %s", blob_str(&errMsg));
17691770
}
17701771
webpage_assert( pPost->type==CFTYPE_FORUM );
17711772
manifest_destroy(pPost);
17721773
1773
- if( (iFlags & FPOST_DRY_RUN)!=0 ){
1774
+ if( (iFlags & FPOST_DRYRUN)!=0 ){
17741775
@ <div class='debug'>
17751776
@ This is the artifact that would have been generated:
17761777
@ <pre>%h(blob_str(&x))</pre>
17771778
@ </div>
17781779
blob_reset(&x);
@@ -1831,12 +1832,17 @@
18311832
int addTag, int validFpid){
18321833
if( !cgi_csrf_safe(2) ){
18331834
webpage_error("CSRF validation failed");
18341835
}else{
18351836
const int fpid = validFpid>0 ? validFpid : forum_validate_fpid_param();
1836
- forumpost_tag(fpid, addTag, zTag, zVal);
1837
- cgi_redirectf("%R/forumpost/%S",P("fpid"));
1837
+ if( fpid>0 ){
1838
+ if( forumpost_tag(fpid, addTag, zTag, zVal) < 0 ){
1839
+ webpage_error("Tagging artifact failed: %s", g.zErrMsg);
1840
+ }else{
1841
+ cgi_redirectf("%R/forumpost/%S",P("fpid"));
1842
+ }
1843
+ }
18381844
}
18391845
}
18401846
18411847
/*
18421848
** WEBPAGE: forumpost_close hidden
@@ -2918,11 +2924,11 @@
29182924
rc = -500;
29192925
goto post_ajax_end;
29202926
}
29212927
webpage_assert( pPost->type==CFTYPE_FORUM );
29222928
2923
- if( (iFlags & FPOST_DRY_RUN)!=0 ){
2929
+ if( (iFlags & FPOST_DRYRUN)!=0 ){
29242930
rc = 0;
29252931
}else{
29262932
int nrid;
29272933
db_begin_transaction();
29282934
nrid = wiki_put(&x, iEdit>0 ? iEdit : 0, forum_need_moderation());
@@ -2948,25 +2954,25 @@
29482954
** Response JSON:
29492955
**
29502956
** { uuid: hash, ...tbd }
29512957
*/
29522958
void forum_ajax_save(void){
2953
- const char *zUuid;
2959
+ const char *zFpid;
29542960
const char *zTitle;
29552961
const char *zIrt;
29562962
const char *zMimetype;
29572963
const char *zContent;
29582964
const char *zStatus;
29592965
const int bHasAttachment = P("file1")!=0;
29602966
char *zNewUuid = 0;
2961
- int bNeedsModeration = 0;
29622967
int goodCaptcha = 1;
2963
- int bRollback = PB("rollback"); /* True if we should roll back. */
29642968
int iIrt = 0; /* In-reply-to rid or 0 */
29652969
int iEditRid = 0; /* Post rid being edited or 0 */
29662970
int rc = 0;
29672971
int nrid = 0;
2972
+ const int iPostFlags = forum_post_flags();
2973
+ int bRollback = (FPOST_DRYRUN & iPostFlags); /* True = roll back. */
29682974
29692975
if( !ajax_route_bootstrap(0, 1) ){
29702976
return;
29712977
}else if( !g.perm.WrForum
29722978
|| (bHasAttachment && !g.perm.AttachForum) ){
@@ -2992,60 +2998,89 @@
29922998
** - Permissions and sanity checks, of course.
29932999
**
29943000
** - Fork forum_post() into an AJAX-friendly form. It currently
29953001
** assumes HTML output.
29963002
**
2997
- ** - If zUuid then this is an edit. Else...
3003
+ ** - If zFpid then this is an edit. Else...
29983004
**
29993005
** - If zIrt then this is a new response.
30003006
**
3001
- ** - zTitle is only honored if !zIrt, i.e. zUuid is the root post.
3007
+ ** - zTitle is only honored if !zIrt, i.e. zFpid is the root post.
30023008
**
30033009
** - attachments_ajax_from_POST()
30043010
**
30053011
** - Allow status change only if permissions allow.
30063012
*/
30073013
3008
- (void)bNeedsModeration;
3009
- (void)zUuid;
3010
- (void)zIrt;
3011
- (void)zMimetype;
3012
- (void)zContent;
3013
- (void)zStatus;
3014
-
3015
- if( 1 ){
3016
- bRollback = 1;
3017
- ajax_route_error(400, "Save is TODO");
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;
3019
+ }else if( 0==iEditRid ){
3020
+ rc = -ajax_route_error(404, "Cannot resolve forum post ID.");
3021
+ goto ajax_save_end;
3022
+ }
3023
+ }
3024
+ if( zIrt ){
3025
+ iIrt = symbolic_name_to_rid(zIrt, "f");
3026
+ if( iIrt<0 ){
3027
+ rc = -ajax_route_error(400, "Ambiguous in-reply-do ID.");
3028
+ goto ajax_save_end;
3029
+ }else if( 0==iIrt ){
3030
+ rc = -ajax_route_error(404, "Cannot resolve in-reply-do ID.");
3031
+ goto ajax_save_end;
3032
+ }
3033
+ }
3034
+
3035
+ if( 0 ){
3036
+ rc = -ajax_route_error(400, "Save is TODO");
30183037
goto ajax_save_end;
30193038
}
30203039
30213040
nrid = forum_post_ajax(zTitle, iIrt, iEditRid, 0, zMimetype,
3022
- zContent, forum_post_flags());
3023
- if( nrid==0 ){
3024
- CX("{\"message\": \"No changes needed saving.\"}\n");
3025
- goto ajax_save_end;
3026
- }
3027
- if( zStatus!=0 && zStatus[0]!=0 ){
3028
- forumpost_tag(nrid, 1, "status", zStatus)
3029
- /* FIXME: ^^^ fails fatally on error */;
3030
- }
3031
- zNewUuid = rid_to_uuid(nrid);
3032
- if( 0!=P("file1") ){
3033
- /* Attachments */
3034
- const int atRc =
3035
- attachments_ajax_from_POST(zNewUuid, bNeedsModeration);
3036
- if( atRc<0 ){
3037
- rc = atRc;
3038
- goto ajax_save_end;
3039
- }
3040
- }
3041
-
3042
-
3043
- if( 0==rc ){
3044
- CX("{\"uuid\": %!j}\n", zNewUuid);
3045
- }
3046
-
3047
-ajax_save_end:
3048
- fossil_free(zNewUuid);
3049
- if( 0!=rc ) bRollback = 1;
3050
- db_end_transaction(bRollback);
3041
+ zContent, iPostFlags);
3042
+ if( nrid<0 ){
3043
+ rc = nrid;
3044
+ goto ajax_save_end;
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);
3056
+ if( 0!=P("file1") ){
3057
+ /* Attachments */
3058
+ if( !g.perm.Admin && !g.perm.AttachForum ){
3059
+ rc = -ajax_route_error(403, "No permission no attach files.");
3060
+ goto ajax_save_end;
3061
+ }else{
3062
+ const int atRc =
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 ){
3073
+ rc = -ajax_route_error(500, "Tagging failed: %s", g.zErrMsg);
3074
+ goto ajax_save_end;
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);
30513086
}
30523087
--- src/forum.c
+++ src/forum.c
@@ -416,12 +416,13 @@
416 ** (not accounting for an inherited closed tag), this is a no-op.
417 **
418 ** If bCheckIrt is true then the forum post IRT hierarchy is searched
419 ** for the tag, otherwise only the given RID is checked.
420 **
421 ** Returns true if it actually creates a new tag, else false. Fails
422 ** fatally on error.
 
423 **
424 ** If it returns true then state from previously-loaded posts may be
425 ** invalidated if they refer to the amended post or a response to it.
426 ** e.g. if zTagName is "closed" then ForumPost::iClosed values may be
427 ** stale.
@@ -484,22 +485,22 @@
484 md5sum_blob(&artifact, &cksum);
485 blob_appendf(&artifact, "Z %b\n", &cksum);
486 blob_reset(&cksum);
487 trid = content_put_ex(&artifact, 0, 0, 0, 0);
488 if( trid==0 ){
489 fossil_fatal("Error saving tag artifact: %s", g.zErrMsg);
490 }
491 if( manifest_crosslink(trid, &artifact, MC_NONE)==0 ){
492 fossil_fatal("%s", g.zErrMsg);
493 }
494 assert( blob_is_reset(&artifact) );
495 db_add_unsent(trid);
496 admin_log("Tag forum post %S with %c%s",
497 zUuid, addTag ? '*' : '-', zTagName);
498 fossil_free(zUuid);
499 db_end_transaction(0);
500 return 1;
501 }
502
503 /*
504 ** Returns true if the forum-close-policy setting is true, else false,
505 ** caching the result for subsequent calls.
@@ -1658,11 +1659,11 @@
1658 return z[0]==0;
1659 }
1660
1661 /* Flags for use with forum_post() */
1662 #define FPOST_NO_ALERT 1 /* do not send any alerts */
1663 #define FPOST_DRY_RUN 2 /* do not save the artifact */
1664
1665 /*
1666 ** Return a flags value for use with the final argument to
1667 ** forum_post(), extracted from the CGI environment.
1668 */
@@ -1670,11 +1671,11 @@
1670 int iPostFlags = 0;
1671 if( g.perm.Debug && P("fpsilent")!=0 ){
1672 iPostFlags |= FPOST_NO_ALERT;
1673 }
1674 if( P("dryrun")!=0 ){
1675 iPostFlags |= FPOST_DRY_RUN;
1676 }
1677 return iPostFlags;
1678 }
1679
1680 /*
@@ -1768,11 +1769,11 @@
1768 webpage_error("malformed forum post artifact - %s", blob_str(&errMsg));
1769 }
1770 webpage_assert( pPost->type==CFTYPE_FORUM );
1771 manifest_destroy(pPost);
1772
1773 if( (iFlags & FPOST_DRY_RUN)!=0 ){
1774 @ <div class='debug'>
1775 @ This is the artifact that would have been generated:
1776 @ <pre>%h(blob_str(&x))</pre>
1777 @ </div>
1778 blob_reset(&x);
@@ -1831,12 +1832,17 @@
1831 int addTag, int validFpid){
1832 if( !cgi_csrf_safe(2) ){
1833 webpage_error("CSRF validation failed");
1834 }else{
1835 const int fpid = validFpid>0 ? validFpid : forum_validate_fpid_param();
1836 forumpost_tag(fpid, addTag, zTag, zVal);
1837 cgi_redirectf("%R/forumpost/%S",P("fpid"));
 
 
 
 
 
1838 }
1839 }
1840
1841 /*
1842 ** WEBPAGE: forumpost_close hidden
@@ -2918,11 +2924,11 @@
2918 rc = -500;
2919 goto post_ajax_end;
2920 }
2921 webpage_assert( pPost->type==CFTYPE_FORUM );
2922
2923 if( (iFlags & FPOST_DRY_RUN)!=0 ){
2924 rc = 0;
2925 }else{
2926 int nrid;
2927 db_begin_transaction();
2928 nrid = wiki_put(&x, iEdit>0 ? iEdit : 0, forum_need_moderation());
@@ -2948,25 +2954,25 @@
2948 ** Response JSON:
2949 **
2950 ** { uuid: hash, ...tbd }
2951 */
2952 void forum_ajax_save(void){
2953 const char *zUuid;
2954 const char *zTitle;
2955 const char *zIrt;
2956 const char *zMimetype;
2957 const char *zContent;
2958 const char *zStatus;
2959 const int bHasAttachment = P("file1")!=0;
2960 char *zNewUuid = 0;
2961 int bNeedsModeration = 0;
2962 int goodCaptcha = 1;
2963 int bRollback = PB("rollback"); /* True if we should roll back. */
2964 int iIrt = 0; /* In-reply-to rid or 0 */
2965 int iEditRid = 0; /* Post rid being edited or 0 */
2966 int rc = 0;
2967 int nrid = 0;
 
 
2968
2969 if( !ajax_route_bootstrap(0, 1) ){
2970 return;
2971 }else if( !g.perm.WrForum
2972 || (bHasAttachment && !g.perm.AttachForum) ){
@@ -2992,60 +2998,89 @@
2992 ** - Permissions and sanity checks, of course.
2993 **
2994 ** - Fork forum_post() into an AJAX-friendly form. It currently
2995 ** assumes HTML output.
2996 **
2997 ** - If zUuid then this is an edit. Else...
2998 **
2999 ** - If zIrt then this is a new response.
3000 **
3001 ** - zTitle is only honored if !zIrt, i.e. zUuid is the root post.
3002 **
3003 ** - attachments_ajax_from_POST()
3004 **
3005 ** - Allow status change only if permissions allow.
3006 */
3007
3008 (void)bNeedsModeration;
3009 (void)zUuid;
3010 (void)zIrt;
3011 (void)zMimetype;
3012 (void)zContent;
3013 (void)zStatus;
3014
3015 if( 1 ){
3016 bRollback = 1;
3017 ajax_route_error(400, "Save is TODO");
 
 
 
 
 
 
 
 
 
 
 
 
 
3018 goto ajax_save_end;
3019 }
3020
3021 nrid = forum_post_ajax(zTitle, iIrt, iEditRid, 0, zMimetype,
3022 zContent, forum_post_flags());
3023 if( nrid==0 ){
3024 CX("{\"message\": \"No changes needed saving.\"}\n");
3025 goto ajax_save_end;
3026 }
3027 if( zStatus!=0 && zStatus[0]!=0 ){
3028 forumpost_tag(nrid, 1, "status", zStatus)
3029 /* FIXME: ^^^ fails fatally on error */;
3030 }
3031 zNewUuid = rid_to_uuid(nrid);
3032 if( 0!=P("file1") ){
3033 /* Attachments */
3034 const int atRc =
3035 attachments_ajax_from_POST(zNewUuid, bNeedsModeration);
3036 if( atRc<0 ){
3037 rc = atRc;
3038 goto ajax_save_end;
3039 }
3040 }
3041
3042
3043 if( 0==rc ){
3044 CX("{\"uuid\": %!j}\n", zNewUuid);
3045 }
3046
3047 ajax_save_end:
3048 fossil_free(zNewUuid);
3049 if( 0!=rc ) bRollback = 1;
3050 db_end_transaction(bRollback);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3051 }
3052
--- src/forum.c
+++ src/forum.c
@@ -416,12 +416,13 @@
416 ** (not accounting for an inherited closed tag), this is a no-op.
417 **
418 ** If bCheckIrt is true then the forum post IRT hierarchy is searched
419 ** for the tag, otherwise only the given RID is checked.
420 **
421 ** Returns a positive value (a new tag.tagid value) if it actually
422 ** creates a new tag, else 0. On error it returns a negative alue
423 ** and g.zErrMsg "should" contain details.
424 **
425 ** If it returns true then state from previously-loaded posts may be
426 ** invalidated if they refer to the amended post or a response to it.
427 ** e.g. if zTagName is "closed" then ForumPost::iClosed values may be
428 ** stale.
@@ -484,22 +485,22 @@
485 md5sum_blob(&artifact, &cksum);
486 blob_appendf(&artifact, "Z %b\n", &cksum);
487 blob_reset(&cksum);
488 trid = content_put_ex(&artifact, 0, 0, 0, 0);
489 if( trid==0 ){
490 return -1;
491 }
492 if( manifest_crosslink(trid, &artifact, MC_NONE)==0 ){
493 return -2;
494 }
495 assert( blob_is_reset(&artifact) );
496 db_add_unsent(trid);
497 admin_log("Tag forum post %S with %c%s",
498 zUuid, addTag ? '*' : '-', zTagName);
499 fossil_free(zUuid);
500 db_end_transaction(0);
501 return trid;
502 }
503
504 /*
505 ** Returns true if the forum-close-policy setting is true, else false,
506 ** caching the result for subsequent calls.
@@ -1658,11 +1659,11 @@
1659 return z[0]==0;
1660 }
1661
1662 /* Flags for use with forum_post() */
1663 #define FPOST_NO_ALERT 1 /* do not send any alerts */
1664 #define FPOST_DRYRUN 2 /* do not save the artifact */
1665
1666 /*
1667 ** Return a flags value for use with the final argument to
1668 ** forum_post(), extracted from the CGI environment.
1669 */
@@ -1670,11 +1671,11 @@
1671 int iPostFlags = 0;
1672 if( g.perm.Debug && P("fpsilent")!=0 ){
1673 iPostFlags |= FPOST_NO_ALERT;
1674 }
1675 if( P("dryrun")!=0 ){
1676 iPostFlags |= FPOST_DRYRUN;
1677 }
1678 return iPostFlags;
1679 }
1680
1681 /*
@@ -1768,11 +1769,11 @@
1769 webpage_error("malformed forum post artifact - %s", blob_str(&errMsg));
1770 }
1771 webpage_assert( pPost->type==CFTYPE_FORUM );
1772 manifest_destroy(pPost);
1773
1774 if( (iFlags & FPOST_DRYRUN)!=0 ){
1775 @ <div class='debug'>
1776 @ This is the artifact that would have been generated:
1777 @ <pre>%h(blob_str(&x))</pre>
1778 @ </div>
1779 blob_reset(&x);
@@ -1831,12 +1832,17 @@
1832 int addTag, int validFpid){
1833 if( !cgi_csrf_safe(2) ){
1834 webpage_error("CSRF validation failed");
1835 }else{
1836 const int fpid = validFpid>0 ? validFpid : forum_validate_fpid_param();
1837 if( fpid>0 ){
1838 if( forumpost_tag(fpid, addTag, zTag, zVal) < 0 ){
1839 webpage_error("Tagging artifact failed: %s", g.zErrMsg);
1840 }else{
1841 cgi_redirectf("%R/forumpost/%S",P("fpid"));
1842 }
1843 }
1844 }
1845 }
1846
1847 /*
1848 ** WEBPAGE: forumpost_close hidden
@@ -2918,11 +2924,11 @@
2924 rc = -500;
2925 goto post_ajax_end;
2926 }
2927 webpage_assert( pPost->type==CFTYPE_FORUM );
2928
2929 if( (iFlags & FPOST_DRYRUN)!=0 ){
2930 rc = 0;
2931 }else{
2932 int nrid;
2933 db_begin_transaction();
2934 nrid = wiki_put(&x, iEdit>0 ? iEdit : 0, forum_need_moderation());
@@ -2948,25 +2954,25 @@
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;
2964 const char *zStatus;
2965 const int bHasAttachment = P("file1")!=0;
2966 char *zNewUuid = 0;
 
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) ){
@@ -2992,60 +2998,89 @@
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;
3019 }else if( 0==iEditRid ){
3020 rc = -ajax_route_error(404, "Cannot resolve forum post ID.");
3021 goto ajax_save_end;
3022 }
3023 }
3024 if( zIrt ){
3025 iIrt = symbolic_name_to_rid(zIrt, "f");
3026 if( iIrt<0 ){
3027 rc = -ajax_route_error(400, "Ambiguous in-reply-do ID.");
3028 goto ajax_save_end;
3029 }else if( 0==iIrt ){
3030 rc = -ajax_route_error(404, "Cannot resolve in-reply-do ID.");
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);
3042 if( nrid<0 ){
3043 rc = nrid;
3044 goto ajax_save_end;
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);
3056 if( 0!=P("file1") ){
3057 /* Attachments */
3058 if( !g.perm.Admin && !g.perm.AttachForum ){
3059 rc = -ajax_route_error(403, "No permission no attach files.");
3060 goto ajax_save_end;
3061 }else{
3062 const int atRc =
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 ){
3073 rc = -ajax_route_error(500, "Tagging failed: %s", g.zErrMsg);
3074 goto ajax_save_end;
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/fossil.page.forumpost.js
+++ src/fossil.page.forumpost.js
@@ -60,11 +60,15 @@
6060
D.clearElement(wrapper);
6161
6262
if( !opt.inReplyTo ){
6363
/* Title... */
6464
e.titleBar = D.addClass(D.div(),'titlebar');
65
- e.title = D.addClass(D.input('text'), 'title');
65
+ e.title = D.attr(
66
+ D.addClass(D.input('text'), 'title'),
67
+ 'placeholder',
68
+ 'Thread title (required)'
69
+ );
6670
e.title.setAttribute('maxlength', 125);
6771
e.titleBar.append(
6872
D.append(D.span(), "Title:"),
6973
e.title
7074
);
@@ -71,40 +75,42 @@
7175
if( this.#draft ){
7276
e.title.addEventListener('blur', ()=>{
7377
this.#draft.title = e.title.value;
7478
this.#storeDraft();
7579
});
76
- e.title.value = opt.title || this.#draft.title;
80
+ e.title.value = opt.title || this.#draft.title || '';
7781
}else if( opt.title ){
7882
e.title.value = opt.title;
7983
}
8084
wrapper.append(e.titleBar);
8185
}
8286
8387
{ /* Mimetype... */
8488
e.mimetype.wrapper = D.addClass(D.div(), 'mimetype-wrapper');
85
- e.mimetype.select = D.addClass(D.select(), 'mimetype-select');
86
- this.#toDisable.push(e.mimetype.select);
89
+ const sel = e.mimetype.select = D.addClass(D.select(), 'mimetype-select');
90
+ this.#toDisable.push(sel);
8791
let i = 0;
88
- D.option(e.mimetype.select, '', 'Markup format').disabled = true;
92
+ D.option(sel, '', 'Markup format').disabled = true;
8993
for(const [k,v] of Object.entries({
9094
'text/x-markdown': 'Markdown',
9195
'text/x-fossil-wiki': 'Fossil Wiki',
9296
'text/plain': 'Plain text'
9397
})) {
94
- D.option(e.mimetype.select, k, v);
98
+ D.option(sel, k, v);
9599
}
100
+ sel.value = opt.mimetype
101
+ || this.#draft?.mimetype
102
+ || sel.options[1].value;
96103
if( this.#draft ){
97
- e.mimetype.select.value = opt.mimetype || this.#draft.mimetype;
98
- e.mimetype.select.addEventListener('change',ev=>{
104
+ sel.addEventListener('change',ev=>{
99105
if( this.#draft.mimetype!==ev.target.value ){
100106
this.#draft.mimetype = ev.target.value;
101107
this.#storeDraft();
102108
}
103109
});
104110
}
105
- e.mimetype.wrapper.append(e.mimetype.select);
111
+ e.mimetype.wrapper.append(sel);
106112
}
107113
108114
e.buttons = D.addClass(D.div(), 'buttons');
109115
{ /* Preview/submit buttons... */
110116
e.button.preview = D.button("Preview", e=>this.#preview());
@@ -176,11 +182,11 @@
176182
e.tabEdit.append(e.editor);
177183
e.tabEdit.dataset.tabLabel = 'Edit';
178184
this.#tabs.addTab( e.tabEdit );
179185
this.#tabs.switchToTab( e.tabEdit );
180186
if( this.#draft ){
181
- this.editorContent = this.#draft.content;
187
+ this.editorContent = this.#draft.content || '';
182188
e.editor.addEventListener(
183189
'blur', ()=>{
184190
this.#draft.content = this.editorContent;
185191
this.#storeDraft();
186192
}
@@ -523,14 +529,21 @@
523529
const v = this.#e.status.value;
524530
if( this.#e.status.dataset.originalValue !== v ){
525531
fd.append( "status", v );
526532
}
527533
}
528
- if( 1 ){
529
- fd.append("dryrun", 1);
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
+ });
530539
}
531540
console.warn("Ready to submit",fd);
541
+ if( 0 ){
542
+ this.#isWaiting = false;
543
+ return;
544
+ }
532545
/*
533546
TODO: save it, set #isWaiting=false, then handle error or
534547
redirect to the post (if this is a new post) or, if replying
535548
inline, replace this object with a static rendering from the
536549
response.
@@ -538,19 +551,25 @@
538551
const resp = window.fetch(F.repoUrl('forumajax_save'), {
539552
method: 'POST',
540553
body: fd
541554
}).then(r=>r.json())
542555
.then(j=>{
543
- console.debug("submit response:",j);
556
+ console.debug("forum post submit response:",j);
544557
if( j.error ){
545558
throw new Error(j.error);
546559
}else if( j.message ){
547560
this.reportError(j.message);
548561
return;
549562
}
550
- this.#clearDraft();
551
- window.location = F.repoUrl('forumpost/'+j.uuid);
563
+ if( 1 ){
564
+ this.#clearDraft();
565
+ window.location = F.repoUrl('forumpost/'+j.uuid);
566
+ }else{
567
+ this.reportError(
568
+ "Saving worked but we're ignoring it and staying here."
569
+ );
570
+ }
552571
})
553572
.catch((e)=>this.reportError(e.message))
554573
.finally(()=>this.#isWaiting = false);
555574
}
556575
557576
--- src/fossil.page.forumpost.js
+++ src/fossil.page.forumpost.js
@@ -60,11 +60,15 @@
60 D.clearElement(wrapper);
61
62 if( !opt.inReplyTo ){
63 /* Title... */
64 e.titleBar = D.addClass(D.div(),'titlebar');
65 e.title = D.addClass(D.input('text'), 'title');
 
 
 
 
66 e.title.setAttribute('maxlength', 125);
67 e.titleBar.append(
68 D.append(D.span(), "Title:"),
69 e.title
70 );
@@ -71,40 +75,42 @@
71 if( this.#draft ){
72 e.title.addEventListener('blur', ()=>{
73 this.#draft.title = e.title.value;
74 this.#storeDraft();
75 });
76 e.title.value = opt.title || this.#draft.title;
77 }else if( opt.title ){
78 e.title.value = opt.title;
79 }
80 wrapper.append(e.titleBar);
81 }
82
83 { /* Mimetype... */
84 e.mimetype.wrapper = D.addClass(D.div(), 'mimetype-wrapper');
85 e.mimetype.select = D.addClass(D.select(), 'mimetype-select');
86 this.#toDisable.push(e.mimetype.select);
87 let i = 0;
88 D.option(e.mimetype.select, '', 'Markup format').disabled = true;
89 for(const [k,v] of Object.entries({
90 'text/x-markdown': 'Markdown',
91 'text/x-fossil-wiki': 'Fossil Wiki',
92 'text/plain': 'Plain text'
93 })) {
94 D.option(e.mimetype.select, k, v);
95 }
 
 
 
96 if( this.#draft ){
97 e.mimetype.select.value = opt.mimetype || this.#draft.mimetype;
98 e.mimetype.select.addEventListener('change',ev=>{
99 if( this.#draft.mimetype!==ev.target.value ){
100 this.#draft.mimetype = ev.target.value;
101 this.#storeDraft();
102 }
103 });
104 }
105 e.mimetype.wrapper.append(e.mimetype.select);
106 }
107
108 e.buttons = D.addClass(D.div(), 'buttons');
109 { /* Preview/submit buttons... */
110 e.button.preview = D.button("Preview", e=>this.#preview());
@@ -176,11 +182,11 @@
176 e.tabEdit.append(e.editor);
177 e.tabEdit.dataset.tabLabel = 'Edit';
178 this.#tabs.addTab( e.tabEdit );
179 this.#tabs.switchToTab( e.tabEdit );
180 if( this.#draft ){
181 this.editorContent = this.#draft.content;
182 e.editor.addEventListener(
183 'blur', ()=>{
184 this.#draft.content = this.editorContent;
185 this.#storeDraft();
186 }
@@ -523,14 +529,21 @@
523 const v = this.#e.status.value;
524 if( this.#e.status.dataset.originalValue !== v ){
525 fd.append( "status", v );
526 }
527 }
528 if( 1 ){
529 fd.append("dryrun", 1);
 
 
 
530 }
531 console.warn("Ready to submit",fd);
 
 
 
 
532 /*
533 TODO: save it, set #isWaiting=false, then handle error or
534 redirect to the post (if this is a new post) or, if replying
535 inline, replace this object with a static rendering from the
536 response.
@@ -538,19 +551,25 @@
538 const resp = window.fetch(F.repoUrl('forumajax_save'), {
539 method: 'POST',
540 body: fd
541 }).then(r=>r.json())
542 .then(j=>{
543 console.debug("submit response:",j);
544 if( j.error ){
545 throw new Error(j.error);
546 }else if( j.message ){
547 this.reportError(j.message);
548 return;
549 }
550 this.#clearDraft();
551 window.location = F.repoUrl('forumpost/'+j.uuid);
 
 
 
 
 
 
552 })
553 .catch((e)=>this.reportError(e.message))
554 .finally(()=>this.#isWaiting = false);
555 }
556
557
--- src/fossil.page.forumpost.js
+++ src/fossil.page.forumpost.js
@@ -60,11 +60,15 @@
60 D.clearElement(wrapper);
61
62 if( !opt.inReplyTo ){
63 /* Title... */
64 e.titleBar = D.addClass(D.div(),'titlebar');
65 e.title = D.attr(
66 D.addClass(D.input('text'), 'title'),
67 'placeholder',
68 'Thread title (required)'
69 );
70 e.title.setAttribute('maxlength', 125);
71 e.titleBar.append(
72 D.append(D.span(), "Title:"),
73 e.title
74 );
@@ -71,40 +75,42 @@
75 if( this.#draft ){
76 e.title.addEventListener('blur', ()=>{
77 this.#draft.title = e.title.value;
78 this.#storeDraft();
79 });
80 e.title.value = opt.title || this.#draft.title || '';
81 }else if( opt.title ){
82 e.title.value = opt.title;
83 }
84 wrapper.append(e.titleBar);
85 }
86
87 { /* Mimetype... */
88 e.mimetype.wrapper = D.addClass(D.div(), 'mimetype-wrapper');
89 const sel = e.mimetype.select = D.addClass(D.select(), 'mimetype-select');
90 this.#toDisable.push(sel);
91 let i = 0;
92 D.option(sel, '', 'Markup format').disabled = true;
93 for(const [k,v] of Object.entries({
94 'text/x-markdown': 'Markdown',
95 'text/x-fossil-wiki': 'Fossil Wiki',
96 'text/plain': 'Plain text'
97 })) {
98 D.option(sel, k, v);
99 }
100 sel.value = opt.mimetype
101 || this.#draft?.mimetype
102 || sel.options[1].value;
103 if( this.#draft ){
104 sel.addEventListener('change',ev=>{
 
105 if( this.#draft.mimetype!==ev.target.value ){
106 this.#draft.mimetype = ev.target.value;
107 this.#storeDraft();
108 }
109 });
110 }
111 e.mimetype.wrapper.append(sel);
112 }
113
114 e.buttons = D.addClass(D.div(), 'buttons');
115 { /* Preview/submit buttons... */
116 e.button.preview = D.button("Preview", e=>this.#preview());
@@ -176,11 +182,11 @@
182 e.tabEdit.append(e.editor);
183 e.tabEdit.dataset.tabLabel = 'Edit';
184 this.#tabs.addTab( e.tabEdit );
185 this.#tabs.switchToTab( e.tabEdit );
186 if( this.#draft ){
187 this.editorContent = this.#draft.content || '';
188 e.editor.addEventListener(
189 'blur', ()=>{
190 this.#draft.content = this.editorContent;
191 this.#storeDraft();
192 }
@@ -523,14 +529,21 @@
529 const v = this.#e.status.value;
530 if( this.#e.status.dataset.originalValue !== v ){
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 /*
546 TODO: save it, set #isWaiting=false, then handle error or
547 redirect to the post (if this is a new post) or, if replying
548 inline, replace this object with a static rendering from the
549 response.
@@ -538,19 +551,25 @@
551 const resp = window.fetch(F.repoUrl('forumajax_save'), {
552 method: 'POST',
553 body: fd
554 }).then(r=>r.json())
555 .then(j=>{
556 console.debug("forum post submit response:",j);
557 if( j.error ){
558 throw new Error(j.error);
559 }else if( j.message ){
560 this.reportError(j.message);
561 return;
562 }
563 if( 1 ){
564 this.#clearDraft();
565 window.location = F.repoUrl('forumpost/'+j.uuid);
566 }else{
567 this.reportError(
568 "Saving worked but we're ignoring it and staying here."
569 );
570 }
571 })
572 .catch((e)=>this.reportError(e.message))
573 .finally(()=>this.#isWaiting = false);
574 }
575
576

Keyboard Shortcuts

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