Fossil SCM

Back out the "Pinned" pages feature.

drh 2026-05-27 16:52 UTC forum-statuses
Commit 2d23c2e9cd5fbc33b428d2dd710800c8424c717f1f2b3fc1dc684a8a73d5434e
--- src/default.css
+++ src/default.css
@@ -1114,15 +1114,10 @@
11141114
div.setup_forum-column {
11151115
display: flex;
11161116
flex-direction: column;
11171117
}
11181118
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
-
11241119
body.forum span.forum-status-selection {
11251120
white-space: nowrap;
11261121
}
11271122
11281123
body.cpage-forum div.forumPosts tr[data-status] td.status {
11291124
--- src/default.css
+++ src/default.css
@@ -1114,15 +1114,10 @@
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
--- src/default.css
+++ src/default.css
@@ -1114,15 +1114,10 @@
1114 div.setup_forum-column {
1115 display: flex;
1116 flex-direction: column;
1117 }
1118
 
 
 
 
 
1119 body.forum span.forum-status-selection {
1120 white-space: nowrap;
1121 }
1122
1123 body.cpage-forum div.forumPosts tr[data-status] td.status {
1124
+10 -52
--- src/forum.c
+++ src/forum.c
@@ -1265,22 +1265,10 @@
12651265
@ %s(iClosed ? "action-reopen" : "action-close")'/>
12661266
/* ^^^ activated by fossil.page.forumpost.js */
12671267
}
12681268
@ </form>
12691269
}
1270
- if( !p->pIrt && g.perm.Setup ){
1271
- const int isPinned = forum_rid_is_tagged(pHead->fpid, "pinned", 0);
1272
- @ <form method="post" \
1273
- @ action='%R/forumpost_%s(isPinned ? "unpin" : "pin")'>
1274
- login_insert_csrf_secret();
1275
- @ <input type="hidden" name="fpid" value="%s(p->zUuid)" />
1276
- @ <input type="button" value='%s(isPinned ? "Unpin" : "Pin")' \
1277
- @ class='submit hidden \
1278
- @ %s(isPinned ? "action-unpin" : "action-pin")'/>
1279
- /* ^^^ activated by fossil.page.forumpost.js */
1280
- @ </form>
1281
- }
12821270
if( g.perm.Admin ||
12831271
(login_is_individual()
12841272
&& forumpost_is_owner(p/*not pHead*/->fpid, 0)) ){
12851273
/* When an admin edits someone else's post, the admin
12861274
** effectively takes over ownership of it (and we currently
@@ -1850,31 +1838,10 @@
18501838
char const *zReason = bIsAdd ? 0 : PD("reason", 0);
18511839
forumpost_action_helper("closed", zReason, bIsAdd, 0);
18521840
}
18531841
}
18541842
1855
-/*
1856
-** WEBPAGE: forumpost_pin hidden
1857
-** WEBPAGE: forumpost_unpin hidden
1858
-**
1859
-** fpid=X Hash of the post to be edited. REQUIRED
1860
-**
1861
-** Pins or unpins the given forum post, within the bounds of the
1862
-** API for forumpost_tag(). After (perhaps) modifying the "pinned"
1863
-** tag of the given thread, it redirects to that post's thread
1864
-** view. Requires setup privileges.
1865
-*/
1866
-void forum_page_pin(void){
1867
- login_check_credentials();
1868
- if( !g.perm.Setup ){
1869
- login_needed(g.anon.Setup);
1870
- }else{
1871
- const int bIsAdd = sqlite3_strglob("*_pin*", g.zPath)==0;
1872
- forumpost_action_helper("pinned", 0, bIsAdd, 0);
1873
- }
1874
-}
1875
-
18761843
/*
18771844
** WEBPAGE: forumpost_status hidden
18781845
**
18791846
** fpid=X Hash of the post to be edited. REQUIRED
18801847
** status=Y New status value. REQUIRED
@@ -2500,69 +2467,60 @@
25002467
zStatusFilter = 0;
25012468
}
25022469
if( db_table_exists("repository","forumpost") ){
25032470
db_prepare(&q,
25042471
"WITH "
2505
- "pinned(pinnedid) AS MATERIALIZED (\n"
2506
- " SELECT DISTINCT tagxref.rid\n"
2507
- " FROM tag, tagxref\n"
2508
- " WHERE tag.tagname='pinned'\n"
2509
- " AND tagxref.tagid=tag.tagid\n"
2510
- " AND tagxref.tagtype>=1\n"
2511
- "),\n"
2512
- "thread(age,duration,cnt,root,last,pinned) AS (\n"
2472
+ "thread(age,duration,cnt,root,last) AS (\n"
25132473
" SELECT\n"
25142474
" julianday('now') - max(fmtime),\n"
25152475
" max(fmtime) - min(fmtime),\n"
25162476
" sum(fprev IS NULL),\n"
25172477
" froot,\n"
25182478
" (SELECT fpid FROM forumpost AS y\n"
25192479
" WHERE y.froot=x.froot %s\n"
2520
- " ORDER BY y.fmtime DESC LIMIT 1),\n"
2521
- " froot IN pinned\n"
2480
+ " ORDER BY y.fmtime DESC LIMIT 1)\n"
25222481
" FROM forumpost AS x\n"
25232482
" WHERE firt IS NULL AND %s/*ModForum*/\n"
25242483
" GROUP BY froot\n"
2525
- " ORDER BY 6 DESC, 1 LIMIT %d OFFSET %d\n"
2484
+ " ORDER BY 1\n"
2485
+ " LIMIT %d OFFSET %d\n"
25262486
")\n"
25272487
"SELECT\n"
25282488
" thread.age,\n" /* 0 */
25292489
" thread.duration,\n" /* 1 */
25302490
" thread.cnt,\n" /* 2 */
25312491
" blob.uuid,\n" /* 3 */
25322492
" substr(event.comment,instr(event.comment,':')+1),\n" /* 4 */
25332493
" thread.last,\n" /* 5 */
2534
- " thread.pinned,\n" /* 6 */
25352494
" (SELECT coalesce(fs.value,dfs.value)\n"
25362495
" FROM tag, tagxref, forumstatus fs\n"
25372496
" WHERE tag.tagname='status'\n"
25382497
" AND tagxref.tagid=tag.tagid\n"
25392498
" AND tagxref.tagtype>=1\n"
2540
- " AND fs.value=tagxref.value)," /* 7 */
2499
+ " AND fs.value=tagxref.value)," /* 6 */
25412500
" (SELECT coalesce(fs.label,dfs.label)\n"
25422501
" FROM tag, tagxref, forumstatus fs\n"
25432502
" WHERE tag.tagname='status'\n"
25442503
" AND tagxref.tagid=tag.tagid\n"
25452504
" AND tagxref.tagtype>=1\n"
2546
- " AND fs.value=tagxref.value)" /* 8 */
2505
+ " AND fs.value=tagxref.value)" /* 7 */
25472506
" FROM thread, blob, event\n"
25482507
" LEFT JOIN forumstatus AS dfs ON (dfs.ord=1)\n"
25492508
" WHERE blob.rid=thread.last\n"
25502509
" AND event.objid=thread.last\n"
2551
- " ORDER BY 7/*pinned*/ DESC, 1;",
2510
+ " ORDER BY 1;",
25522511
g.perm.ModForum ? "" : "AND y.fpid NOT IN private" /*safe-for-%s*/,
25532512
g.perm.ModForum ? "true" : "fpid NOT IN private" /*safe-for-%s*/,
25542513
iLimit+1, iOfst
25552514
);
25562515
while( db_step(&q)==SQLITE_ROW ){
25572516
char *zAge = human_readable_age(db_column_double(&q,0));
25582517
int nMsg = db_column_int(&q, 2);
2559
- int bPinned = db_column_int(&q, 6);
25602518
const char *zUuid = db_column_text(&q, 3);
25612519
const char *zTitle = db_column_text(&q, 4);
2562
- const char *zStatus = bHasStatus ? db_column_text(&q, 7) : NULL;
2563
- const char *zStatusLbl = bHasStatus ? db_column_text(&q, 8) : NULL;
2520
+ const char *zStatus = bHasStatus ? db_column_text(&q, 6) : NULL;
2521
+ const char *zStatusLbl = bHasStatus ? db_column_text(&q, 7) : NULL;
25642522
const int bShowStatus = bHasStatus && !zStatusFilter;
25652523
const int nCols = bShowStatus ? 4 : 3;
25662524
if( iCnt==0 ){
25672525
char * zTail = zStatusFilter
25682526
? mprintf(" with status=%Q", zStatusFilter)
@@ -2601,11 +2559,11 @@
26012559
}
26022560
@ '>&darr; Older...</a></td></tr>
26032561
fossil_free(zAge);
26042562
break;
26052563
}
2606
- @ <tr%s(bPinned ? " class='pinned'" : "")
2564
+ @ <tr \
26072565
if( bHasStatus ){
26082566
@ data-status="%h(zStatus)"\
26092567
}
26102568
@ ><td>%h(zAge) ago</td>
26112569
@ <td class='subject'>%z(href("%R/forumpost/%S",zUuid))%h(zTitle)</a>\
26122570
--- src/forum.c
+++ src/forum.c
@@ -1265,22 +1265,10 @@
1265 @ %s(iClosed ? "action-reopen" : "action-close")'/>
1266 /* ^^^ activated by fossil.page.forumpost.js */
1267 }
1268 @ </form>
1269 }
1270 if( !p->pIrt && g.perm.Setup ){
1271 const int isPinned = forum_rid_is_tagged(pHead->fpid, "pinned", 0);
1272 @ <form method="post" \
1273 @ action='%R/forumpost_%s(isPinned ? "unpin" : "pin")'>
1274 login_insert_csrf_secret();
1275 @ <input type="hidden" name="fpid" value="%s(p->zUuid)" />
1276 @ <input type="button" value='%s(isPinned ? "Unpin" : "Pin")' \
1277 @ class='submit hidden \
1278 @ %s(isPinned ? "action-unpin" : "action-pin")'/>
1279 /* ^^^ activated by fossil.page.forumpost.js */
1280 @ </form>
1281 }
1282 if( g.perm.Admin ||
1283 (login_is_individual()
1284 && forumpost_is_owner(p/*not pHead*/->fpid, 0)) ){
1285 /* When an admin edits someone else's post, the admin
1286 ** effectively takes over ownership of it (and we currently
@@ -1850,31 +1838,10 @@
1850 char const *zReason = bIsAdd ? 0 : PD("reason", 0);
1851 forumpost_action_helper("closed", zReason, bIsAdd, 0);
1852 }
1853 }
1854
1855 /*
1856 ** WEBPAGE: forumpost_pin hidden
1857 ** WEBPAGE: forumpost_unpin hidden
1858 **
1859 ** fpid=X Hash of the post to be edited. REQUIRED
1860 **
1861 ** Pins or unpins the given forum post, within the bounds of the
1862 ** API for forumpost_tag(). After (perhaps) modifying the "pinned"
1863 ** tag of the given thread, it redirects to that post's thread
1864 ** view. Requires setup privileges.
1865 */
1866 void forum_page_pin(void){
1867 login_check_credentials();
1868 if( !g.perm.Setup ){
1869 login_needed(g.anon.Setup);
1870 }else{
1871 const int bIsAdd = sqlite3_strglob("*_pin*", g.zPath)==0;
1872 forumpost_action_helper("pinned", 0, bIsAdd, 0);
1873 }
1874 }
1875
1876 /*
1877 ** WEBPAGE: forumpost_status hidden
1878 **
1879 ** fpid=X Hash of the post to be edited. REQUIRED
1880 ** status=Y New status value. REQUIRED
@@ -2500,69 +2467,60 @@
2500 zStatusFilter = 0;
2501 }
2502 if( db_table_exists("repository","forumpost") ){
2503 db_prepare(&q,
2504 "WITH "
2505 "pinned(pinnedid) AS MATERIALIZED (\n"
2506 " SELECT DISTINCT tagxref.rid\n"
2507 " FROM tag, tagxref\n"
2508 " WHERE tag.tagname='pinned'\n"
2509 " AND tagxref.tagid=tag.tagid\n"
2510 " AND tagxref.tagtype>=1\n"
2511 "),\n"
2512 "thread(age,duration,cnt,root,last,pinned) AS (\n"
2513 " SELECT\n"
2514 " julianday('now') - max(fmtime),\n"
2515 " max(fmtime) - min(fmtime),\n"
2516 " sum(fprev IS NULL),\n"
2517 " froot,\n"
2518 " (SELECT fpid FROM forumpost AS y\n"
2519 " WHERE y.froot=x.froot %s\n"
2520 " ORDER BY y.fmtime DESC LIMIT 1),\n"
2521 " froot IN pinned\n"
2522 " FROM forumpost AS x\n"
2523 " WHERE firt IS NULL AND %s/*ModForum*/\n"
2524 " GROUP BY froot\n"
2525 " ORDER BY 6 DESC, 1 LIMIT %d OFFSET %d\n"
 
2526 ")\n"
2527 "SELECT\n"
2528 " thread.age,\n" /* 0 */
2529 " thread.duration,\n" /* 1 */
2530 " thread.cnt,\n" /* 2 */
2531 " blob.uuid,\n" /* 3 */
2532 " substr(event.comment,instr(event.comment,':')+1),\n" /* 4 */
2533 " thread.last,\n" /* 5 */
2534 " thread.pinned,\n" /* 6 */
2535 " (SELECT coalesce(fs.value,dfs.value)\n"
2536 " FROM tag, tagxref, forumstatus fs\n"
2537 " WHERE tag.tagname='status'\n"
2538 " AND tagxref.tagid=tag.tagid\n"
2539 " AND tagxref.tagtype>=1\n"
2540 " AND fs.value=tagxref.value)," /* 7 */
2541 " (SELECT coalesce(fs.label,dfs.label)\n"
2542 " FROM tag, tagxref, forumstatus fs\n"
2543 " WHERE tag.tagname='status'\n"
2544 " AND tagxref.tagid=tag.tagid\n"
2545 " AND tagxref.tagtype>=1\n"
2546 " AND fs.value=tagxref.value)" /* 8 */
2547 " FROM thread, blob, event\n"
2548 " LEFT JOIN forumstatus AS dfs ON (dfs.ord=1)\n"
2549 " WHERE blob.rid=thread.last\n"
2550 " AND event.objid=thread.last\n"
2551 " ORDER BY 7/*pinned*/ DESC, 1;",
2552 g.perm.ModForum ? "" : "AND y.fpid NOT IN private" /*safe-for-%s*/,
2553 g.perm.ModForum ? "true" : "fpid NOT IN private" /*safe-for-%s*/,
2554 iLimit+1, iOfst
2555 );
2556 while( db_step(&q)==SQLITE_ROW ){
2557 char *zAge = human_readable_age(db_column_double(&q,0));
2558 int nMsg = db_column_int(&q, 2);
2559 int bPinned = db_column_int(&q, 6);
2560 const char *zUuid = db_column_text(&q, 3);
2561 const char *zTitle = db_column_text(&q, 4);
2562 const char *zStatus = bHasStatus ? db_column_text(&q, 7) : NULL;
2563 const char *zStatusLbl = bHasStatus ? db_column_text(&q, 8) : NULL;
2564 const int bShowStatus = bHasStatus && !zStatusFilter;
2565 const int nCols = bShowStatus ? 4 : 3;
2566 if( iCnt==0 ){
2567 char * zTail = zStatusFilter
2568 ? mprintf(" with status=%Q", zStatusFilter)
@@ -2601,11 +2559,11 @@
2601 }
2602 @ '>&darr; Older...</a></td></tr>
2603 fossil_free(zAge);
2604 break;
2605 }
2606 @ <tr%s(bPinned ? " class='pinned'" : "")
2607 if( bHasStatus ){
2608 @ data-status="%h(zStatus)"\
2609 }
2610 @ ><td>%h(zAge) ago</td>
2611 @ <td class='subject'>%z(href("%R/forumpost/%S",zUuid))%h(zTitle)</a>\
2612
--- src/forum.c
+++ src/forum.c
@@ -1265,22 +1265,10 @@
1265 @ %s(iClosed ? "action-reopen" : "action-close")'/>
1266 /* ^^^ activated by fossil.page.forumpost.js */
1267 }
1268 @ </form>
1269 }
 
 
 
 
 
 
 
 
 
 
 
 
1270 if( g.perm.Admin ||
1271 (login_is_individual()
1272 && forumpost_is_owner(p/*not pHead*/->fpid, 0)) ){
1273 /* When an admin edits someone else's post, the admin
1274 ** effectively takes over ownership of it (and we currently
@@ -1850,31 +1838,10 @@
1838 char const *zReason = bIsAdd ? 0 : PD("reason", 0);
1839 forumpost_action_helper("closed", zReason, bIsAdd, 0);
1840 }
1841 }
1842
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1843 /*
1844 ** WEBPAGE: forumpost_status hidden
1845 **
1846 ** fpid=X Hash of the post to be edited. REQUIRED
1847 ** status=Y New status value. REQUIRED
@@ -2500,69 +2467,60 @@
2467 zStatusFilter = 0;
2468 }
2469 if( db_table_exists("repository","forumpost") ){
2470 db_prepare(&q,
2471 "WITH "
2472 "thread(age,duration,cnt,root,last) AS (\n"
 
 
 
 
 
 
 
2473 " SELECT\n"
2474 " julianday('now') - max(fmtime),\n"
2475 " max(fmtime) - min(fmtime),\n"
2476 " sum(fprev IS NULL),\n"
2477 " froot,\n"
2478 " (SELECT fpid FROM forumpost AS y\n"
2479 " WHERE y.froot=x.froot %s\n"
2480 " ORDER BY y.fmtime DESC LIMIT 1)\n"
 
2481 " FROM forumpost AS x\n"
2482 " WHERE firt IS NULL AND %s/*ModForum*/\n"
2483 " GROUP BY froot\n"
2484 " ORDER BY 1\n"
2485 " LIMIT %d OFFSET %d\n"
2486 ")\n"
2487 "SELECT\n"
2488 " thread.age,\n" /* 0 */
2489 " thread.duration,\n" /* 1 */
2490 " thread.cnt,\n" /* 2 */
2491 " blob.uuid,\n" /* 3 */
2492 " substr(event.comment,instr(event.comment,':')+1),\n" /* 4 */
2493 " thread.last,\n" /* 5 */
 
2494 " (SELECT coalesce(fs.value,dfs.value)\n"
2495 " FROM tag, tagxref, forumstatus fs\n"
2496 " WHERE tag.tagname='status'\n"
2497 " AND tagxref.tagid=tag.tagid\n"
2498 " AND tagxref.tagtype>=1\n"
2499 " AND fs.value=tagxref.value)," /* 6 */
2500 " (SELECT coalesce(fs.label,dfs.label)\n"
2501 " FROM tag, tagxref, forumstatus fs\n"
2502 " WHERE tag.tagname='status'\n"
2503 " AND tagxref.tagid=tag.tagid\n"
2504 " AND tagxref.tagtype>=1\n"
2505 " AND fs.value=tagxref.value)" /* 7 */
2506 " FROM thread, blob, event\n"
2507 " LEFT JOIN forumstatus AS dfs ON (dfs.ord=1)\n"
2508 " WHERE blob.rid=thread.last\n"
2509 " AND event.objid=thread.last\n"
2510 " ORDER BY 1;",
2511 g.perm.ModForum ? "" : "AND y.fpid NOT IN private" /*safe-for-%s*/,
2512 g.perm.ModForum ? "true" : "fpid NOT IN private" /*safe-for-%s*/,
2513 iLimit+1, iOfst
2514 );
2515 while( db_step(&q)==SQLITE_ROW ){
2516 char *zAge = human_readable_age(db_column_double(&q,0));
2517 int nMsg = db_column_int(&q, 2);
 
2518 const char *zUuid = db_column_text(&q, 3);
2519 const char *zTitle = db_column_text(&q, 4);
2520 const char *zStatus = bHasStatus ? db_column_text(&q, 6) : NULL;
2521 const char *zStatusLbl = bHasStatus ? db_column_text(&q, 7) : NULL;
2522 const int bShowStatus = bHasStatus && !zStatusFilter;
2523 const int nCols = bShowStatus ? 4 : 3;
2524 if( iCnt==0 ){
2525 char * zTail = zStatusFilter
2526 ? mprintf(" with status=%Q", zStatusFilter)
@@ -2601,11 +2559,11 @@
2559 }
2560 @ '>&darr; Older...</a></td></tr>
2561 fossil_free(zAge);
2562 break;
2563 }
2564 @ <tr \
2565 if( bHasStatus ){
2566 @ data-status="%h(zStatus)"\
2567 }
2568 @ ><td>%h(zAge) ago</td>
2569 @ <td class='subject'>%z(href("%R/forumpost/%S",zUuid))%h(zTitle)</a>\
2570
--- src/fossil.page.forumpost.js
+++ src/fossil.page.forumpost.js
@@ -130,21 +130,10 @@
130130
: "Confirm close"),
131131
onconfirm: ()=>form.submit()
132132
});
133133
});
134134
form
135
- .querySelectorAll("input.action-pin, input.action-unpin")
136
- .forEach(function(e){
137
- e.classList.remove('hidden');
138
- F.confirmer(e, {
139
- confirmText: (e.classList.contains('action-unpin')
140
- ? "Confirm unpin"
141
- : "Confirm pin"),
142
- onconfirm: ()=>form.submit()
143
- });
144
- });
145
- form
146135
.querySelectorAll("input[type='button'].action-status")
147136
.forEach(function(btn){
148137
btn.classList.remove('hidden');
149138
const sel = btn.previousElementSibling;
150139
const updateAble = ()=>{
151140
--- src/fossil.page.forumpost.js
+++ src/fossil.page.forumpost.js
@@ -130,21 +130,10 @@
130 : "Confirm close"),
131 onconfirm: ()=>form.submit()
132 });
133 });
134 form
135 .querySelectorAll("input.action-pin, input.action-unpin")
136 .forEach(function(e){
137 e.classList.remove('hidden');
138 F.confirmer(e, {
139 confirmText: (e.classList.contains('action-unpin')
140 ? "Confirm unpin"
141 : "Confirm pin"),
142 onconfirm: ()=>form.submit()
143 });
144 });
145 form
146 .querySelectorAll("input[type='button'].action-status")
147 .forEach(function(btn){
148 btn.classList.remove('hidden');
149 const sel = btn.previousElementSibling;
150 const updateAble = ()=>{
151
--- src/fossil.page.forumpost.js
+++ src/fossil.page.forumpost.js
@@ -130,21 +130,10 @@
130 : "Confirm close"),
131 onconfirm: ()=>form.submit()
132 });
133 });
134 form
 
 
 
 
 
 
 
 
 
 
 
135 .querySelectorAll("input[type='button'].action-status")
136 .forEach(function(btn){
137 btn.classList.remove('hidden');
138 const sel = btn.previousElementSibling;
139 const updateAble = ()=>{
140
--- www/changes.wiki
+++ www/changes.wiki
@@ -19,14 +19,10 @@
1919
<li> Forum posts may now have attachments if their poster has the new "B"
2020
capability.</li>
2121
<li> Add the "[/help/attachment-size-limit|attachment-size-limit]" setting
2222
to limit the size of file attachments to wiki pages, tech notes,
2323
tickets, and forum posts.
24
- <li> Forum threads may now be "pinned", also known as "sticky", such that
25
- they will sort first in the thread list view. To pin or unpin a post,
26
- a Setup user must visit the top-most post in the thread and then tap
27
- the new button.
2824
</ol>
2925
3026
<h2 id='v2_28'>Changes for version 2.28 (2026-03-11)</h2><ol>
3127
<li> Improvements to [./antibot.wiki|anti-robot defenses]:<ol type="a">
3228
<li> The default configuration now allows robots to download any tarball
3329
--- www/changes.wiki
+++ www/changes.wiki
@@ -19,14 +19,10 @@
19 <li> Forum posts may now have attachments if their poster has the new "B"
20 capability.</li>
21 <li> Add the "[/help/attachment-size-limit|attachment-size-limit]" setting
22 to limit the size of file attachments to wiki pages, tech notes,
23 tickets, and forum posts.
24 <li> Forum threads may now be "pinned", also known as "sticky", such that
25 they will sort first in the thread list view. To pin or unpin a post,
26 a Setup user must visit the top-most post in the thread and then tap
27 the new button.
28 </ol>
29
30 <h2 id='v2_28'>Changes for version 2.28 (2026-03-11)</h2><ol>
31 <li> Improvements to [./antibot.wiki|anti-robot defenses]:<ol type="a">
32 <li> The default configuration now allows robots to download any tarball
33
--- www/changes.wiki
+++ www/changes.wiki
@@ -19,14 +19,10 @@
19 <li> Forum posts may now have attachments if their poster has the new "B"
20 capability.</li>
21 <li> Add the "[/help/attachment-size-limit|attachment-size-limit]" setting
22 to limit the size of file attachments to wiki pages, tech notes,
23 tickets, and forum posts.
 
 
 
 
24 </ol>
25
26 <h2 id='v2_28'>Changes for version 2.28 (2026-03-11)</h2><ol>
27 <li> Improvements to [./antibot.wiki|anti-robot defenses]:<ol type="a">
28 <li> The default configuration now allows robots to download any tarball
29

Keyboard Shortcuts

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