Fossil SCM

Add the status= query parameter to /timeline. For y=f or y=h (display of only forum posts and forum threads) the status= query parameter restricts the display to only those posts or threads that have the specified status. This is a no-op if forum-statuses are disabled (the default).

drh 2026-10-19 09:14 UTC trunk
Commit 556f21daf7c73e431ee75d15f2ec7fcd2a264f28667b8aae30937a07a1e0885e
2 files changed +31 +18
+31
--- src/forum.c
+++ src/forum.c
@@ -2559,10 +2559,41 @@
25592559
i = 0;
25602560
}
25612561
db_reset(&q);
25622562
sqlite3_result_int(context, i==pMData->iMatch);
25632563
}
2564
+
2565
+/*
2566
+** Register the status_match() SQL function so that it matches forum
2567
+** threads having status zStatus. Used to implement the status=NAME
2568
+** query parameter on /timeline.
2569
+**
2570
+** Return true if the status_match() function was registered and
2571
+** filtering should be applied. Return false (and register nothing)
2572
+** if zStatus is NULL, empty, or "*", or if the forum-statuses setting
2573
+** has fewer than two entries, or if there is no forum.
2574
+*/
2575
+int forum_status_match_setup(const char *zStatus){
2576
+ static ForumStatusMatch sFSM;
2577
+ const ForumStatusList *pFstat;
2578
+ if( zStatus==0 || zStatus[0]==0 || fossil_strcmp(zStatus,"*")==0 ){
2579
+ return 0;
2580
+ }
2581
+ if( !db_table_exists("repository","forumpost") ) return 0;
2582
+ pFstat = forum_statuses();
2583
+ if( pFstat->n<2 ) return 0;
2584
+ sFSM.pFses = pFstat;
2585
+ sFSM.eStatusTag = db_int(0, "SELECT tagid FROM tag WHERE tagname='status'");
2586
+ for(sFSM.iMatch=0; sFSM.iMatch<pFstat->n; sFSM.iMatch++){
2587
+ if( 0==fossil_strcmp(zStatus, pFstat->aStatus[sFSM.iMatch].zValue) ){
2588
+ break;
2589
+ }
2590
+ }
2591
+ sqlite3_create_function(g.db,"status_match",1,SQLITE_UTF8,(void*)&sFSM,
2592
+ forum_status_match, 0, 0);
2593
+ return 1;
2594
+}
25642595
25652596
/*
25662597
** WEBPAGE: forummain
25672598
** WEBPAGE: forum
25682599
**
25692600
--- src/forum.c
+++ src/forum.c
@@ -2559,10 +2559,41 @@
2559 i = 0;
2560 }
2561 db_reset(&q);
2562 sqlite3_result_int(context, i==pMData->iMatch);
2563 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2564
2565 /*
2566 ** WEBPAGE: forummain
2567 ** WEBPAGE: forum
2568 **
2569
--- src/forum.c
+++ src/forum.c
@@ -2559,10 +2559,41 @@
2559 i = 0;
2560 }
2561 db_reset(&q);
2562 sqlite3_result_int(context, i==pMData->iMatch);
2563 }
2564
2565 /*
2566 ** Register the status_match() SQL function so that it matches forum
2567 ** threads having status zStatus. Used to implement the status=NAME
2568 ** query parameter on /timeline.
2569 **
2570 ** Return true if the status_match() function was registered and
2571 ** filtering should be applied. Return false (and register nothing)
2572 ** if zStatus is NULL, empty, or "*", or if the forum-statuses setting
2573 ** has fewer than two entries, or if there is no forum.
2574 */
2575 int forum_status_match_setup(const char *zStatus){
2576 static ForumStatusMatch sFSM;
2577 const ForumStatusList *pFstat;
2578 if( zStatus==0 || zStatus[0]==0 || fossil_strcmp(zStatus,"*")==0 ){
2579 return 0;
2580 }
2581 if( !db_table_exists("repository","forumpost") ) return 0;
2582 pFstat = forum_statuses();
2583 if( pFstat->n<2 ) return 0;
2584 sFSM.pFses = pFstat;
2585 sFSM.eStatusTag = db_int(0, "SELECT tagid FROM tag WHERE tagname='status'");
2586 for(sFSM.iMatch=0; sFSM.iMatch<pFstat->n; sFSM.iMatch++){
2587 if( 0==fossil_strcmp(zStatus, pFstat->aStatus[sFSM.iMatch].zValue) ){
2588 break;
2589 }
2590 }
2591 sqlite3_create_function(g.db,"status_match",1,SQLITE_UTF8,(void*)&sFSM,
2592 forum_status_match, 0, 0);
2593 return 1;
2594 }
2595
2596 /*
2597 ** WEBPAGE: forummain
2598 ** WEBPAGE: forum
2599 **
2600
--- src/timeline.c
+++ src/timeline.c
@@ -1747,10 +1747,13 @@
17471747
** nowiki Do not show wiki associated with branch or tag
17481748
** ms=MATCHSTYLE Set tag name match algorithm. One of "exact", "glob",
17491749
** "like", or "regexp".
17501750
** u=USER Only show items associated with USER
17511751
** y=TYPE 'ci', 'w', 't', 'n', 'e', 'f', 'h', or 'all'.
1752
+** status=NAME With y=f or y=h, show only forum posts or threads
1753
+** whose thread has status NAME. See the
1754
+** forum-statuses setting.
17521755
** ss=VIEWSTYLE c: "Compact", v: "Verbose", m: "Modern", j: "Columnar",
17531756
** x: "Classic".
17541757
** advm Use the "Advanced" or "Busy" menu design.
17551758
** ng No Graph.
17561759
** ncp Omit cherrypick merges
@@ -2626,10 +2629,11 @@
26262629
}
26272630
}else{
26282631
/* Otherwise, a timeline based on a span of time */
26292632
int n;
26302633
const char *zEType = "event";
2634
+ const char *zStatus = 0; /* status=NAME filter for forum threads */
26312635
char *zDate;
26322636
Blob cond;
26332637
blob_zero(&cond);
26342638
tmFlags |= TIMELINE_FILLGAPS;
26352639
if( zChng && *zChng ){
@@ -3039,10 +3043,18 @@
30393043
}else if( zType[0]=='f' ){
30403044
zEType = "forum post";
30413045
}else if( zType[0]=='h' ){
30423046
zEType = "new forum thread";
30433047
}
3048
+ if( (zType[0]=='f' || zType[0]=='h')
3049
+ && forum_status_match_setup(P("status"))
3050
+ ){
3051
+ zStatus = P("status");
3052
+ blob_append_sql(&cond,
3053
+ " AND status_match((SELECT froot FROM forumpost"
3054
+ " WHERE fpid=event.objid))");
3055
+ }
30443056
}
30453057
if( zUser ){
30463058
int n = db_int(0,"SELECT count(*) FROM event"
30473059
" WHERE user=%Q OR euser=%Q", zUser, zUser);
30483060
if( n<=nEntry ){
@@ -3134,10 +3146,13 @@
31343146
}else if( zBefore==0 && zCirca==0 && n>=nEntry && nEntry>0 ){
31353147
blob_appendf(&desc, "%d most recent %s%s", n, zEType, zPlural);
31363148
}else{
31373149
blob_appendf(&desc, "%d %s%s", n, zEType, zPlural);
31383150
}
3151
+ if( zStatus ){
3152
+ blob_appendf(&desc, " with status %h", zStatus);
3153
+ }
31393154
if( zUses ){
31403155
char *zFilenames = names_of_file(zUses);
31413156
blob_appendf(&desc, " using file %s version %z%S</a>", zFilenames,
31423157
href("%R/artifact/%!S",zUses), zUses);
31433158
tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS;
@@ -3252,10 +3267,13 @@
32523267
style_submenu_entry("t", "Tag Filter:", -8, 0);
32533268
style_submenu_multichoice("ms", count(azMatchStyles)/2,azMatchStyles,0);
32543269
}
32553270
}
32563271
blob_zero(&cond);
3272
+ if( zStatus ){
3273
+ sqlite3_create_function(g.db,"status_match",1,SQLITE_UTF8,0,0,0,0);
3274
+ }
32573275
}
32583276
if( showSql ){
32593277
db_append_dml_to_blob(0);
32603278
@ <pre>%h(blob_str(&allSql))</pre>
32613279
blob_reset(&allSql);
32623280
--- src/timeline.c
+++ src/timeline.c
@@ -1747,10 +1747,13 @@
1747 ** nowiki Do not show wiki associated with branch or tag
1748 ** ms=MATCHSTYLE Set tag name match algorithm. One of "exact", "glob",
1749 ** "like", or "regexp".
1750 ** u=USER Only show items associated with USER
1751 ** y=TYPE 'ci', 'w', 't', 'n', 'e', 'f', 'h', or 'all'.
 
 
 
1752 ** ss=VIEWSTYLE c: "Compact", v: "Verbose", m: "Modern", j: "Columnar",
1753 ** x: "Classic".
1754 ** advm Use the "Advanced" or "Busy" menu design.
1755 ** ng No Graph.
1756 ** ncp Omit cherrypick merges
@@ -2626,10 +2629,11 @@
2626 }
2627 }else{
2628 /* Otherwise, a timeline based on a span of time */
2629 int n;
2630 const char *zEType = "event";
 
2631 char *zDate;
2632 Blob cond;
2633 blob_zero(&cond);
2634 tmFlags |= TIMELINE_FILLGAPS;
2635 if( zChng && *zChng ){
@@ -3039,10 +3043,18 @@
3039 }else if( zType[0]=='f' ){
3040 zEType = "forum post";
3041 }else if( zType[0]=='h' ){
3042 zEType = "new forum thread";
3043 }
 
 
 
 
 
 
 
 
3044 }
3045 if( zUser ){
3046 int n = db_int(0,"SELECT count(*) FROM event"
3047 " WHERE user=%Q OR euser=%Q", zUser, zUser);
3048 if( n<=nEntry ){
@@ -3134,10 +3146,13 @@
3134 }else if( zBefore==0 && zCirca==0 && n>=nEntry && nEntry>0 ){
3135 blob_appendf(&desc, "%d most recent %s%s", n, zEType, zPlural);
3136 }else{
3137 blob_appendf(&desc, "%d %s%s", n, zEType, zPlural);
3138 }
 
 
 
3139 if( zUses ){
3140 char *zFilenames = names_of_file(zUses);
3141 blob_appendf(&desc, " using file %s version %z%S</a>", zFilenames,
3142 href("%R/artifact/%!S",zUses), zUses);
3143 tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS;
@@ -3252,10 +3267,13 @@
3252 style_submenu_entry("t", "Tag Filter:", -8, 0);
3253 style_submenu_multichoice("ms", count(azMatchStyles)/2,azMatchStyles,0);
3254 }
3255 }
3256 blob_zero(&cond);
 
 
 
3257 }
3258 if( showSql ){
3259 db_append_dml_to_blob(0);
3260 @ <pre>%h(blob_str(&allSql))</pre>
3261 blob_reset(&allSql);
3262
--- src/timeline.c
+++ src/timeline.c
@@ -1747,10 +1747,13 @@
1747 ** nowiki Do not show wiki associated with branch or tag
1748 ** ms=MATCHSTYLE Set tag name match algorithm. One of "exact", "glob",
1749 ** "like", or "regexp".
1750 ** u=USER Only show items associated with USER
1751 ** y=TYPE 'ci', 'w', 't', 'n', 'e', 'f', 'h', or 'all'.
1752 ** status=NAME With y=f or y=h, show only forum posts or threads
1753 ** whose thread has status NAME. See the
1754 ** forum-statuses setting.
1755 ** ss=VIEWSTYLE c: "Compact", v: "Verbose", m: "Modern", j: "Columnar",
1756 ** x: "Classic".
1757 ** advm Use the "Advanced" or "Busy" menu design.
1758 ** ng No Graph.
1759 ** ncp Omit cherrypick merges
@@ -2626,10 +2629,11 @@
2629 }
2630 }else{
2631 /* Otherwise, a timeline based on a span of time */
2632 int n;
2633 const char *zEType = "event";
2634 const char *zStatus = 0; /* status=NAME filter for forum threads */
2635 char *zDate;
2636 Blob cond;
2637 blob_zero(&cond);
2638 tmFlags |= TIMELINE_FILLGAPS;
2639 if( zChng && *zChng ){
@@ -3039,10 +3043,18 @@
3043 }else if( zType[0]=='f' ){
3044 zEType = "forum post";
3045 }else if( zType[0]=='h' ){
3046 zEType = "new forum thread";
3047 }
3048 if( (zType[0]=='f' || zType[0]=='h')
3049 && forum_status_match_setup(P("status"))
3050 ){
3051 zStatus = P("status");
3052 blob_append_sql(&cond,
3053 " AND status_match((SELECT froot FROM forumpost"
3054 " WHERE fpid=event.objid))");
3055 }
3056 }
3057 if( zUser ){
3058 int n = db_int(0,"SELECT count(*) FROM event"
3059 " WHERE user=%Q OR euser=%Q", zUser, zUser);
3060 if( n<=nEntry ){
@@ -3134,10 +3146,13 @@
3146 }else if( zBefore==0 && zCirca==0 && n>=nEntry && nEntry>0 ){
3147 blob_appendf(&desc, "%d most recent %s%s", n, zEType, zPlural);
3148 }else{
3149 blob_appendf(&desc, "%d %s%s", n, zEType, zPlural);
3150 }
3151 if( zStatus ){
3152 blob_appendf(&desc, " with status %h", zStatus);
3153 }
3154 if( zUses ){
3155 char *zFilenames = names_of_file(zUses);
3156 blob_appendf(&desc, " using file %s version %z%S</a>", zFilenames,
3157 href("%R/artifact/%!S",zUses), zUses);
3158 tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS;
@@ -3252,10 +3267,13 @@
3267 style_submenu_entry("t", "Tag Filter:", -8, 0);
3268 style_submenu_multichoice("ms", count(azMatchStyles)/2,azMatchStyles,0);
3269 }
3270 }
3271 blob_zero(&cond);
3272 if( zStatus ){
3273 sqlite3_create_function(g.db,"status_match",1,SQLITE_UTF8,0,0,0,0);
3274 }
3275 }
3276 if( showSql ){
3277 db_append_dml_to_blob(0);
3278 @ <pre>%h(blob_str(&allSql))</pre>
3279 blob_reset(&allSql);
3280

Keyboard Shortcuts

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