Fossil SCM

Rough and untested implementation for forum display and reply. Add two new capabilities for posting to the forum not subject to moderation, and for the ability to edit posts from others.

drh 2018-06-15 20:48 UTC forum-brainstorm-1
Commit f8927901c2c6579c8f926e38aa136ba257df466178b1e5887e05ac1498c766cd
4 files changed +209 -34 +5 -2 +5 -3 +25 -13
+209 -34
--- src/forum.c
+++ src/forum.c
@@ -25,26 +25,26 @@
2525
** The schema for the tables that manage the forum, if forum is
2626
** enabled.
2727
*/
2828
static const char zForumInit[] =
2929
@ CREATE TABLE repository.forumpost(
30
-@ mpostid INTEGER PRIMARY KEY, -- unique id for each post
30
+@ mpostid INTEGER PRIMARY KEY, -- unique id for each post (local)
31
+@ mposthash TEXT, -- uuid for this post
3132
@ mthreadid INTEGER, -- thread to which this post belongs
3233
@ uname TEXT, -- name of user
3334
@ mtime REAL, -- julian day number
34
-@ mstatus TEXT, -- status. ('mod','ok')
35
+@ mstatus TEXT, -- status. NULL=ok. 'mod'=pending moderation
3536
@ mimetype TEXT, -- Mimetype for mbody
3637
@ ipaddr TEXT, -- IP address of post origin
3738
@ inreplyto INT, -- Parent posting
3839
@ mbody TEXT -- Content of the post
3940
@ );
4041
@ CREATE INDEX repository.forumpost_x1 ON
41
-@ forumpost(threadid,inreplyto,mtime);
42
-@ CREATE INDEX repository.forumpost_x2 ON
43
-@ forumpost(mtime) WHERE mstatus='mod';
42
+@ forumpost(inreplyto,mtime);
4443
@ CREATE TABLE repository.forumthread(
4544
@ mthreadid INTEGER PRIMARY KEY,
45
+@ mthreadhash TEXT, -- uuid for this thread
4646
@ mtitle TEXT, -- Title or subject line
4747
@ mtime REAL, -- Most recent update
4848
@ npost INT -- Number of posts on this thread
4949
@ );
5050
;
@@ -62,49 +62,224 @@
6262
/*
6363
** WEBPAGE: forum
6464
** URL: /forum
6565
** Query parameters:
6666
**
67
-** thread=N Show posts from thread N
6867
** item=N Show post N and its replies
6968
**
7069
*/
7170
void forum_page(void){
72
- int threadId = 0;
7371
int itemId;
7472
Stmt q;
7573
int i;
7674
7775
login_check_credentials();
7876
if( !g.perm.RdForum ){ login_needed(g.anon.RdForum); return; }
7977
forum_verify_schema();
78
+ style_header("Forum");
8079
itemId = atoi(PD("item","0"));
8180
if( itemId>0 ){
82
- threadId = db_int(0, "SELECT mthreadid FROM forumpost WHERE mpostid=%d",
83
- itemId);
84
- }
85
- if( threadId==0 ) threadId = atoi(PD("thread","0"));
86
- if( threadId>0 ){
87
-
88
- }
89
- i = 0;
90
- db_prepare(&q,
91
- "SELECT mtitle, npost, mthreadid FROM forumthread"
92
- " WHERE inreplyto IS NULL ORDER BY mtime DESC LIMIT 40"
93
- );
94
- style_header("Recent Forum Threads");
95
- while( db_step(&q)==SQLITE_OK ){
96
- int n = db_column_int(&q,1);
97
- int threadid = db_column_int(&q,2);
98
- const char *zTitle = db_column_text(&q,0);
99
- if( i==0 ){
100
- @ <ol>
101
- }
102
- @ <li>
103
- @ %z(href("%R/forum?thread=%d",threadid))%h(zTitle)</a><br>
104
- @ %d(n) post%s(n==1?"":"s")</li>
105
- }
106
- if( i ){
107
- @ </ol>
108
- }
81
+ double rNow = db_double(0.0, "SELECT julianday('now')");
82
+ /* Show the post given by itemId and all its descendents */
83
+ db_prepare(&q,
84
+ "WITH RECURSIVE"
85
+ " post(id,uname,mstat,mime,ipaddr,parent,mbody,depth,mtime) AS ("
86
+ " SELECT mpostid, uname, mstatus, mimetype, ipaddr, inreplyto, mbody,"
87
+ " 0, 1 FROM forumpost WHERE mpostid=%d"
88
+ " UNION"
89
+ " SELECT f.mpostid, f.uname, f.mstatus, f.mimetype, f.ipaddr,"
90
+ " f.inreplyto, f.mbody, p.depth+1 AS xdepth, f.mtime AS xtime"
91
+ " FROM forumpost AS f, post AS p"
92
+ " WHERE forumpost.inreplyto=post.id"
93
+ " ORDER BY xdepth DESC, xtime ASC"
94
+ ") SELECT * FROM post;",
95
+ itemId
96
+ );
97
+ @ <table border=0 class="forumtable">
98
+ while( db_step(&q)==SQLITE_ROW ){
99
+ int id = db_column_int(&q, 0);
100
+ const char *zUser = db_column_text(&q, 1);
101
+ const char *zStat = db_column_text(&q, 2);
102
+ const char *zMime = db_column_text(&q, 3);
103
+ const char *zIp = db_column_text(&q, 4);
104
+ int iDepth = db_column_int(&q, 7);
105
+ double rMTime = db_column_double(&q, 8);
106
+ char *zAge = db_timespan_name(rNow - rMTime);
107
+ Blob body;
108
+ @ <!-- Forum post %d(id) -->
109
+ @ <tr>
110
+ @ <td class="forum_margin" width="%d((iDepth-1)*10)" rowspan="3"></td>
111
+ @ <td>%h(zUser) %z(zAge) ago</td>
112
+ @ </tr>
113
+ @ <tr><td class="forum_body">
114
+ blob_init(&body, db_column_text(&q,6), db_column_bytes(&q,6));
115
+ wiki_render_by_mimetype(&body, zMime);
116
+ blob_reset(&body);
117
+ @ </td></tr>
118
+ @ <tr><td class="forum_buttons">
119
+ if( g.perm.WrForum ){
120
+ if( g.perm.AdminForum || fossil_strcmp(g.zLogin, zUser)==0 ){
121
+ @ <a href='%R/forumedit?item=%d(id)'>Edit</a>
122
+ }
123
+ @ <a href='%R/forumedit?replyto=%d(id)'>Reply</a>
124
+ }
125
+ @ </td></tr>
126
+ }
127
+ @ </table>
128
+ }else{
129
+ /* If we reach this point, that means the users wants a list of
130
+ ** recent threads.
131
+ */
132
+ i = 0;
133
+ db_prepare(&q,
134
+ "SELECT a.mtitle, a.npost, b.mpostid"
135
+ " FROM forumthread AS a, forumpost AS b "
136
+ " WHERE a.mthreadid=b.mthreadid"
137
+ " AND b.inreplyto IS NULL"
138
+ " ORDER BY a.mtime DESC LIMIT 40"
139
+ );
140
+ if( g.perm.WrForum ){
141
+ style_submenu_element("New", "%R/forumedit");
142
+ }
143
+ @ <h1>Recent Forum Threads</h>
144
+ while( db_step(&q)==SQLITE_OK ){
145
+ int n = db_column_int(&q,1);
146
+ int itemid = db_column_int(&q,2);
147
+ const char *zTitle = db_column_text(&q,0);
148
+ if( i==0 ){
149
+ @ <ol>
150
+ }
151
+ @ <li>
152
+ @ %z(href("%R/forum?item=%d",itemid))%h(zTitle)</a><br>
153
+ @ %d(n) post%s(n==1?"":"s")</li>
154
+ }
155
+ if( i ){
156
+ @ </ol>
157
+ }
158
+ }
159
+ style_footer();
160
+}
161
+
162
+/*
163
+** Use content in CGI parameters "s" (subject), "b" (body), and
164
+** "m" (mimetype) to create a new forum entry.
165
+** Return the id of the new forum entry.
166
+**
167
+** If any problems occur, return 0 and set *pzErr to a description of
168
+** the problem.
169
+**
170
+** Cases:
171
+**
172
+** itemId==0 && parentId==0 Starting a new thread.
173
+** itemId==0 && parentId>0 New reply to parentId
174
+** itemId>0 && parentId==0 Edit existing post itemId
175
+*/
176
+static int forum_post(int itemId, int parentId, char **pzErr){
177
+ const char *zSubject = 0;
178
+ int threadId;
179
+ double rNow = db_double(0.0, "SELECT julianday('now')");
180
+ if( itemId==0 && parentId==0 ){
181
+ /* Start a new thread. Subject required. */
182
+ sqlite3_uint64 r1, r2;
183
+ zSubject = PT("s");
184
+ if( zSubject==0 || zSubject[0]==0 ){
185
+ *pzErr = "\"Subject\" required to start a new thread";
186
+ return 0;
187
+ }
188
+ sqlite3_randomness(sizeof(r1), &r1);
189
+ sqlite3_randomness(sizeof(r2), &r2);
190
+ db_multi_exec(
191
+ "INSERT INTO forumthread(mthreadhash, mtitle, mtime, npost)"
192
+ "VALUES(lower(hex(randomblob(32))),%Q,%!.17g,1)",
193
+ zSubject, rNow
194
+ );
195
+ threadId = db_last_insert_rowid();
196
+ }
197
+ if( itemId ){
198
+ db_multi_exec(
199
+ "UPDATE forumpost SET"
200
+ " mtime=%!.17g,"
201
+ " mimetype=%Q,"
202
+ " ipaddr=%Q,"
203
+ " mbody=%Q"
204
+ " WHERE mpostid=%d",
205
+ rNow, PT("m"), P("REMOTE_ADDR"), PT("b"), itemId
206
+ );
207
+ }else{
208
+ db_multi_exec(
209
+ "INSERT INTO forumpost(mposthash,mthreadid,uname,mtime,"
210
+ " mstatus,mimetype,ipaddr,inreplyto,mbody) VALUES"
211
+ " (lower(hex(randomblob(32))),%d,%Q,%!.17g,%Q,%Q,%Q,NULL,%Q)",
212
+ threadId,g.zLogin,rNow,NULL,P("m"),P("REMOTE_ADDR"),P("b"));
213
+ itemId = db_last_insert_rowid();
214
+ }
215
+ if( zSubject==0 ){
216
+ db_multi_exec(
217
+ "UPDATE forumthread SET mtime=%!.17g"
218
+ " WHERE mthreadid=(SELECT mthreadid FROM forumpost WHERE mpostid=%d)",
219
+ rNow, itemId
220
+ );
221
+ }
222
+ return itemId;
223
+}
224
+
225
+/*
226
+** WEBPAGE: forumedit
227
+**
228
+** Query parameters:
229
+**
230
+** replyto=N Enter a reply to forum item N
231
+** item=N Edit item N
232
+** s=SUBJECT Subject. New thread only. Omitted for replies
233
+** b=BODY Body of the post
234
+** m=MIMETYPE Mimetype for the body of the post
235
+** x Submit changes
236
+** p Preview changes
237
+*/
238
+static void forum_reply_page(void){
239
+ int itemId;
240
+ int parentId;
241
+ const char *zErr = 0;
242
+ login_check_credentials();
243
+ const char *zBody;
244
+ const char *zMime;
245
+ const char *zSub;
246
+ if( !g.perm.WrForum ){ login_needed(g.anon.WrForum); return; }
247
+ forum_verify_schema();
248
+ itemId = atoi(PD("item","0"));
249
+ parentId = atoi(PD("replyto","0"));
250
+ if( P("x")!=0 && cgi_csrf_safe(1) ){
251
+ itemId = forum_post(itemId,parentId,&zErr);
252
+ if( itemId ){
253
+ cgi_redirectf("%R/forum?item=%d",itemId);
254
+ return;
255
+ }
256
+ }
257
+ style_header("Edit Forum Post");
258
+ @ <form method="POST">
259
+ if( itemId ){
260
+ @ <input type="hidden" name="item" value="%d(itemId)">
261
+ }
262
+ if( parentId ){
263
+ @ <input type="hidden" name="replyto" value="%d(parentId)">
264
+ }
265
+ if( P("p") ){
266
+ Blob x;
267
+ @ <div class="forumpreview">
268
+ if( P("s") ){
269
+ @ <h1>%h(PT("s"))</h1>
270
+ }
271
+ @ <div class="forumpreviewbody">
272
+ blob_init(&x, PT("b"), -1);
273
+ wiki_render_by_mimetype(&x, PT("m"));
274
+ blob_reset(&x);
275
+ @ </div>
276
+ @ </div>
277
+ }
278
+ @ <table border="0" class="forumeditform">
279
+ if( itemId==0 && parentId==0 ){
280
+ zSub = PT("s");
281
+ }
282
+ @ </table>
283
+ @ </form>
109284
style_footer();
110285
}
111286
--- src/forum.c
+++ src/forum.c
@@ -25,26 +25,26 @@
25 ** The schema for the tables that manage the forum, if forum is
26 ** enabled.
27 */
28 static const char zForumInit[] =
29 @ CREATE TABLE repository.forumpost(
30 @ mpostid INTEGER PRIMARY KEY, -- unique id for each post
 
31 @ mthreadid INTEGER, -- thread to which this post belongs
32 @ uname TEXT, -- name of user
33 @ mtime REAL, -- julian day number
34 @ mstatus TEXT, -- status. ('mod','ok')
35 @ mimetype TEXT, -- Mimetype for mbody
36 @ ipaddr TEXT, -- IP address of post origin
37 @ inreplyto INT, -- Parent posting
38 @ mbody TEXT -- Content of the post
39 @ );
40 @ CREATE INDEX repository.forumpost_x1 ON
41 @ forumpost(threadid,inreplyto,mtime);
42 @ CREATE INDEX repository.forumpost_x2 ON
43 @ forumpost(mtime) WHERE mstatus='mod';
44 @ CREATE TABLE repository.forumthread(
45 @ mthreadid INTEGER PRIMARY KEY,
 
46 @ mtitle TEXT, -- Title or subject line
47 @ mtime REAL, -- Most recent update
48 @ npost INT -- Number of posts on this thread
49 @ );
50 ;
@@ -62,49 +62,224 @@
62 /*
63 ** WEBPAGE: forum
64 ** URL: /forum
65 ** Query parameters:
66 **
67 ** thread=N Show posts from thread N
68 ** item=N Show post N and its replies
69 **
70 */
71 void forum_page(void){
72 int threadId = 0;
73 int itemId;
74 Stmt q;
75 int i;
76
77 login_check_credentials();
78 if( !g.perm.RdForum ){ login_needed(g.anon.RdForum); return; }
79 forum_verify_schema();
 
80 itemId = atoi(PD("item","0"));
81 if( itemId>0 ){
82 threadId = db_int(0, "SELECT mthreadid FROM forumpost WHERE mpostid=%d",
83 itemId);
84 }
85 if( threadId==0 ) threadId = atoi(PD("thread","0"));
86 if( threadId>0 ){
87
88 }
89 i = 0;
90 db_prepare(&q,
91 "SELECT mtitle, npost, mthreadid FROM forumthread"
92 " WHERE inreplyto IS NULL ORDER BY mtime DESC LIMIT 40"
93 );
94 style_header("Recent Forum Threads");
95 while( db_step(&q)==SQLITE_OK ){
96 int n = db_column_int(&q,1);
97 int threadid = db_column_int(&q,2);
98 const char *zTitle = db_column_text(&q,0);
99 if( i==0 ){
100 @ <ol>
101 }
102 @ <li>
103 @ %z(href("%R/forum?thread=%d",threadid))%h(zTitle)</a><br>
104 @ %d(n) post%s(n==1?"":"s")</li>
105 }
106 if( i ){
107 @ </ol>
108 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109 style_footer();
110 }
111
--- src/forum.c
+++ src/forum.c
@@ -25,26 +25,26 @@
25 ** The schema for the tables that manage the forum, if forum is
26 ** enabled.
27 */
28 static const char zForumInit[] =
29 @ CREATE TABLE repository.forumpost(
30 @ mpostid INTEGER PRIMARY KEY, -- unique id for each post (local)
31 @ mposthash TEXT, -- uuid for this post
32 @ mthreadid INTEGER, -- thread to which this post belongs
33 @ uname TEXT, -- name of user
34 @ mtime REAL, -- julian day number
35 @ mstatus TEXT, -- status. NULL=ok. 'mod'=pending moderation
36 @ mimetype TEXT, -- Mimetype for mbody
37 @ ipaddr TEXT, -- IP address of post origin
38 @ inreplyto INT, -- Parent posting
39 @ mbody TEXT -- Content of the post
40 @ );
41 @ CREATE INDEX repository.forumpost_x1 ON
42 @ forumpost(inreplyto,mtime);
 
 
43 @ CREATE TABLE repository.forumthread(
44 @ mthreadid INTEGER PRIMARY KEY,
45 @ mthreadhash TEXT, -- uuid for this thread
46 @ mtitle TEXT, -- Title or subject line
47 @ mtime REAL, -- Most recent update
48 @ npost INT -- Number of posts on this thread
49 @ );
50 ;
@@ -62,49 +62,224 @@
62 /*
63 ** WEBPAGE: forum
64 ** URL: /forum
65 ** Query parameters:
66 **
 
67 ** item=N Show post N and its replies
68 **
69 */
70 void forum_page(void){
 
71 int itemId;
72 Stmt q;
73 int i;
74
75 login_check_credentials();
76 if( !g.perm.RdForum ){ login_needed(g.anon.RdForum); return; }
77 forum_verify_schema();
78 style_header("Forum");
79 itemId = atoi(PD("item","0"));
80 if( itemId>0 ){
81 double rNow = db_double(0.0, "SELECT julianday('now')");
82 /* Show the post given by itemId and all its descendents */
83 db_prepare(&q,
84 "WITH RECURSIVE"
85 " post(id,uname,mstat,mime,ipaddr,parent,mbody,depth,mtime) AS ("
86 " SELECT mpostid, uname, mstatus, mimetype, ipaddr, inreplyto, mbody,"
87 " 0, 1 FROM forumpost WHERE mpostid=%d"
88 " UNION"
89 " SELECT f.mpostid, f.uname, f.mstatus, f.mimetype, f.ipaddr,"
90 " f.inreplyto, f.mbody, p.depth+1 AS xdepth, f.mtime AS xtime"
91 " FROM forumpost AS f, post AS p"
92 " WHERE forumpost.inreplyto=post.id"
93 " ORDER BY xdepth DESC, xtime ASC"
94 ") SELECT * FROM post;",
95 itemId
96 );
97 @ <table border=0 class="forumtable">
98 while( db_step(&q)==SQLITE_ROW ){
99 int id = db_column_int(&q, 0);
100 const char *zUser = db_column_text(&q, 1);
101 const char *zStat = db_column_text(&q, 2);
102 const char *zMime = db_column_text(&q, 3);
103 const char *zIp = db_column_text(&q, 4);
104 int iDepth = db_column_int(&q, 7);
105 double rMTime = db_column_double(&q, 8);
106 char *zAge = db_timespan_name(rNow - rMTime);
107 Blob body;
108 @ <!-- Forum post %d(id) -->
109 @ <tr>
110 @ <td class="forum_margin" width="%d((iDepth-1)*10)" rowspan="3"></td>
111 @ <td>%h(zUser) %z(zAge) ago</td>
112 @ </tr>
113 @ <tr><td class="forum_body">
114 blob_init(&body, db_column_text(&q,6), db_column_bytes(&q,6));
115 wiki_render_by_mimetype(&body, zMime);
116 blob_reset(&body);
117 @ </td></tr>
118 @ <tr><td class="forum_buttons">
119 if( g.perm.WrForum ){
120 if( g.perm.AdminForum || fossil_strcmp(g.zLogin, zUser)==0 ){
121 @ <a href='%R/forumedit?item=%d(id)'>Edit</a>
122 }
123 @ <a href='%R/forumedit?replyto=%d(id)'>Reply</a>
124 }
125 @ </td></tr>
126 }
127 @ </table>
128 }else{
129 /* If we reach this point, that means the users wants a list of
130 ** recent threads.
131 */
132 i = 0;
133 db_prepare(&q,
134 "SELECT a.mtitle, a.npost, b.mpostid"
135 " FROM forumthread AS a, forumpost AS b "
136 " WHERE a.mthreadid=b.mthreadid"
137 " AND b.inreplyto IS NULL"
138 " ORDER BY a.mtime DESC LIMIT 40"
139 );
140 if( g.perm.WrForum ){
141 style_submenu_element("New", "%R/forumedit");
142 }
143 @ <h1>Recent Forum Threads</h>
144 while( db_step(&q)==SQLITE_OK ){
145 int n = db_column_int(&q,1);
146 int itemid = db_column_int(&q,2);
147 const char *zTitle = db_column_text(&q,0);
148 if( i==0 ){
149 @ <ol>
150 }
151 @ <li>
152 @ %z(href("%R/forum?item=%d",itemid))%h(zTitle)</a><br>
153 @ %d(n) post%s(n==1?"":"s")</li>
154 }
155 if( i ){
156 @ </ol>
157 }
158 }
159 style_footer();
160 }
161
162 /*
163 ** Use content in CGI parameters "s" (subject), "b" (body), and
164 ** "m" (mimetype) to create a new forum entry.
165 ** Return the id of the new forum entry.
166 **
167 ** If any problems occur, return 0 and set *pzErr to a description of
168 ** the problem.
169 **
170 ** Cases:
171 **
172 ** itemId==0 && parentId==0 Starting a new thread.
173 ** itemId==0 && parentId>0 New reply to parentId
174 ** itemId>0 && parentId==0 Edit existing post itemId
175 */
176 static int forum_post(int itemId, int parentId, char **pzErr){
177 const char *zSubject = 0;
178 int threadId;
179 double rNow = db_double(0.0, "SELECT julianday('now')");
180 if( itemId==0 && parentId==0 ){
181 /* Start a new thread. Subject required. */
182 sqlite3_uint64 r1, r2;
183 zSubject = PT("s");
184 if( zSubject==0 || zSubject[0]==0 ){
185 *pzErr = "\"Subject\" required to start a new thread";
186 return 0;
187 }
188 sqlite3_randomness(sizeof(r1), &r1);
189 sqlite3_randomness(sizeof(r2), &r2);
190 db_multi_exec(
191 "INSERT INTO forumthread(mthreadhash, mtitle, mtime, npost)"
192 "VALUES(lower(hex(randomblob(32))),%Q,%!.17g,1)",
193 zSubject, rNow
194 );
195 threadId = db_last_insert_rowid();
196 }
197 if( itemId ){
198 db_multi_exec(
199 "UPDATE forumpost SET"
200 " mtime=%!.17g,"
201 " mimetype=%Q,"
202 " ipaddr=%Q,"
203 " mbody=%Q"
204 " WHERE mpostid=%d",
205 rNow, PT("m"), P("REMOTE_ADDR"), PT("b"), itemId
206 );
207 }else{
208 db_multi_exec(
209 "INSERT INTO forumpost(mposthash,mthreadid,uname,mtime,"
210 " mstatus,mimetype,ipaddr,inreplyto,mbody) VALUES"
211 " (lower(hex(randomblob(32))),%d,%Q,%!.17g,%Q,%Q,%Q,NULL,%Q)",
212 threadId,g.zLogin,rNow,NULL,P("m"),P("REMOTE_ADDR"),P("b"));
213 itemId = db_last_insert_rowid();
214 }
215 if( zSubject==0 ){
216 db_multi_exec(
217 "UPDATE forumthread SET mtime=%!.17g"
218 " WHERE mthreadid=(SELECT mthreadid FROM forumpost WHERE mpostid=%d)",
219 rNow, itemId
220 );
221 }
222 return itemId;
223 }
224
225 /*
226 ** WEBPAGE: forumedit
227 **
228 ** Query parameters:
229 **
230 ** replyto=N Enter a reply to forum item N
231 ** item=N Edit item N
232 ** s=SUBJECT Subject. New thread only. Omitted for replies
233 ** b=BODY Body of the post
234 ** m=MIMETYPE Mimetype for the body of the post
235 ** x Submit changes
236 ** p Preview changes
237 */
238 static void forum_reply_page(void){
239 int itemId;
240 int parentId;
241 const char *zErr = 0;
242 login_check_credentials();
243 const char *zBody;
244 const char *zMime;
245 const char *zSub;
246 if( !g.perm.WrForum ){ login_needed(g.anon.WrForum); return; }
247 forum_verify_schema();
248 itemId = atoi(PD("item","0"));
249 parentId = atoi(PD("replyto","0"));
250 if( P("x")!=0 && cgi_csrf_safe(1) ){
251 itemId = forum_post(itemId,parentId,&zErr);
252 if( itemId ){
253 cgi_redirectf("%R/forum?item=%d",itemId);
254 return;
255 }
256 }
257 style_header("Edit Forum Post");
258 @ <form method="POST">
259 if( itemId ){
260 @ <input type="hidden" name="item" value="%d(itemId)">
261 }
262 if( parentId ){
263 @ <input type="hidden" name="replyto" value="%d(parentId)">
264 }
265 if( P("p") ){
266 Blob x;
267 @ <div class="forumpreview">
268 if( P("s") ){
269 @ <h1>%h(PT("s"))</h1>
270 }
271 @ <div class="forumpreviewbody">
272 blob_init(&x, PT("b"), -1);
273 wiki_render_by_mimetype(&x, PT("m"));
274 blob_reset(&x);
275 @ </div>
276 @ </div>
277 }
278 @ <table border="0" class="forumeditform">
279 if( itemId==0 && parentId==0 ){
280 zSub = PT("s");
281 }
282 @ </table>
283 @ </form>
284 style_footer();
285 }
286
+5 -2
--- src/login.c
+++ src/login.c
@@ -1197,10 +1197,11 @@
11971197
p->ApndWiki = p->Hyperlink = p->Clone =
11981198
p->NewTkt = p->Password = p->RdAddr =
11991199
p->TktFmt = p->Attach = p->ApndTkt =
12001200
p->ModWiki = p->ModTkt = p->Delete =
12011201
p->RdForum = p->WrForum = p->ModForum =
1202
+ p->WrTForum = p->AdminForum =
12021203
p->WrUnver = p->Private = 1;
12031204
/* Fall thru into Read/Write */
12041205
case 'i': p->Read = p->Write = 1; break;
12051206
case 'o': p->Read = 1; break;
12061207
case 'z': p->Zip = 1; break;
@@ -1226,13 +1227,15 @@
12261227
case 't': p->TktFmt = 1; break;
12271228
case 'b': p->Attach = 1; break;
12281229
case 'x': p->Private = 1; break;
12291230
case 'y': p->WrUnver = 1; break;
12301231
1232
+ case '6': p->AdminForum = 1;
1233
+ case '5': p->ModForum = 1;
1234
+ case '4': p->WrTForum = 1;
1235
+ case '3': p->WrForum = 1;
12311236
case '2': p->RdForum = 1; break;
1232
- case '3': p->WrForum = 1; break;
1233
- case '4': p->ModForum = 1; break;
12341237
12351238
/* The "u" privileges is a little different. It recursively
12361239
** inherits all privileges of the user named "reader" */
12371240
case 'u': {
12381241
if( (flags & LOGIN_IGNORE_UV)==0 ){
12391242
--- src/login.c
+++ src/login.c
@@ -1197,10 +1197,11 @@
1197 p->ApndWiki = p->Hyperlink = p->Clone =
1198 p->NewTkt = p->Password = p->RdAddr =
1199 p->TktFmt = p->Attach = p->ApndTkt =
1200 p->ModWiki = p->ModTkt = p->Delete =
1201 p->RdForum = p->WrForum = p->ModForum =
 
1202 p->WrUnver = p->Private = 1;
1203 /* Fall thru into Read/Write */
1204 case 'i': p->Read = p->Write = 1; break;
1205 case 'o': p->Read = 1; break;
1206 case 'z': p->Zip = 1; break;
@@ -1226,13 +1227,15 @@
1226 case 't': p->TktFmt = 1; break;
1227 case 'b': p->Attach = 1; break;
1228 case 'x': p->Private = 1; break;
1229 case 'y': p->WrUnver = 1; break;
1230
 
 
 
 
1231 case '2': p->RdForum = 1; break;
1232 case '3': p->WrForum = 1; break;
1233 case '4': p->ModForum = 1; break;
1234
1235 /* The "u" privileges is a little different. It recursively
1236 ** inherits all privileges of the user named "reader" */
1237 case 'u': {
1238 if( (flags & LOGIN_IGNORE_UV)==0 ){
1239
--- src/login.c
+++ src/login.c
@@ -1197,10 +1197,11 @@
1197 p->ApndWiki = p->Hyperlink = p->Clone =
1198 p->NewTkt = p->Password = p->RdAddr =
1199 p->TktFmt = p->Attach = p->ApndTkt =
1200 p->ModWiki = p->ModTkt = p->Delete =
1201 p->RdForum = p->WrForum = p->ModForum =
1202 p->WrTForum = p->AdminForum =
1203 p->WrUnver = p->Private = 1;
1204 /* Fall thru into Read/Write */
1205 case 'i': p->Read = p->Write = 1; break;
1206 case 'o': p->Read = 1; break;
1207 case 'z': p->Zip = 1; break;
@@ -1226,13 +1227,15 @@
1227 case 't': p->TktFmt = 1; break;
1228 case 'b': p->Attach = 1; break;
1229 case 'x': p->Private = 1; break;
1230 case 'y': p->WrUnver = 1; break;
1231
1232 case '6': p->AdminForum = 1;
1233 case '5': p->ModForum = 1;
1234 case '4': p->WrTForum = 1;
1235 case '3': p->WrForum = 1;
1236 case '2': p->RdForum = 1; break;
 
 
1237
1238 /* The "u" privileges is a little different. It recursively
1239 ** inherits all privileges of the user named "reader" */
1240 case 'u': {
1241 if( (flags & LOGIN_IGNORE_UV)==0 ){
1242
+5 -3
--- src/main.c
+++ src/main.c
@@ -83,13 +83,15 @@
8383
char TktFmt; /* t: create new ticket report formats */
8484
char RdAddr; /* e: read email addresses or other private data */
8585
char Zip; /* z: download zipped artifact via /zip URL */
8686
char Private; /* x: can send and receive private content */
8787
char WrUnver; /* y: can push unversioned content */
88
- char RdForum; /* 2: Read forum posts and comments */
89
- char WrForum; /* 3: Create new forum posts and comments */
90
- char ModForum; /* 4: Moderate forum posts and comments */
88
+ char RdForum; /* 2: Read forum posts */
89
+ char WrForum; /* 3: Create new forum posts */
90
+ char WrTForum; /* 4: Post to forums not subject to moderation */
91
+ char ModForum; /* 5: Moderate (approve or reject) forum posts */
92
+ char AdminForum; /* 6: Edit forum posts by other users */
9193
};
9294
9395
#ifdef FOSSIL_ENABLE_TCL
9496
/*
9597
** All Tcl related context information is in this structure. This structure
9698
--- src/main.c
+++ src/main.c
@@ -83,13 +83,15 @@
83 char TktFmt; /* t: create new ticket report formats */
84 char RdAddr; /* e: read email addresses or other private data */
85 char Zip; /* z: download zipped artifact via /zip URL */
86 char Private; /* x: can send and receive private content */
87 char WrUnver; /* y: can push unversioned content */
88 char RdForum; /* 2: Read forum posts and comments */
89 char WrForum; /* 3: Create new forum posts and comments */
90 char ModForum; /* 4: Moderate forum posts and comments */
 
 
91 };
92
93 #ifdef FOSSIL_ENABLE_TCL
94 /*
95 ** All Tcl related context information is in this structure. This structure
96
--- src/main.c
+++ src/main.c
@@ -83,13 +83,15 @@
83 char TktFmt; /* t: create new ticket report formats */
84 char RdAddr; /* e: read email addresses or other private data */
85 char Zip; /* z: download zipped artifact via /zip URL */
86 char Private; /* x: can send and receive private content */
87 char WrUnver; /* y: can push unversioned content */
88 char RdForum; /* 2: Read forum posts */
89 char WrForum; /* 3: Create new forum posts */
90 char WrTForum; /* 4: Post to forums not subject to moderation */
91 char ModForum; /* 5: Moderate (approve or reject) forum posts */
92 char AdminForum; /* 6: Edit forum posts by other users */
93 };
94
95 #ifdef FOSSIL_ENABLE_TCL
96 /*
97 ** All Tcl related context information is in this structure. This structure
98
+25 -13
--- src/setup.c
+++ src/setup.c
@@ -347,15 +347,20 @@
347347
@ <tr><th valign="top">y</th>
348348
@ <td><i>Write-Unver:</i> Push unversioned files</td></tr>
349349
@ <tr><th valign="top">z</th>
350350
@ <td><i>Zip download:</i> Download a ZIP archive or tarball</td></tr>
351351
@ <tr><th valign="top">2</th>
352
- @ <td><i>Forum-Read:</i> Read Forum posts by others </td></tr>
352
+ @ <td><i>Forum-Read:</i> Read forum posts by others </td></tr>
353353
@ <tr><th valign="top">3</th>
354
- @ <td><i>Forum-Append:</i> Add new Forum posts or comments</td></tr>
354
+ @ <td><i>Forum-Append:</i> Add new forum posts</td></tr>
355355
@ <tr><th valign="top">4</th>
356
- @ <td><i>Forum-Moderator:</i> Moderate Forums</td></tr>
356
+ @ <td><i>Forum-Trusted:</i> Add pre-approved forum posts </td></tr>
357
+ @ <tr><th valign="top">5</th>
358
+ @ <td><i>Forum-Moderator:</i> Approve or disapprove forum posts</td></tr>
359
+ @ <tr><th valign="top">6</th>
360
+ @ <td><i>Forum-Supervisor:</i> \
361
+ @ Edit forum posts submitted by others</td></tr>
357362
@ </table>
358363
}
359364
360365
/*
361366
** WEBPAGE: setup_ulist_notes
@@ -678,10 +683,14 @@
678683
@ <label><input type="checkbox" name="as"%s(oa['s']) />
679684
@ Setup%s(B('s'))</label><br />
680685
}
681686
@ <label><input type="checkbox" name="aa"%s(oa['a']) />
682687
@ Admin%s(B('a'))</label><br />
688
+ @ <label><input type="checkbox" name="au"%s(oa['u']) />
689
+ @ Reader%s(B('u'))</label><br>
690
+ @ <label><input type="checkbox" name="av"%s(oa['v']) />
691
+ @ Developer%s(B('v'))</label><br />
683692
@ <label><input type="checkbox" name="ad"%s(oa['d']) />
684693
@ Delete%s(B('d'))</label><br />
685694
@ <label><input type="checkbox" name="ae"%s(oa['e']) />
686695
@ Email%s(B('e'))</label><br />
687696
@ <label><input type="checkbox" name="ap"%s(oa['p']) />
@@ -691,20 +700,17 @@
691700
@ <label><input type="checkbox" name="ao"%s(oa['o']) />
692701
@ Check-Out%s(B('o'))</label><br />
693702
@ <label><input type="checkbox" name="ah"%s(oa['h']) />
694703
@ Hyperlinks%s(B('h'))</label><br />
695704
@ <label><input type="checkbox" name="ab"%s(oa['b']) />
696
- @ Attachments%s(B('b'))</label><br />
697
- @ <label><input type="checkbox" name="au"%s(oa['u']) />
698
- @ Reader%s(B('u'))</label>
705
+ @ Attachments%s(B('b'))</label>
706
+
699707
@ </td><td><td width="40"></td><td valign="top">
700
- @ <label><input type="checkbox" name="av"%s(oa['v']) />
701
- @ Developer%s(B('v'))</label><br />
702708
@ <label><input type="checkbox" name="ag"%s(oa['g']) />
703709
@ Clone%s(B('g'))</label><br />
704710
@ <label><input type="checkbox" name="aj"%s(oa['j']) />
705
- @ Read Wiki%s(B('j'))</label><br />
711
+ @ Read Wiki%s(B('j'))</label><br>
706712
@ <label><input type="checkbox" name="af"%s(oa['f']) />
707713
@ New Wiki%s(B('f'))</label><br />
708714
@ <label><input type="checkbox" name="am"%s(oa['m']) />
709715
@ Append Wiki%s(B('m'))</label><br />
710716
@ <label><input type="checkbox" name="ak"%s(oa['k']) />
@@ -714,16 +720,17 @@
714720
@ <label><input type="checkbox" name="ar"%s(oa['r']) />
715721
@ Read Ticket%s(B('r'))</label><br />
716722
@ <label><input type="checkbox" name="an"%s(oa['n']) />
717723
@ New Tickets%s(B('n'))</label><br />
718724
@ <label><input type="checkbox" name="ac"%s(oa['c']) />
719
- @ Append To Ticket%s(B('c'))</label>
720
- @ </td><td><td width="40"></td><td valign="top">
725
+ @ Append To Ticket%s(B('c'))</label><br>
721726
@ <label><input type="checkbox" name="aw"%s(oa['w']) />
722727
@ Write Tickets%s(B('w'))</label><br />
723728
@ <label><input type="checkbox" name="aq"%s(oa['q']) />
724
- @ Moderate Tickets%s(B('q'))</label><br />
729
+ @ Moderate Tickets%s(B('q'))</label>
730
+
731
+ @ </td><td><td width="40"></td><td valign="top">
725732
@ <label><input type="checkbox" name="at"%s(oa['t']) />
726733
@ Ticket Report%s(B('t'))</label><br />
727734
@ <label><input type="checkbox" name="ax"%s(oa['x']) />
728735
@ Private%s(B('x'))</label><br />
729736
@ <label><input type="checkbox" name="ay"%s(oa['y']) />
@@ -733,19 +740,24 @@
733740
@ <label><input type="checkbox" name="a2"%s(oa['2']) />
734741
@ Read Forum%s(B('2'))</label><br />
735742
@ <label><input type="checkbox" name="a3"%s(oa['3']) />
736743
@ Write Forum%s(B('3'))</label><br />
737744
@ <label><input type="checkbox" name="a4"%s(oa['4']) />
738
- @ Moderate Forum%s(B('4'))</label>
745
+ @ WriteTrusted Forum%s(B('4'))</label><br>
746
+ @ <label><input type="checkbox" name="a5"%s(oa['5']) />
747
+ @ Moderate Forum%s(B('5'))</label><br>
748
+ @ <label><input type="checkbox" name="a6"%s(oa['6']) />
749
+ @ Supervise Forum%s(B('6'))</label>
739750
@ </td></tr>
740751
@ </table>
741752
@ </td>
742753
@ </tr>
743754
@ <tr>
744755
@ <td class="usetupEditLabel">Selected Cap.:</td>
745756
@ <td>
746757
@ <span id="usetupEditCapability">(missing JS?)</span>
758
+ @ <a href="%R/setup_ucap_list">(key)</a>
747759
@ </td>
748760
@ </tr>
749761
if( !login_is_special(zLogin) ){
750762
@ <tr>
751763
@ <td align="right">Password:</td>
752764
--- src/setup.c
+++ src/setup.c
@@ -347,15 +347,20 @@
347 @ <tr><th valign="top">y</th>
348 @ <td><i>Write-Unver:</i> Push unversioned files</td></tr>
349 @ <tr><th valign="top">z</th>
350 @ <td><i>Zip download:</i> Download a ZIP archive or tarball</td></tr>
351 @ <tr><th valign="top">2</th>
352 @ <td><i>Forum-Read:</i> Read Forum posts by others </td></tr>
353 @ <tr><th valign="top">3</th>
354 @ <td><i>Forum-Append:</i> Add new Forum posts or comments</td></tr>
355 @ <tr><th valign="top">4</th>
356 @ <td><i>Forum-Moderator:</i> Moderate Forums</td></tr>
 
 
 
 
 
357 @ </table>
358 }
359
360 /*
361 ** WEBPAGE: setup_ulist_notes
@@ -678,10 +683,14 @@
678 @ <label><input type="checkbox" name="as"%s(oa['s']) />
679 @ Setup%s(B('s'))</label><br />
680 }
681 @ <label><input type="checkbox" name="aa"%s(oa['a']) />
682 @ Admin%s(B('a'))</label><br />
 
 
 
 
683 @ <label><input type="checkbox" name="ad"%s(oa['d']) />
684 @ Delete%s(B('d'))</label><br />
685 @ <label><input type="checkbox" name="ae"%s(oa['e']) />
686 @ Email%s(B('e'))</label><br />
687 @ <label><input type="checkbox" name="ap"%s(oa['p']) />
@@ -691,20 +700,17 @@
691 @ <label><input type="checkbox" name="ao"%s(oa['o']) />
692 @ Check-Out%s(B('o'))</label><br />
693 @ <label><input type="checkbox" name="ah"%s(oa['h']) />
694 @ Hyperlinks%s(B('h'))</label><br />
695 @ <label><input type="checkbox" name="ab"%s(oa['b']) />
696 @ Attachments%s(B('b'))</label><br />
697 @ <label><input type="checkbox" name="au"%s(oa['u']) />
698 @ Reader%s(B('u'))</label>
699 @ </td><td><td width="40"></td><td valign="top">
700 @ <label><input type="checkbox" name="av"%s(oa['v']) />
701 @ Developer%s(B('v'))</label><br />
702 @ <label><input type="checkbox" name="ag"%s(oa['g']) />
703 @ Clone%s(B('g'))</label><br />
704 @ <label><input type="checkbox" name="aj"%s(oa['j']) />
705 @ Read Wiki%s(B('j'))</label><br />
706 @ <label><input type="checkbox" name="af"%s(oa['f']) />
707 @ New Wiki%s(B('f'))</label><br />
708 @ <label><input type="checkbox" name="am"%s(oa['m']) />
709 @ Append Wiki%s(B('m'))</label><br />
710 @ <label><input type="checkbox" name="ak"%s(oa['k']) />
@@ -714,16 +720,17 @@
714 @ <label><input type="checkbox" name="ar"%s(oa['r']) />
715 @ Read Ticket%s(B('r'))</label><br />
716 @ <label><input type="checkbox" name="an"%s(oa['n']) />
717 @ New Tickets%s(B('n'))</label><br />
718 @ <label><input type="checkbox" name="ac"%s(oa['c']) />
719 @ Append To Ticket%s(B('c'))</label>
720 @ </td><td><td width="40"></td><td valign="top">
721 @ <label><input type="checkbox" name="aw"%s(oa['w']) />
722 @ Write Tickets%s(B('w'))</label><br />
723 @ <label><input type="checkbox" name="aq"%s(oa['q']) />
724 @ Moderate Tickets%s(B('q'))</label><br />
 
 
725 @ <label><input type="checkbox" name="at"%s(oa['t']) />
726 @ Ticket Report%s(B('t'))</label><br />
727 @ <label><input type="checkbox" name="ax"%s(oa['x']) />
728 @ Private%s(B('x'))</label><br />
729 @ <label><input type="checkbox" name="ay"%s(oa['y']) />
@@ -733,19 +740,24 @@
733 @ <label><input type="checkbox" name="a2"%s(oa['2']) />
734 @ Read Forum%s(B('2'))</label><br />
735 @ <label><input type="checkbox" name="a3"%s(oa['3']) />
736 @ Write Forum%s(B('3'))</label><br />
737 @ <label><input type="checkbox" name="a4"%s(oa['4']) />
738 @ Moderate Forum%s(B('4'))</label>
 
 
 
 
739 @ </td></tr>
740 @ </table>
741 @ </td>
742 @ </tr>
743 @ <tr>
744 @ <td class="usetupEditLabel">Selected Cap.:</td>
745 @ <td>
746 @ <span id="usetupEditCapability">(missing JS?)</span>
 
747 @ </td>
748 @ </tr>
749 if( !login_is_special(zLogin) ){
750 @ <tr>
751 @ <td align="right">Password:</td>
752
--- src/setup.c
+++ src/setup.c
@@ -347,15 +347,20 @@
347 @ <tr><th valign="top">y</th>
348 @ <td><i>Write-Unver:</i> Push unversioned files</td></tr>
349 @ <tr><th valign="top">z</th>
350 @ <td><i>Zip download:</i> Download a ZIP archive or tarball</td></tr>
351 @ <tr><th valign="top">2</th>
352 @ <td><i>Forum-Read:</i> Read forum posts by others </td></tr>
353 @ <tr><th valign="top">3</th>
354 @ <td><i>Forum-Append:</i> Add new forum posts</td></tr>
355 @ <tr><th valign="top">4</th>
356 @ <td><i>Forum-Trusted:</i> Add pre-approved forum posts </td></tr>
357 @ <tr><th valign="top">5</th>
358 @ <td><i>Forum-Moderator:</i> Approve or disapprove forum posts</td></tr>
359 @ <tr><th valign="top">6</th>
360 @ <td><i>Forum-Supervisor:</i> \
361 @ Edit forum posts submitted by others</td></tr>
362 @ </table>
363 }
364
365 /*
366 ** WEBPAGE: setup_ulist_notes
@@ -678,10 +683,14 @@
683 @ <label><input type="checkbox" name="as"%s(oa['s']) />
684 @ Setup%s(B('s'))</label><br />
685 }
686 @ <label><input type="checkbox" name="aa"%s(oa['a']) />
687 @ Admin%s(B('a'))</label><br />
688 @ <label><input type="checkbox" name="au"%s(oa['u']) />
689 @ Reader%s(B('u'))</label><br>
690 @ <label><input type="checkbox" name="av"%s(oa['v']) />
691 @ Developer%s(B('v'))</label><br />
692 @ <label><input type="checkbox" name="ad"%s(oa['d']) />
693 @ Delete%s(B('d'))</label><br />
694 @ <label><input type="checkbox" name="ae"%s(oa['e']) />
695 @ Email%s(B('e'))</label><br />
696 @ <label><input type="checkbox" name="ap"%s(oa['p']) />
@@ -691,20 +700,17 @@
700 @ <label><input type="checkbox" name="ao"%s(oa['o']) />
701 @ Check-Out%s(B('o'))</label><br />
702 @ <label><input type="checkbox" name="ah"%s(oa['h']) />
703 @ Hyperlinks%s(B('h'))</label><br />
704 @ <label><input type="checkbox" name="ab"%s(oa['b']) />
705 @ Attachments%s(B('b'))</label>
706
 
707 @ </td><td><td width="40"></td><td valign="top">
 
 
708 @ <label><input type="checkbox" name="ag"%s(oa['g']) />
709 @ Clone%s(B('g'))</label><br />
710 @ <label><input type="checkbox" name="aj"%s(oa['j']) />
711 @ Read Wiki%s(B('j'))</label><br>
712 @ <label><input type="checkbox" name="af"%s(oa['f']) />
713 @ New Wiki%s(B('f'))</label><br />
714 @ <label><input type="checkbox" name="am"%s(oa['m']) />
715 @ Append Wiki%s(B('m'))</label><br />
716 @ <label><input type="checkbox" name="ak"%s(oa['k']) />
@@ -714,16 +720,17 @@
720 @ <label><input type="checkbox" name="ar"%s(oa['r']) />
721 @ Read Ticket%s(B('r'))</label><br />
722 @ <label><input type="checkbox" name="an"%s(oa['n']) />
723 @ New Tickets%s(B('n'))</label><br />
724 @ <label><input type="checkbox" name="ac"%s(oa['c']) />
725 @ Append To Ticket%s(B('c'))</label><br>
 
726 @ <label><input type="checkbox" name="aw"%s(oa['w']) />
727 @ Write Tickets%s(B('w'))</label><br />
728 @ <label><input type="checkbox" name="aq"%s(oa['q']) />
729 @ Moderate Tickets%s(B('q'))</label>
730
731 @ </td><td><td width="40"></td><td valign="top">
732 @ <label><input type="checkbox" name="at"%s(oa['t']) />
733 @ Ticket Report%s(B('t'))</label><br />
734 @ <label><input type="checkbox" name="ax"%s(oa['x']) />
735 @ Private%s(B('x'))</label><br />
736 @ <label><input type="checkbox" name="ay"%s(oa['y']) />
@@ -733,19 +740,24 @@
740 @ <label><input type="checkbox" name="a2"%s(oa['2']) />
741 @ Read Forum%s(B('2'))</label><br />
742 @ <label><input type="checkbox" name="a3"%s(oa['3']) />
743 @ Write Forum%s(B('3'))</label><br />
744 @ <label><input type="checkbox" name="a4"%s(oa['4']) />
745 @ WriteTrusted Forum%s(B('4'))</label><br>
746 @ <label><input type="checkbox" name="a5"%s(oa['5']) />
747 @ Moderate Forum%s(B('5'))</label><br>
748 @ <label><input type="checkbox" name="a6"%s(oa['6']) />
749 @ Supervise Forum%s(B('6'))</label>
750 @ </td></tr>
751 @ </table>
752 @ </td>
753 @ </tr>
754 @ <tr>
755 @ <td class="usetupEditLabel">Selected Cap.:</td>
756 @ <td>
757 @ <span id="usetupEditCapability">(missing JS?)</span>
758 @ <a href="%R/setup_ucap_list">(key)</a>
759 @ </td>
760 @ </tr>
761 if( !login_is_special(zLogin) ){
762 @ <tr>
763 @ <td align="right">Password:</td>
764

Keyboard Shortcuts

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