Fossil SCM
Hierarchical display of forum threads.
Commit
7da12996859b46c1c5867dd0aa2aabbc35ee318ff90fb8e4c33fe2e243fd9a2a
Parent
e9b13d68a7c9411…
1 file changed
+266
-2
+266
-2
| --- src/forum.c | ||
| +++ src/forum.c | ||
| @@ -18,10 +18,208 @@ | ||
| 18 | 18 | ** This file contains code used to generate the user forum. |
| 19 | 19 | */ |
| 20 | 20 | #include "config.h" |
| 21 | 21 | #include <assert.h> |
| 22 | 22 | #include "forum.h" |
| 23 | + | |
| 24 | +#if INTERFACE | |
| 25 | +/* | |
| 26 | +** Each instance of the following object represents a single message - | |
| 27 | +** either the initial post, an edit to a post, a reply, or an edit to | |
| 28 | +** a reply. | |
| 29 | +*/ | |
| 30 | +struct ForumEntry { | |
| 31 | + int fpid; /* rid for this entry */ | |
| 32 | + int fprev; /* zero if initial entry. non-zero if an edit */ | |
| 33 | + int firt; /* This entry replies to firt */ | |
| 34 | + int mfirt; /* Root in-reply-to */ | |
| 35 | + ForumEntry *pLeaf; /* Most recent edit for this entry */ | |
| 36 | + ForumEntry *pEdit; /* This entry is an edit of pEditee */ | |
| 37 | + ForumEntry *pNext; /* Next in chronological order */ | |
| 38 | + ForumEntry *pPrev; /* Previous in chronological order */ | |
| 39 | + ForumEntry *pDisplay; /* Next in display order */ | |
| 40 | + int nIndent; /* Number of levels of indentation for this entry */ | |
| 41 | +}; | |
| 42 | + | |
| 43 | +/* | |
| 44 | +** A single instance of the following tracks all entries for a thread. | |
| 45 | +*/ | |
| 46 | +struct ForumThread { | |
| 47 | + ForumEntry *pFirst; /* First entry in chronological order */ | |
| 48 | + ForumEntry *pLast; /* Last entry in chronological order */ | |
| 49 | + ForumEntry *pDisplay; /* Entries in display order */ | |
| 50 | + ForumEntry *pTail; /* Last on the display list */ | |
| 51 | +}; | |
| 52 | +#endif /* INTERFACE */ | |
| 53 | + | |
| 54 | +/* | |
| 55 | +** Delete a complete ForumThread and all its entries. | |
| 56 | +*/ | |
| 57 | +static void forumthread_delete(ForumThread *pThread){ | |
| 58 | + ForumEntry *pEntry, *pNext; | |
| 59 | + for(pEntry=pThread->pFirst; pEntry; pEntry = pNext){ | |
| 60 | + pNext = pEntry->pNext; | |
| 61 | + fossil_free(pEntry); | |
| 62 | + } | |
| 63 | + fossil_free(pThread); | |
| 64 | +} | |
| 65 | + | |
| 66 | +/* | |
| 67 | +** Search a ForumEntry list forwards looking for the entry with fpid | |
| 68 | +*/ | |
| 69 | +static ForumEntry *forumentry_forward(ForumEntry *p, int fpid){ | |
| 70 | + while( p && p->fpid!=fpid ) p = p->pNext; | |
| 71 | + return p; | |
| 72 | +} | |
| 73 | + | |
| 74 | +/* | |
| 75 | +** Search backwards for a ForumEntry | |
| 76 | +*/ | |
| 77 | +static ForumEntry *forumentry_backward(ForumEntry *p, int fpid){ | |
| 78 | + while( p && p->fpid!=fpid ) p = p->pPrev; | |
| 79 | + return p; | |
| 80 | +} | |
| 81 | + | |
| 82 | +/* | |
| 83 | +** Add an entry to the display list | |
| 84 | +*/ | |
| 85 | +static void forumentry_add_to_display(ForumThread *pThread, ForumEntry *p){ | |
| 86 | + if( pThread->pDisplay==0 ){ | |
| 87 | + pThread->pDisplay = p; | |
| 88 | + }else{ | |
| 89 | + pThread->pTail->pDisplay = p; | |
| 90 | + } | |
| 91 | + pThread->pTail = p; | |
| 92 | +} | |
| 93 | + | |
| 94 | +/* | |
| 95 | +** Extend the display list for pThread by adding all entries that | |
| 96 | +** reference fpid. The first such entry will be no earlier then | |
| 97 | +** entry "p". | |
| 98 | +*/ | |
| 99 | +static void forumthread_display_order( | |
| 100 | + ForumThread *pThread, | |
| 101 | + ForumEntry *p, | |
| 102 | + int fpid, | |
| 103 | + int nIndent | |
| 104 | +){ | |
| 105 | + while( p ){ | |
| 106 | + if( p->pEdit==0 && p->mfirt==fpid ){ | |
| 107 | + p->nIndent = nIndent; | |
| 108 | + forumentry_add_to_display(pThread, p); | |
| 109 | + forumthread_display_order(pThread, p->pNext, p->fpid, nIndent+1); | |
| 110 | + } | |
| 111 | + p = p->pNext; | |
| 112 | + } | |
| 113 | +} | |
| 114 | + | |
| 115 | +/* | |
| 116 | +** Construct a ForumThread object given the root record id. | |
| 117 | +*/ | |
| 118 | +static ForumThread *forumthread_create(int froot){ | |
| 119 | + ForumThread *pThread; | |
| 120 | + ForumEntry *pEntry; | |
| 121 | + Stmt q; | |
| 122 | + pThread = fossil_malloc( sizeof(*pThread) ); | |
| 123 | + memset(pThread, 0, sizeof(*pThread)); | |
| 124 | + db_prepare(&q, "SELECT fpid, firt, fprev FROM forumpost" | |
| 125 | + " WHERE froot=%d ORDER BY fmtime", froot); | |
| 126 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 127 | + pEntry = fossil_malloc( sizeof(*pEntry) ); | |
| 128 | + memset(pEntry, 0, sizeof(*pEntry)); | |
| 129 | + pEntry->fpid = db_column_int(&q, 0); | |
| 130 | + pEntry->firt = db_column_int(&q, 1); | |
| 131 | + pEntry->fprev = db_column_int(&q, 2); | |
| 132 | + pEntry->mfirt = pEntry->firt; | |
| 133 | + pEntry->pPrev = pThread->pLast; | |
| 134 | + pEntry->pNext = 0; | |
| 135 | + if( pThread->pLast==0 ){ | |
| 136 | + pThread->pFirst = pEntry; | |
| 137 | + }else{ | |
| 138 | + pThread->pLast->pNext = pEntry; | |
| 139 | + } | |
| 140 | + pThread->pLast = pEntry; | |
| 141 | + } | |
| 142 | + db_finalize(&q); | |
| 143 | + | |
| 144 | + /* Establish which entries are the latest edit. After this loop | |
| 145 | + ** completes, entries that have non-NULL pLeaf should not be | |
| 146 | + ** displayed. | |
| 147 | + */ | |
| 148 | + for(pEntry=pThread->pFirst; pEntry; pEntry=pEntry->pNext){ | |
| 149 | + if( pEntry->fprev ){ | |
| 150 | + ForumEntry *pBase, *p; | |
| 151 | + pBase = p = forumentry_backward(pEntry->pPrev, pEntry->fprev); | |
| 152 | + pEntry->pEdit = p; | |
| 153 | + while( p ){ | |
| 154 | + pBase = p; | |
| 155 | + p->pLeaf = pEntry; | |
| 156 | + p = pBase->pEdit; | |
| 157 | + } | |
| 158 | + for(p=pEntry->pNext; p; p=p->pNext){ | |
| 159 | + if( p->mfirt==pEntry->fpid ) p->mfirt = pBase->mfirt; | |
| 160 | + } | |
| 161 | + } | |
| 162 | + } | |
| 163 | + | |
| 164 | + /* Compute the display order */ | |
| 165 | + pEntry = pThread->pFirst; | |
| 166 | + pEntry->nIndent = 1; | |
| 167 | + forumentry_add_to_display(pThread, pEntry); | |
| 168 | + forumthread_display_order(pThread, pEntry, pEntry->fpid, 2); | |
| 169 | + | |
| 170 | + /* Return the result */ | |
| 171 | + return pThread; | |
| 172 | +} | |
| 173 | + | |
| 174 | +/* | |
| 175 | +** COMMAND: test-forumthread | |
| 176 | +** | |
| 177 | +** Usage: %fossil test-forumthread THREADID | |
| 178 | +** | |
| 179 | +** Display a summary of all messages on a thread. | |
| 180 | +*/ | |
| 181 | +void forumthread_cmd(void){ | |
| 182 | + int fpid; | |
| 183 | + int froot; | |
| 184 | + const char *zName; | |
| 185 | + ForumThread *pThread; | |
| 186 | + ForumEntry *p; | |
| 187 | + | |
| 188 | + db_find_and_open_repository(0,0); | |
| 189 | + verify_all_options(); | |
| 190 | + if( g.argc!=3 ) usage("THREADID"); | |
| 191 | + zName = g.argv[2]; | |
| 192 | + fpid = symbolic_name_to_rid(zName, "f"); | |
| 193 | + if( fpid<=0 ){ | |
| 194 | + fossil_fatal("Unknown or ambiguous forum id: \"%s\"", zName); | |
| 195 | + } | |
| 196 | + froot = db_int(0, "SELECT froot FROM forumpost WHERE fpid=%d", fpid); | |
| 197 | + if( froot==0 ){ | |
| 198 | + fossil_fatal("Not a forum post: \"%s\"", zName); | |
| 199 | + } | |
| 200 | + fossil_print("fpid = %d\n", fpid); | |
| 201 | + fossil_print("froot = %d\n", froot); | |
| 202 | + pThread = forumthread_create(froot); | |
| 203 | + fossil_print("Chronological:\n"); | |
| 204 | + /* 123456789 123456789 123456789 123456789 123456789 */ | |
| 205 | + fossil_print(" fpid firt fprev mfirt pLeaf\n"); | |
| 206 | + for(p=pThread->pFirst; p; p=p->pNext){ | |
| 207 | + fossil_print("%9d %9d %9d %9d %9d\n", | |
| 208 | + p->fpid, p->firt, p->fprev, p->mfirt, p->pLeaf ? p->pLeaf->fpid : 0); | |
| 209 | + } | |
| 210 | + fossil_print("\nDisplay\n"); | |
| 211 | + for(p=pThread->pDisplay; p; p=p->pDisplay){ | |
| 212 | + fossil_print("%*s", (p->nIndent-1)*3, ""); | |
| 213 | + if( p->pLeaf ){ | |
| 214 | + fossil_print("%d->%d\n", p->fpid, p->pLeaf->fpid); | |
| 215 | + }else{ | |
| 216 | + fossil_print("%d\n", p->fpid); | |
| 217 | + } | |
| 218 | + } | |
| 219 | + forumthread_delete(pThread); | |
| 220 | +} | |
| 23 | 221 | |
| 24 | 222 | /* |
| 25 | 223 | ** Render a forum post for display |
| 26 | 224 | */ |
| 27 | 225 | void forum_render( |
| @@ -50,11 +248,11 @@ | ||
| 50 | 248 | } |
| 51 | 249 | |
| 52 | 250 | /* |
| 53 | 251 | ** Display all posts in a forum thread in chronological order |
| 54 | 252 | */ |
| 55 | -static void forum_thread_chronological(int froot){ | |
| 253 | +static void forum_display_chronological(int froot, int target){ | |
| 56 | 254 | Stmt q; |
| 57 | 255 | int i = 0; |
| 58 | 256 | db_prepare(&q, |
| 59 | 257 | "SELECT fpid, fprev, firt, uuid, datetime(fmtime,'unixepoch')\n" |
| 60 | 258 | " FROM forumpost, blob\n" |
| @@ -115,10 +313,72 @@ | ||
| 115 | 313 | } |
| 116 | 314 | manifest_destroy(pPost); |
| 117 | 315 | } |
| 118 | 316 | db_finalize(&q); |
| 119 | 317 | } |
| 318 | + | |
| 319 | +/* | |
| 320 | +** Display all messages in a forumthread with indentation. | |
| 321 | +*/ | |
| 322 | +static void forum_display(int froot, int target){ | |
| 323 | + ForumThread *pThread; | |
| 324 | + ForumEntry *p; | |
| 325 | + Manifest *pPost; | |
| 326 | + int fpid; | |
| 327 | + char *zDate; | |
| 328 | + char *zUuid; | |
| 329 | + | |
| 330 | + pThread = forumthread_create(froot); | |
| 331 | + for(p=pThread->pDisplay; p; p=p->pDisplay){ | |
| 332 | + @ <div style='margin-left: %d((p->nIndent-1)*3)ex;'> | |
| 333 | + fpid = p->pLeaf ? p->pLeaf->fpid : p->fpid; | |
| 334 | + pPost = manifest_get(fpid, CFTYPE_FORUM, 0); | |
| 335 | + if( pPost==0 ) continue; | |
| 336 | + if( pPost->zThreadTitle ){ | |
| 337 | + @ <h1>%h(pPost->zThreadTitle)</h1> | |
| 338 | + } | |
| 339 | + zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate); | |
| 340 | + @ <p>By %h(pPost->zUser) on %h(zDate) (%d(fpid)) | |
| 341 | + fossil_free(zDate); | |
| 342 | + zUuid = rid_to_uuid(fpid); | |
| 343 | + if( g.perm.Debug ){ | |
| 344 | + @ <span class="debug">\ | |
| 345 | + @ <a href="%R/artifact/%h(zUuid)">artifact</a></span> | |
| 346 | + } | |
| 347 | + forum_render(0, pPost->zMimetype, pPost->zWiki); | |
| 348 | + if( g.perm.WrForum ){ | |
| 349 | + int sameUser = login_is_individual() | |
| 350 | + && fossil_strcmp(pPost->zUser, g.zLogin)==0; | |
| 351 | + int isPrivate = content_is_private(fpid); | |
| 352 | + @ <p><form action="%R/forumedit" method="POST"> | |
| 353 | + @ <input type="hidden" name="fpid" value="%s(zUuid)"> | |
| 354 | + if( !isPrivate ){ | |
| 355 | + /* Reply and Edit are only available if the post has already | |
| 356 | + ** been approved */ | |
| 357 | + @ <input type="submit" name="reply" value="Reply"> | |
| 358 | + if( g.perm.Admin || sameUser ){ | |
| 359 | + @ <input type="submit" name="edit" value="Edit"> | |
| 360 | + @ <input type="submit" name="nullout" value="Delete"> | |
| 361 | + } | |
| 362 | + }else if( g.perm.ModForum ){ | |
| 363 | + /* Provide moderators with moderation buttons for posts that | |
| 364 | + ** are pending moderation */ | |
| 365 | + @ <input type="submit" name="approve" value="Approve"> | |
| 366 | + @ <input type="submit" name="reject" value="Reject"> | |
| 367 | + }else if( sameUser ){ | |
| 368 | + /* A post that is pending moderation can be deleted by the | |
| 369 | + ** person who originally submitted the post */ | |
| 370 | + @ <input type="submit" name="reject" value="Delete"> | |
| 371 | + } | |
| 372 | + @ </form></p> | |
| 373 | + } | |
| 374 | + manifest_destroy(pPost); | |
| 375 | + fossil_free(zUuid); | |
| 376 | + @ </div> | |
| 377 | + } | |
| 378 | + forumthread_delete(pThread); | |
| 379 | +} | |
| 120 | 380 | |
| 121 | 381 | /* |
| 122 | 382 | ** WEBPAGE: forumthread |
| 123 | 383 | ** |
| 124 | 384 | ** Show all forum messages associated with a particular message thread. |
| @@ -146,11 +406,15 @@ | ||
| 146 | 406 | style_header("Forum"); |
| 147 | 407 | froot = db_int(0, "SELECT froot FROM forumpost WHERE fpid=%d", fpid); |
| 148 | 408 | if( froot==0 ){ |
| 149 | 409 | webpage_error("Not a forum post: \"%s\"", zName); |
| 150 | 410 | } |
| 151 | - forum_thread_chronological(froot); | |
| 411 | + if( P("t") ){ | |
| 412 | + forum_display_chronological(froot, fpid); | |
| 413 | + }else{ | |
| 414 | + forum_display(froot, fpid); | |
| 415 | + } | |
| 152 | 416 | style_footer(); |
| 153 | 417 | } |
| 154 | 418 | |
| 155 | 419 | /* |
| 156 | 420 | ** Return true if a forum post should be moderated. |
| 157 | 421 |
| --- src/forum.c | |
| +++ src/forum.c | |
| @@ -18,10 +18,208 @@ | |
| 18 | ** This file contains code used to generate the user forum. |
| 19 | */ |
| 20 | #include "config.h" |
| 21 | #include <assert.h> |
| 22 | #include "forum.h" |
| 23 | |
| 24 | /* |
| 25 | ** Render a forum post for display |
| 26 | */ |
| 27 | void forum_render( |
| @@ -50,11 +248,11 @@ | |
| 50 | } |
| 51 | |
| 52 | /* |
| 53 | ** Display all posts in a forum thread in chronological order |
| 54 | */ |
| 55 | static void forum_thread_chronological(int froot){ |
| 56 | Stmt q; |
| 57 | int i = 0; |
| 58 | db_prepare(&q, |
| 59 | "SELECT fpid, fprev, firt, uuid, datetime(fmtime,'unixepoch')\n" |
| 60 | " FROM forumpost, blob\n" |
| @@ -115,10 +313,72 @@ | |
| 115 | } |
| 116 | manifest_destroy(pPost); |
| 117 | } |
| 118 | db_finalize(&q); |
| 119 | } |
| 120 | |
| 121 | /* |
| 122 | ** WEBPAGE: forumthread |
| 123 | ** |
| 124 | ** Show all forum messages associated with a particular message thread. |
| @@ -146,11 +406,15 @@ | |
| 146 | style_header("Forum"); |
| 147 | froot = db_int(0, "SELECT froot FROM forumpost WHERE fpid=%d", fpid); |
| 148 | if( froot==0 ){ |
| 149 | webpage_error("Not a forum post: \"%s\"", zName); |
| 150 | } |
| 151 | forum_thread_chronological(froot); |
| 152 | style_footer(); |
| 153 | } |
| 154 | |
| 155 | /* |
| 156 | ** Return true if a forum post should be moderated. |
| 157 |
| --- src/forum.c | |
| +++ src/forum.c | |
| @@ -18,10 +18,208 @@ | |
| 18 | ** This file contains code used to generate the user forum. |
| 19 | */ |
| 20 | #include "config.h" |
| 21 | #include <assert.h> |
| 22 | #include "forum.h" |
| 23 | |
| 24 | #if INTERFACE |
| 25 | /* |
| 26 | ** Each instance of the following object represents a single message - |
| 27 | ** either the initial post, an edit to a post, a reply, or an edit to |
| 28 | ** a reply. |
| 29 | */ |
| 30 | struct ForumEntry { |
| 31 | int fpid; /* rid for this entry */ |
| 32 | int fprev; /* zero if initial entry. non-zero if an edit */ |
| 33 | int firt; /* This entry replies to firt */ |
| 34 | int mfirt; /* Root in-reply-to */ |
| 35 | ForumEntry *pLeaf; /* Most recent edit for this entry */ |
| 36 | ForumEntry *pEdit; /* This entry is an edit of pEditee */ |
| 37 | ForumEntry *pNext; /* Next in chronological order */ |
| 38 | ForumEntry *pPrev; /* Previous in chronological order */ |
| 39 | ForumEntry *pDisplay; /* Next in display order */ |
| 40 | int nIndent; /* Number of levels of indentation for this entry */ |
| 41 | }; |
| 42 | |
| 43 | /* |
| 44 | ** A single instance of the following tracks all entries for a thread. |
| 45 | */ |
| 46 | struct ForumThread { |
| 47 | ForumEntry *pFirst; /* First entry in chronological order */ |
| 48 | ForumEntry *pLast; /* Last entry in chronological order */ |
| 49 | ForumEntry *pDisplay; /* Entries in display order */ |
| 50 | ForumEntry *pTail; /* Last on the display list */ |
| 51 | }; |
| 52 | #endif /* INTERFACE */ |
| 53 | |
| 54 | /* |
| 55 | ** Delete a complete ForumThread and all its entries. |
| 56 | */ |
| 57 | static void forumthread_delete(ForumThread *pThread){ |
| 58 | ForumEntry *pEntry, *pNext; |
| 59 | for(pEntry=pThread->pFirst; pEntry; pEntry = pNext){ |
| 60 | pNext = pEntry->pNext; |
| 61 | fossil_free(pEntry); |
| 62 | } |
| 63 | fossil_free(pThread); |
| 64 | } |
| 65 | |
| 66 | /* |
| 67 | ** Search a ForumEntry list forwards looking for the entry with fpid |
| 68 | */ |
| 69 | static ForumEntry *forumentry_forward(ForumEntry *p, int fpid){ |
| 70 | while( p && p->fpid!=fpid ) p = p->pNext; |
| 71 | return p; |
| 72 | } |
| 73 | |
| 74 | /* |
| 75 | ** Search backwards for a ForumEntry |
| 76 | */ |
| 77 | static ForumEntry *forumentry_backward(ForumEntry *p, int fpid){ |
| 78 | while( p && p->fpid!=fpid ) p = p->pPrev; |
| 79 | return p; |
| 80 | } |
| 81 | |
| 82 | /* |
| 83 | ** Add an entry to the display list |
| 84 | */ |
| 85 | static void forumentry_add_to_display(ForumThread *pThread, ForumEntry *p){ |
| 86 | if( pThread->pDisplay==0 ){ |
| 87 | pThread->pDisplay = p; |
| 88 | }else{ |
| 89 | pThread->pTail->pDisplay = p; |
| 90 | } |
| 91 | pThread->pTail = p; |
| 92 | } |
| 93 | |
| 94 | /* |
| 95 | ** Extend the display list for pThread by adding all entries that |
| 96 | ** reference fpid. The first such entry will be no earlier then |
| 97 | ** entry "p". |
| 98 | */ |
| 99 | static void forumthread_display_order( |
| 100 | ForumThread *pThread, |
| 101 | ForumEntry *p, |
| 102 | int fpid, |
| 103 | int nIndent |
| 104 | ){ |
| 105 | while( p ){ |
| 106 | if( p->pEdit==0 && p->mfirt==fpid ){ |
| 107 | p->nIndent = nIndent; |
| 108 | forumentry_add_to_display(pThread, p); |
| 109 | forumthread_display_order(pThread, p->pNext, p->fpid, nIndent+1); |
| 110 | } |
| 111 | p = p->pNext; |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | /* |
| 116 | ** Construct a ForumThread object given the root record id. |
| 117 | */ |
| 118 | static ForumThread *forumthread_create(int froot){ |
| 119 | ForumThread *pThread; |
| 120 | ForumEntry *pEntry; |
| 121 | Stmt q; |
| 122 | pThread = fossil_malloc( sizeof(*pThread) ); |
| 123 | memset(pThread, 0, sizeof(*pThread)); |
| 124 | db_prepare(&q, "SELECT fpid, firt, fprev FROM forumpost" |
| 125 | " WHERE froot=%d ORDER BY fmtime", froot); |
| 126 | while( db_step(&q)==SQLITE_ROW ){ |
| 127 | pEntry = fossil_malloc( sizeof(*pEntry) ); |
| 128 | memset(pEntry, 0, sizeof(*pEntry)); |
| 129 | pEntry->fpid = db_column_int(&q, 0); |
| 130 | pEntry->firt = db_column_int(&q, 1); |
| 131 | pEntry->fprev = db_column_int(&q, 2); |
| 132 | pEntry->mfirt = pEntry->firt; |
| 133 | pEntry->pPrev = pThread->pLast; |
| 134 | pEntry->pNext = 0; |
| 135 | if( pThread->pLast==0 ){ |
| 136 | pThread->pFirst = pEntry; |
| 137 | }else{ |
| 138 | pThread->pLast->pNext = pEntry; |
| 139 | } |
| 140 | pThread->pLast = pEntry; |
| 141 | } |
| 142 | db_finalize(&q); |
| 143 | |
| 144 | /* Establish which entries are the latest edit. After this loop |
| 145 | ** completes, entries that have non-NULL pLeaf should not be |
| 146 | ** displayed. |
| 147 | */ |
| 148 | for(pEntry=pThread->pFirst; pEntry; pEntry=pEntry->pNext){ |
| 149 | if( pEntry->fprev ){ |
| 150 | ForumEntry *pBase, *p; |
| 151 | pBase = p = forumentry_backward(pEntry->pPrev, pEntry->fprev); |
| 152 | pEntry->pEdit = p; |
| 153 | while( p ){ |
| 154 | pBase = p; |
| 155 | p->pLeaf = pEntry; |
| 156 | p = pBase->pEdit; |
| 157 | } |
| 158 | for(p=pEntry->pNext; p; p=p->pNext){ |
| 159 | if( p->mfirt==pEntry->fpid ) p->mfirt = pBase->mfirt; |
| 160 | } |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | /* Compute the display order */ |
| 165 | pEntry = pThread->pFirst; |
| 166 | pEntry->nIndent = 1; |
| 167 | forumentry_add_to_display(pThread, pEntry); |
| 168 | forumthread_display_order(pThread, pEntry, pEntry->fpid, 2); |
| 169 | |
| 170 | /* Return the result */ |
| 171 | return pThread; |
| 172 | } |
| 173 | |
| 174 | /* |
| 175 | ** COMMAND: test-forumthread |
| 176 | ** |
| 177 | ** Usage: %fossil test-forumthread THREADID |
| 178 | ** |
| 179 | ** Display a summary of all messages on a thread. |
| 180 | */ |
| 181 | void forumthread_cmd(void){ |
| 182 | int fpid; |
| 183 | int froot; |
| 184 | const char *zName; |
| 185 | ForumThread *pThread; |
| 186 | ForumEntry *p; |
| 187 | |
| 188 | db_find_and_open_repository(0,0); |
| 189 | verify_all_options(); |
| 190 | if( g.argc!=3 ) usage("THREADID"); |
| 191 | zName = g.argv[2]; |
| 192 | fpid = symbolic_name_to_rid(zName, "f"); |
| 193 | if( fpid<=0 ){ |
| 194 | fossil_fatal("Unknown or ambiguous forum id: \"%s\"", zName); |
| 195 | } |
| 196 | froot = db_int(0, "SELECT froot FROM forumpost WHERE fpid=%d", fpid); |
| 197 | if( froot==0 ){ |
| 198 | fossil_fatal("Not a forum post: \"%s\"", zName); |
| 199 | } |
| 200 | fossil_print("fpid = %d\n", fpid); |
| 201 | fossil_print("froot = %d\n", froot); |
| 202 | pThread = forumthread_create(froot); |
| 203 | fossil_print("Chronological:\n"); |
| 204 | /* 123456789 123456789 123456789 123456789 123456789 */ |
| 205 | fossil_print(" fpid firt fprev mfirt pLeaf\n"); |
| 206 | for(p=pThread->pFirst; p; p=p->pNext){ |
| 207 | fossil_print("%9d %9d %9d %9d %9d\n", |
| 208 | p->fpid, p->firt, p->fprev, p->mfirt, p->pLeaf ? p->pLeaf->fpid : 0); |
| 209 | } |
| 210 | fossil_print("\nDisplay\n"); |
| 211 | for(p=pThread->pDisplay; p; p=p->pDisplay){ |
| 212 | fossil_print("%*s", (p->nIndent-1)*3, ""); |
| 213 | if( p->pLeaf ){ |
| 214 | fossil_print("%d->%d\n", p->fpid, p->pLeaf->fpid); |
| 215 | }else{ |
| 216 | fossil_print("%d\n", p->fpid); |
| 217 | } |
| 218 | } |
| 219 | forumthread_delete(pThread); |
| 220 | } |
| 221 | |
| 222 | /* |
| 223 | ** Render a forum post for display |
| 224 | */ |
| 225 | void forum_render( |
| @@ -50,11 +248,11 @@ | |
| 248 | } |
| 249 | |
| 250 | /* |
| 251 | ** Display all posts in a forum thread in chronological order |
| 252 | */ |
| 253 | static void forum_display_chronological(int froot, int target){ |
| 254 | Stmt q; |
| 255 | int i = 0; |
| 256 | db_prepare(&q, |
| 257 | "SELECT fpid, fprev, firt, uuid, datetime(fmtime,'unixepoch')\n" |
| 258 | " FROM forumpost, blob\n" |
| @@ -115,10 +313,72 @@ | |
| 313 | } |
| 314 | manifest_destroy(pPost); |
| 315 | } |
| 316 | db_finalize(&q); |
| 317 | } |
| 318 | |
| 319 | /* |
| 320 | ** Display all messages in a forumthread with indentation. |
| 321 | */ |
| 322 | static void forum_display(int froot, int target){ |
| 323 | ForumThread *pThread; |
| 324 | ForumEntry *p; |
| 325 | Manifest *pPost; |
| 326 | int fpid; |
| 327 | char *zDate; |
| 328 | char *zUuid; |
| 329 | |
| 330 | pThread = forumthread_create(froot); |
| 331 | for(p=pThread->pDisplay; p; p=p->pDisplay){ |
| 332 | @ <div style='margin-left: %d((p->nIndent-1)*3)ex;'> |
| 333 | fpid = p->pLeaf ? p->pLeaf->fpid : p->fpid; |
| 334 | pPost = manifest_get(fpid, CFTYPE_FORUM, 0); |
| 335 | if( pPost==0 ) continue; |
| 336 | if( pPost->zThreadTitle ){ |
| 337 | @ <h1>%h(pPost->zThreadTitle)</h1> |
| 338 | } |
| 339 | zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate); |
| 340 | @ <p>By %h(pPost->zUser) on %h(zDate) (%d(fpid)) |
| 341 | fossil_free(zDate); |
| 342 | zUuid = rid_to_uuid(fpid); |
| 343 | if( g.perm.Debug ){ |
| 344 | @ <span class="debug">\ |
| 345 | @ <a href="%R/artifact/%h(zUuid)">artifact</a></span> |
| 346 | } |
| 347 | forum_render(0, pPost->zMimetype, pPost->zWiki); |
| 348 | if( g.perm.WrForum ){ |
| 349 | int sameUser = login_is_individual() |
| 350 | && fossil_strcmp(pPost->zUser, g.zLogin)==0; |
| 351 | int isPrivate = content_is_private(fpid); |
| 352 | @ <p><form action="%R/forumedit" method="POST"> |
| 353 | @ <input type="hidden" name="fpid" value="%s(zUuid)"> |
| 354 | if( !isPrivate ){ |
| 355 | /* Reply and Edit are only available if the post has already |
| 356 | ** been approved */ |
| 357 | @ <input type="submit" name="reply" value="Reply"> |
| 358 | if( g.perm.Admin || sameUser ){ |
| 359 | @ <input type="submit" name="edit" value="Edit"> |
| 360 | @ <input type="submit" name="nullout" value="Delete"> |
| 361 | } |
| 362 | }else if( g.perm.ModForum ){ |
| 363 | /* Provide moderators with moderation buttons for posts that |
| 364 | ** are pending moderation */ |
| 365 | @ <input type="submit" name="approve" value="Approve"> |
| 366 | @ <input type="submit" name="reject" value="Reject"> |
| 367 | }else if( sameUser ){ |
| 368 | /* A post that is pending moderation can be deleted by the |
| 369 | ** person who originally submitted the post */ |
| 370 | @ <input type="submit" name="reject" value="Delete"> |
| 371 | } |
| 372 | @ </form></p> |
| 373 | } |
| 374 | manifest_destroy(pPost); |
| 375 | fossil_free(zUuid); |
| 376 | @ </div> |
| 377 | } |
| 378 | forumthread_delete(pThread); |
| 379 | } |
| 380 | |
| 381 | /* |
| 382 | ** WEBPAGE: forumthread |
| 383 | ** |
| 384 | ** Show all forum messages associated with a particular message thread. |
| @@ -146,11 +406,15 @@ | |
| 406 | style_header("Forum"); |
| 407 | froot = db_int(0, "SELECT froot FROM forumpost WHERE fpid=%d", fpid); |
| 408 | if( froot==0 ){ |
| 409 | webpage_error("Not a forum post: \"%s\"", zName); |
| 410 | } |
| 411 | if( P("t") ){ |
| 412 | forum_display_chronological(froot, fpid); |
| 413 | }else{ |
| 414 | forum_display(froot, fpid); |
| 415 | } |
| 416 | style_footer(); |
| 417 | } |
| 418 | |
| 419 | /* |
| 420 | ** Return true if a forum post should be moderated. |
| 421 |