Fossil SCM

Baby steps in refactoring the /forum list query towards better support filtering by status.

stephan 2026-05-26 16:03 UTC forum-statuses
Commit 32501ce53181ce733cdeb43e4a324ec841504e939c4d580938ec7e782d5d0547
3 files changed +11 -1 +51 -30 +19 -3
+11 -1
--- src/default.css
+++ src/default.css
@@ -1114,18 +1114,28 @@
11141114
div.setup_forum-column {
11151115
display: flex;
11161116
flex-direction: column;
11171117
}
11181118
1119
-body.forum div.forumPosts table tr.pinned > td.subject:before {
1119
+body.cpage-forum div.forumPosts table tr.pinned > td.subject:before {
11201120
content: "📌 "/*this space works around an unsightly FF quirk*/;
11211121
font-size: 120%;
11221122
}
11231123
11241124
body.forum span.forum-status-selection {
11251125
white-space: nowrap;
11261126
}
1127
+
1128
+body.cpage-forum div.forumPosts tr[data-status] td.status {
1129
+ /* Add a gap before the "X posts spanning Y time" labels,
1130
+ ** which sometimes wrap and look odd without this gap. */
1131
+ padding-left: 1em;
1132
+}
1133
+body.cpage-forum div.forumPosts tr[data-status="open"] {
1134
+}
1135
+body.cpage-forum div.forumPosts tr[data-status="resolved"] {
1136
+}
11271137
11281138
body.cpage-setup_forum > .content table {
11291139
margin-bottom: 1em;
11301140
}
11311141
body.cpage-setup_forum > .content table.bordered {
11321142
--- src/default.css
+++ src/default.css
@@ -1114,18 +1114,28 @@
1114 div.setup_forum-column {
1115 display: flex;
1116 flex-direction: column;
1117 }
1118
1119 body.forum div.forumPosts table tr.pinned > td.subject:before {
1120 content: "📌 "/*this space works around an unsightly FF quirk*/;
1121 font-size: 120%;
1122 }
1123
1124 body.forum span.forum-status-selection {
1125 white-space: nowrap;
1126 }
 
 
 
 
 
 
 
 
 
 
1127
1128 body.cpage-setup_forum > .content table {
1129 margin-bottom: 1em;
1130 }
1131 body.cpage-setup_forum > .content table.bordered {
1132
--- src/default.css
+++ src/default.css
@@ -1114,18 +1114,28 @@
1114 div.setup_forum-column {
1115 display: flex;
1116 flex-direction: column;
1117 }
1118
1119 body.cpage-forum div.forumPosts table tr.pinned > td.subject:before {
1120 content: "📌 "/*this space works around an unsightly FF quirk*/;
1121 font-size: 120%;
1122 }
1123
1124 body.forum span.forum-status-selection {
1125 white-space: nowrap;
1126 }
1127
1128 body.cpage-forum div.forumPosts tr[data-status] td.status {
1129 /* Add a gap before the "X posts spanning Y time" labels,
1130 ** which sometimes wrap and look odd without this gap. */
1131 padding-left: 1em;
1132 }
1133 body.cpage-forum div.forumPosts tr[data-status="open"] {
1134 }
1135 body.cpage-forum div.forumPosts tr[data-status="resolved"] {
1136 }
1137
1138 body.cpage-setup_forum > .content table {
1139 margin-bottom: 1em;
1140 }
1141 body.cpage-setup_forum > .content table.bordered {
1142
+51 -30
--- src/forum.c
+++ src/forum.c
@@ -2437,10 +2437,11 @@
24372437
void forum_main_page(void){
24382438
Stmt q;
24392439
int iLimit = 0, iOfst, iCnt;
24402440
int srchFlags;
24412441
const int isSearch = P("s")!=0;
2442
+ const char *zStatus = P("status");
24422443
char const *zLimit = 0;
24432444
24442445
login_check_credentials();
24452446
srchFlags = search_restrict(SRCH_FORUM);
24462447
if( !g.perm.RdForum ){
@@ -2488,42 +2489,55 @@
24882489
iOfst = atoi(PD("x","0"));
24892490
iCnt = 0;
24902491
if( db_table_exists("repository","forumpost") ){
24912492
const int bHasStatus = forum_statuses()->n>1;
24922493
db_prepare(&q,
2493
- "WITH thread(age,duration,cnt,root,last,pinned,status) AS ("
2494
+ "WITH "
2495
+ "root(id,pinned,status,statlbl) AS ("
2496
+ /* FIXME: the status/statlbl columns are running the same query
2497
+ ** to get two adjacent columns. Certainly this query can be
2498
+ ** restructured to avoid the duplicated lookup? */
2499
+ " SELECT froot id,"
2500
+ " (SELECT 1 FROM tagxref ref, tag t"
2501
+ " WHERE ref.rid=x.fpid AND ref.tagtype>0"
2502
+ " AND ref.tagid=t.tagid"
2503
+ " AND t.tagname='pinned') pinned,"
2504
+ " CASE WHEN %d /*bHasStatus*/ THEN coalesce("
2505
+ " (SELECT ref.value FROM tagxref ref, tag t, forumstatus fs"
2506
+ " WHERE ref.rid=x.froot AND ref.tagtype>0"
2507
+ " AND ref.tagid=t.tagid"
2508
+ " AND t.tagname='status'"
2509
+ " AND ref.value=fs.value"
2510
+ " ORDER BY ref.mtime desc"
2511
+ " ),"
2512
+ " (SELECT value FROM forumstatus WHERE ord=1)"
2513
+ " ) ELSE NULL END status,"
2514
+ " CASE WHEN %d /*bHasStatus*/ THEN coalesce("
2515
+ " (SELECT fs.label FROM tagxref ref, tag t, forumstatus fs"
2516
+ " WHERE ref.rid=x.froot AND ref.tagtype>0"
2517
+ " AND ref.tagid=t.tagid"
2518
+ " AND t.tagname='status'"
2519
+ " AND ref.value=fs.value"
2520
+ " ORDER BY ref.mtime desc"
2521
+ " ),"
2522
+ " (SELECT label FROM forumstatus WHERE ord=1)"
2523
+ " ) ELSE NULL END statlbl"
2524
+ " FROM forumpost x WHERE firt IS NULL"
2525
+ "),"
2526
+ " thread(age,duration,cnt,root,last,pinned,status,statlbl)"
2527
+ " AS ("
24942528
" SELECT"
24952529
" julianday('now') - max(fmtime),"
24962530
" max(fmtime) - min(fmtime),"
24972531
" sum(fprev IS NULL),"
2498
- " froot,"
2532
+ " root.id,"
24992533
" (SELECT fpid FROM forumpost AS y"
2500
- " WHERE y.froot=x.froot %s"
2534
+ " WHERE y.froot=root.id %s"
25012535
" ORDER BY y.fmtime DESC LIMIT 1),"
2502
- " (firt IS NULL AND"
2503
- " (SELECT 1 FROM tagxref ref, tag t"
2504
- " WHERE ref.rid=x.fpid AND ref.tagtype>0"
2505
- " AND ref.tagid=t.tagid"
2506
- " AND t.tagname='pinned')),"
2507
-#if 0
2508
- " (SELECT ref.value FROM tagxref ref, tag t"
2509
- " WHERE ref.rid=x.froot AND ref.tagtype>0"
2510
- " AND ref.tagid=t.tagid"
2511
- " AND t.tagname='status'"
2512
- " UNION ALL"
2513
- " SELECT value FROM forumstatus WHERE ord=1)"
2514
-#else
2515
- " (SELECT fs.label FROM tagxref ref, tag t, forumstatus fs"
2516
- " WHERE ref.rid=x.froot AND ref.tagtype>0"
2517
- " AND ref.tagid=t.tagid"
2518
- " AND t.tagname='status'"
2519
- " AND fs.value=ref.value"
2520
- " UNION ALL"
2521
- " SELECT label FROM forumstatus WHERE ord=1)"
2522
-#endif
2523
- " FROM forumpost AS x"
2524
- " WHERE %s"
2536
+ " root.pinned, root.status, root.statlbl"
2537
+ " FROM forumpost, root"
2538
+ " WHERE root.id=froot AND %s"
25252539
" GROUP BY froot"
25262540
" ORDER BY 6 DESC, 1 LIMIT %d OFFSET %d"
25272541
")"
25282542
"SELECT"
25292543
" thread.age," /* 0 */
@@ -2531,15 +2545,17 @@
25312545
" thread.cnt," /* 2 */
25322546
" blob.uuid," /* 3 */
25332547
" substr(event.comment,instr(event.comment,':')+1)," /* 4 */
25342548
" thread.last," /* 5 */
25352549
" thread.pinned," /* 6 */
2536
- " thread.status" /* 7 */
2550
+ " thread.status," /* 7 */
2551
+ " thread.statlbl" /* 8 */
25372552
" FROM thread, blob, event"
25382553
" WHERE blob.rid=thread.last"
25392554
" AND event.objid=thread.last"
25402555
" ORDER BY 7 DESC, 1;",
2556
+ bHasStatus, bHasStatus,
25412557
g.perm.ModForum ? "" : "AND y.fpid NOT IN private" /*safe-for-%s*/,
25422558
g.perm.ModForum ? "true" : "fpid NOT IN private" /*safe-for-%s*/,
25432559
iLimit+1, iOfst
25442560
);
25452561
while( db_step(&q)==SQLITE_ROW ){
@@ -2547,10 +2563,11 @@
25472563
int nMsg = db_column_int(&q, 2);
25482564
int bPinned = db_column_int(&q, 6);
25492565
const char *zUuid = db_column_text(&q, 3);
25502566
const char *zTitle = db_column_text(&q, 4);
25512567
const char *zStatus = bHasStatus ? db_column_text(&q, 7) : NULL;
2568
+ const char *zStatusLbl = bHasStatus ? db_column_text(&q, 8) : NULL;
25522569
if( iCnt==0 ){
25532570
if( iOfst>0 ){
25542571
@ <h1>Threads at least %s(zAge) old</h1>
25552572
}else{
25562573
@ <h1>Most recent threads</h1>
@@ -2573,11 +2590,15 @@
25732590
@ %z(href("%R/forum?x=%d&n=%d",iOfst+iLimit,iLimit))\
25742591
@ &darr; Older...</a></td></tr>
25752592
fossil_free(zAge);
25762593
break;
25772594
}
2578
- @ <tr%s(bPinned ? " class='pinned'" : "")><td>%h(zAge) ago</td>
2595
+ @ <tr%s(bPinned ? " class='pinned'" : "")
2596
+ if( bHasStatus ){
2597
+ @ data-status="%h(zStatus)"\
2598
+ }
2599
+ @ ><td>%h(zAge) ago</td>
25792600
@ <td class='subject'>%z(href("%R/forumpost/%S",zUuid))%h(zTitle)</a>\
25802601
@ </td><td>\
25812602
if( g.perm.ModForum && moderation_pending(db_column_int(&q,5)) ){
25822603
@ <span class="modpending">\
25832604
@ Awaiting Moderator Approval</span><br>
@@ -2588,12 +2609,12 @@
25882609
char *zDuration = human_readable_age(db_column_double(&q,1));
25892610
@ %d(nMsg) posts spanning %h(zDuration)\
25902611
fossil_free(zDuration);
25912612
}
25922613
@ </td>\
2593
- if( zStatus ){
2594
- @ <td>%h(zStatus)</td>\
2614
+ if( bHasStatus ){
2615
+ @ <td class='status'>%h(zStatusLbl)</td>\
25952616
}
25962617
@</tr>
25972618
fossil_free(zAge);
25982619
}
25992620
db_finalize(&q);
26002621
--- src/forum.c
+++ src/forum.c
@@ -2437,10 +2437,11 @@
2437 void forum_main_page(void){
2438 Stmt q;
2439 int iLimit = 0, iOfst, iCnt;
2440 int srchFlags;
2441 const int isSearch = P("s")!=0;
 
2442 char const *zLimit = 0;
2443
2444 login_check_credentials();
2445 srchFlags = search_restrict(SRCH_FORUM);
2446 if( !g.perm.RdForum ){
@@ -2488,42 +2489,55 @@
2488 iOfst = atoi(PD("x","0"));
2489 iCnt = 0;
2490 if( db_table_exists("repository","forumpost") ){
2491 const int bHasStatus = forum_statuses()->n>1;
2492 db_prepare(&q,
2493 "WITH thread(age,duration,cnt,root,last,pinned,status) AS ("
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2494 " SELECT"
2495 " julianday('now') - max(fmtime),"
2496 " max(fmtime) - min(fmtime),"
2497 " sum(fprev IS NULL),"
2498 " froot,"
2499 " (SELECT fpid FROM forumpost AS y"
2500 " WHERE y.froot=x.froot %s"
2501 " ORDER BY y.fmtime DESC LIMIT 1),"
2502 " (firt IS NULL AND"
2503 " (SELECT 1 FROM tagxref ref, tag t"
2504 " WHERE ref.rid=x.fpid AND ref.tagtype>0"
2505 " AND ref.tagid=t.tagid"
2506 " AND t.tagname='pinned')),"
2507 #if 0
2508 " (SELECT ref.value FROM tagxref ref, tag t"
2509 " WHERE ref.rid=x.froot AND ref.tagtype>0"
2510 " AND ref.tagid=t.tagid"
2511 " AND t.tagname='status'"
2512 " UNION ALL"
2513 " SELECT value FROM forumstatus WHERE ord=1)"
2514 #else
2515 " (SELECT fs.label FROM tagxref ref, tag t, forumstatus fs"
2516 " WHERE ref.rid=x.froot AND ref.tagtype>0"
2517 " AND ref.tagid=t.tagid"
2518 " AND t.tagname='status'"
2519 " AND fs.value=ref.value"
2520 " UNION ALL"
2521 " SELECT label FROM forumstatus WHERE ord=1)"
2522 #endif
2523 " FROM forumpost AS x"
2524 " WHERE %s"
2525 " GROUP BY froot"
2526 " ORDER BY 6 DESC, 1 LIMIT %d OFFSET %d"
2527 ")"
2528 "SELECT"
2529 " thread.age," /* 0 */
@@ -2531,15 +2545,17 @@
2531 " thread.cnt," /* 2 */
2532 " blob.uuid," /* 3 */
2533 " substr(event.comment,instr(event.comment,':')+1)," /* 4 */
2534 " thread.last," /* 5 */
2535 " thread.pinned," /* 6 */
2536 " thread.status" /* 7 */
 
2537 " FROM thread, blob, event"
2538 " WHERE blob.rid=thread.last"
2539 " AND event.objid=thread.last"
2540 " ORDER BY 7 DESC, 1;",
 
2541 g.perm.ModForum ? "" : "AND y.fpid NOT IN private" /*safe-for-%s*/,
2542 g.perm.ModForum ? "true" : "fpid NOT IN private" /*safe-for-%s*/,
2543 iLimit+1, iOfst
2544 );
2545 while( db_step(&q)==SQLITE_ROW ){
@@ -2547,10 +2563,11 @@
2547 int nMsg = db_column_int(&q, 2);
2548 int bPinned = db_column_int(&q, 6);
2549 const char *zUuid = db_column_text(&q, 3);
2550 const char *zTitle = db_column_text(&q, 4);
2551 const char *zStatus = bHasStatus ? db_column_text(&q, 7) : NULL;
 
2552 if( iCnt==0 ){
2553 if( iOfst>0 ){
2554 @ <h1>Threads at least %s(zAge) old</h1>
2555 }else{
2556 @ <h1>Most recent threads</h1>
@@ -2573,11 +2590,15 @@
2573 @ %z(href("%R/forum?x=%d&n=%d",iOfst+iLimit,iLimit))\
2574 @ &darr; Older...</a></td></tr>
2575 fossil_free(zAge);
2576 break;
2577 }
2578 @ <tr%s(bPinned ? " class='pinned'" : "")><td>%h(zAge) ago</td>
 
 
 
 
2579 @ <td class='subject'>%z(href("%R/forumpost/%S",zUuid))%h(zTitle)</a>\
2580 @ </td><td>\
2581 if( g.perm.ModForum && moderation_pending(db_column_int(&q,5)) ){
2582 @ <span class="modpending">\
2583 @ Awaiting Moderator Approval</span><br>
@@ -2588,12 +2609,12 @@
2588 char *zDuration = human_readable_age(db_column_double(&q,1));
2589 @ %d(nMsg) posts spanning %h(zDuration)\
2590 fossil_free(zDuration);
2591 }
2592 @ </td>\
2593 if( zStatus ){
2594 @ <td>%h(zStatus)</td>\
2595 }
2596 @</tr>
2597 fossil_free(zAge);
2598 }
2599 db_finalize(&q);
2600
--- src/forum.c
+++ src/forum.c
@@ -2437,10 +2437,11 @@
2437 void forum_main_page(void){
2438 Stmt q;
2439 int iLimit = 0, iOfst, iCnt;
2440 int srchFlags;
2441 const int isSearch = P("s")!=0;
2442 const char *zStatus = P("status");
2443 char const *zLimit = 0;
2444
2445 login_check_credentials();
2446 srchFlags = search_restrict(SRCH_FORUM);
2447 if( !g.perm.RdForum ){
@@ -2488,42 +2489,55 @@
2489 iOfst = atoi(PD("x","0"));
2490 iCnt = 0;
2491 if( db_table_exists("repository","forumpost") ){
2492 const int bHasStatus = forum_statuses()->n>1;
2493 db_prepare(&q,
2494 "WITH "
2495 "root(id,pinned,status,statlbl) AS ("
2496 /* FIXME: the status/statlbl columns are running the same query
2497 ** to get two adjacent columns. Certainly this query can be
2498 ** restructured to avoid the duplicated lookup? */
2499 " SELECT froot id,"
2500 " (SELECT 1 FROM tagxref ref, tag t"
2501 " WHERE ref.rid=x.fpid AND ref.tagtype>0"
2502 " AND ref.tagid=t.tagid"
2503 " AND t.tagname='pinned') pinned,"
2504 " CASE WHEN %d /*bHasStatus*/ THEN coalesce("
2505 " (SELECT ref.value FROM tagxref ref, tag t, forumstatus fs"
2506 " WHERE ref.rid=x.froot AND ref.tagtype>0"
2507 " AND ref.tagid=t.tagid"
2508 " AND t.tagname='status'"
2509 " AND ref.value=fs.value"
2510 " ORDER BY ref.mtime desc"
2511 " ),"
2512 " (SELECT value FROM forumstatus WHERE ord=1)"
2513 " ) ELSE NULL END status,"
2514 " CASE WHEN %d /*bHasStatus*/ THEN coalesce("
2515 " (SELECT fs.label FROM tagxref ref, tag t, forumstatus fs"
2516 " WHERE ref.rid=x.froot AND ref.tagtype>0"
2517 " AND ref.tagid=t.tagid"
2518 " AND t.tagname='status'"
2519 " AND ref.value=fs.value"
2520 " ORDER BY ref.mtime desc"
2521 " ),"
2522 " (SELECT label FROM forumstatus WHERE ord=1)"
2523 " ) ELSE NULL END statlbl"
2524 " FROM forumpost x WHERE firt IS NULL"
2525 "),"
2526 " thread(age,duration,cnt,root,last,pinned,status,statlbl)"
2527 " AS ("
2528 " SELECT"
2529 " julianday('now') - max(fmtime),"
2530 " max(fmtime) - min(fmtime),"
2531 " sum(fprev IS NULL),"
2532 " root.id,"
2533 " (SELECT fpid FROM forumpost AS y"
2534 " WHERE y.froot=root.id %s"
2535 " ORDER BY y.fmtime DESC LIMIT 1),"
2536 " root.pinned, root.status, root.statlbl"
2537 " FROM forumpost, root"
2538 " WHERE root.id=froot AND %s"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2539 " GROUP BY froot"
2540 " ORDER BY 6 DESC, 1 LIMIT %d OFFSET %d"
2541 ")"
2542 "SELECT"
2543 " thread.age," /* 0 */
@@ -2531,15 +2545,17 @@
2545 " thread.cnt," /* 2 */
2546 " blob.uuid," /* 3 */
2547 " substr(event.comment,instr(event.comment,':')+1)," /* 4 */
2548 " thread.last," /* 5 */
2549 " thread.pinned," /* 6 */
2550 " thread.status," /* 7 */
2551 " thread.statlbl" /* 8 */
2552 " FROM thread, blob, event"
2553 " WHERE blob.rid=thread.last"
2554 " AND event.objid=thread.last"
2555 " ORDER BY 7 DESC, 1;",
2556 bHasStatus, bHasStatus,
2557 g.perm.ModForum ? "" : "AND y.fpid NOT IN private" /*safe-for-%s*/,
2558 g.perm.ModForum ? "true" : "fpid NOT IN private" /*safe-for-%s*/,
2559 iLimit+1, iOfst
2560 );
2561 while( db_step(&q)==SQLITE_ROW ){
@@ -2547,10 +2563,11 @@
2563 int nMsg = db_column_int(&q, 2);
2564 int bPinned = db_column_int(&q, 6);
2565 const char *zUuid = db_column_text(&q, 3);
2566 const char *zTitle = db_column_text(&q, 4);
2567 const char *zStatus = bHasStatus ? db_column_text(&q, 7) : NULL;
2568 const char *zStatusLbl = bHasStatus ? db_column_text(&q, 8) : NULL;
2569 if( iCnt==0 ){
2570 if( iOfst>0 ){
2571 @ <h1>Threads at least %s(zAge) old</h1>
2572 }else{
2573 @ <h1>Most recent threads</h1>
@@ -2573,11 +2590,15 @@
2590 @ %z(href("%R/forum?x=%d&n=%d",iOfst+iLimit,iLimit))\
2591 @ &darr; Older...</a></td></tr>
2592 fossil_free(zAge);
2593 break;
2594 }
2595 @ <tr%s(bPinned ? " class='pinned'" : "")
2596 if( bHasStatus ){
2597 @ data-status="%h(zStatus)"\
2598 }
2599 @ ><td>%h(zAge) ago</td>
2600 @ <td class='subject'>%z(href("%R/forumpost/%S",zUuid))%h(zTitle)</a>\
2601 @ </td><td>\
2602 if( g.perm.ModForum && moderation_pending(db_column_int(&q,5)) ){
2603 @ <span class="modpending">\
2604 @ Awaiting Moderator Approval</span><br>
@@ -2588,12 +2609,12 @@
2609 char *zDuration = human_readable_age(db_column_double(&q,1));
2610 @ %d(nMsg) posts spanning %h(zDuration)\
2611 fossil_free(zDuration);
2612 }
2613 @ </td>\
2614 if( bHasStatus ){
2615 @ <td class='status'>%h(zStatusLbl)</td>\
2616 }
2617 @</tr>
2618 fossil_free(zAge);
2619 }
2620 db_finalize(&q);
2621
+19 -3
--- www/forum.wiki
+++ www/forum.wiki
@@ -428,16 +428,27 @@
428428
429429
The list defines the legal statuses for forum posts and syncs with
430430
other project-level settings. All "label" and "value" members must be
431431
unique within the scope of that list.
432432
433
-If the list has two or more entries then the forum behavior changes in
434
-the following ways:
433
+The "value" values must be legal for use as HTML "dataset" members so
434
+that they can be used for with CSS selectors to apply per-status
435
+styling.
436
+
437
+If the list is valid JSON5 and has two or more entries then the forum
438
+behavior changes in the following ways:
435439
440
+ * Each TR element of the main <tt>/forum</tt> view table gets a
441
+ <tt>data-status="X"</tt> member, where "X" is the value part from
442
+ the associated status. This can be used to style the statuses
443
+ in the site skin using CSS selectors such as<br>
444
+ <tt>body.cpage-forum div.forumPosts tr&#91;data-status="open"]</tt><br>
445
+ Because there is no default status list, no predefined styles
446
+ are applied.
447
+ * The forum list shows the "label" value of the current status.
436448
* The root of each thread, when selected, gets a new UI control for
437449
displaying or editing the status, depending on permissions.
438
- * The forum list shows the "label" value of the current status.
439450
440451
A list with only a single entry is treated as empty, the justification
441452
being that if there is only one option then the UI does not need to be
442453
cluttered with it.
443454
@@ -457,5 +468,10 @@
457468
UI ensures that the tag is applied to the proper post, even when
458469
it is rendering a newer version.
459470
460471
The UI will eventually offer the ability to filter the post list by
461472
status.
473
+
474
+Caveat: a "closed" status is not recommended because it's easy to confuse with
475
+the <a href='#close-post'>"closed" tag feature</a>, which behaves considerably
476
+differently and predates that "status" tag support by about three years. The
477
+"closed" semantics cannot be trivially consolidated with those of "status".
462478
--- www/forum.wiki
+++ www/forum.wiki
@@ -428,16 +428,27 @@
428
429 The list defines the legal statuses for forum posts and syncs with
430 other project-level settings. All "label" and "value" members must be
431 unique within the scope of that list.
432
433 If the list has two or more entries then the forum behavior changes in
434 the following ways:
 
 
 
 
435
 
 
 
 
 
 
 
 
436 * The root of each thread, when selected, gets a new UI control for
437 displaying or editing the status, depending on permissions.
438 * The forum list shows the "label" value of the current status.
439
440 A list with only a single entry is treated as empty, the justification
441 being that if there is only one option then the UI does not need to be
442 cluttered with it.
443
@@ -457,5 +468,10 @@
457 UI ensures that the tag is applied to the proper post, even when
458 it is rendering a newer version.
459
460 The UI will eventually offer the ability to filter the post list by
461 status.
 
 
 
 
 
462
--- www/forum.wiki
+++ www/forum.wiki
@@ -428,16 +428,27 @@
428
429 The list defines the legal statuses for forum posts and syncs with
430 other project-level settings. All "label" and "value" members must be
431 unique within the scope of that list.
432
433 The "value" values must be legal for use as HTML "dataset" members so
434 that they can be used for with CSS selectors to apply per-status
435 styling.
436
437 If the list is valid JSON5 and has two or more entries then the forum
438 behavior changes in the following ways:
439
440 * Each TR element of the main <tt>/forum</tt> view table gets a
441 <tt>data-status="X"</tt> member, where "X" is the value part from
442 the associated status. This can be used to style the statuses
443 in the site skin using CSS selectors such as<br>
444 <tt>body.cpage-forum div.forumPosts tr&#91;data-status="open"]</tt><br>
445 Because there is no default status list, no predefined styles
446 are applied.
447 * The forum list shows the "label" value of the current status.
448 * The root of each thread, when selected, gets a new UI control for
449 displaying or editing the status, depending on permissions.
 
450
451 A list with only a single entry is treated as empty, the justification
452 being that if there is only one option then the UI does not need to be
453 cluttered with it.
454
@@ -457,5 +468,10 @@
468 UI ensures that the tag is applied to the proper post, even when
469 it is rendering a newer version.
470
471 The UI will eventually offer the ability to filter the post list by
472 status.
473
474 Caveat: a "closed" status is not recommended because it's easy to confuse with
475 the <a href='#close-post'>"closed" tag feature</a>, which behaves considerably
476 differently and predates that "status" tag support by about three years. The
477 "closed" semantics cannot be trivially consolidated with those of "status".
478

Keyboard Shortcuts

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