| | @@ -30,10 +30,11 @@ |
| 30 | 30 | struct ForumEntry { |
| 31 | 31 | int fpid; /* rid for this entry */ |
| 32 | 32 | int fprev; /* zero if initial entry. non-zero if an edit */ |
| 33 | 33 | int firt; /* This entry replies to firt */ |
| 34 | 34 | int mfirt; /* Root in-reply-to */ |
| 35 | + char *zUuid; /* Artifact hash */ |
| 35 | 36 | ForumEntry *pLeaf; /* Most recent edit for this entry */ |
| 36 | 37 | ForumEntry *pEdit; /* This entry is an edit of pEditee */ |
| 37 | 38 | ForumEntry *pNext; /* Next in chronological order */ |
| 38 | 39 | ForumEntry *pPrev; /* Previous in chronological order */ |
| 39 | 40 | ForumEntry *pDisplay; /* Next in display order */ |
| | @@ -56,10 +57,11 @@ |
| 56 | 57 | */ |
| 57 | 58 | static void forumthread_delete(ForumThread *pThread){ |
| 58 | 59 | ForumEntry *pEntry, *pNext; |
| 59 | 60 | for(pEntry=pThread->pFirst; pEntry; pEntry = pNext){ |
| 60 | 61 | pNext = pEntry->pNext; |
| 62 | + fossil_free(pEntry->zUuid); |
| 61 | 63 | fossil_free(pEntry); |
| 62 | 64 | } |
| 63 | 65 | fossil_free(pThread); |
| 64 | 66 | } |
| 65 | 67 | |
| | @@ -115,24 +117,29 @@ |
| 115 | 117 | } |
| 116 | 118 | |
| 117 | 119 | /* |
| 118 | 120 | ** Construct a ForumThread object given the root record id. |
| 119 | 121 | */ |
| 120 | | -static ForumThread *forumthread_create(int froot){ |
| 122 | +static ForumThread *forumthread_create(int froot, int computeHierarchy){ |
| 121 | 123 | ForumThread *pThread; |
| 122 | 124 | ForumEntry *pEntry; |
| 123 | 125 | Stmt q; |
| 124 | 126 | pThread = fossil_malloc( sizeof(*pThread) ); |
| 125 | 127 | memset(pThread, 0, sizeof(*pThread)); |
| 126 | | - db_prepare(&q, "SELECT fpid, firt, fprev FROM forumpost" |
| 127 | | - " WHERE froot=%d ORDER BY fmtime", froot); |
| 128 | + db_prepare(&q, |
| 129 | + "SELECT fpid, firt, fprev, (SELECT uuid FROM blob WHERE rid=fpid)" |
| 130 | + " FROM forumpost" |
| 131 | + " WHERE froot=%d ORDER BY fmtime", |
| 132 | + froot |
| 133 | + ); |
| 128 | 134 | while( db_step(&q)==SQLITE_ROW ){ |
| 129 | 135 | pEntry = fossil_malloc( sizeof(*pEntry) ); |
| 130 | 136 | memset(pEntry, 0, sizeof(*pEntry)); |
| 131 | 137 | pEntry->fpid = db_column_int(&q, 0); |
| 132 | 138 | pEntry->firt = db_column_int(&q, 1); |
| 133 | 139 | pEntry->fprev = db_column_int(&q, 2); |
| 140 | + pEntry->zUuid = fossil_strdup(db_column_text(&q,3)); |
| 134 | 141 | pEntry->mfirt = pEntry->firt; |
| 135 | 142 | pEntry->pPrev = pThread->pLast; |
| 136 | 143 | pEntry->pNext = 0; |
| 137 | 144 | if( pThread->pLast==0 ){ |
| 138 | 145 | pThread->pFirst = pEntry; |
| | @@ -161,15 +168,17 @@ |
| 161 | 168 | if( p->mfirt==pEntry->fpid ) p->mfirt = pBase->fpid; |
| 162 | 169 | } |
| 163 | 170 | } |
| 164 | 171 | } |
| 165 | 172 | |
| 166 | | - /* Compute the display order */ |
| 167 | | - pEntry = pThread->pFirst; |
| 168 | | - pEntry->nIndent = 1; |
| 169 | | - forumentry_add_to_display(pThread, pEntry); |
| 170 | | - forumthread_display_order(pThread, pEntry, pEntry->fpid, 2); |
| 173 | + if( computeHierarchy ){ |
| 174 | + /* Compute the hierarchical display order */ |
| 175 | + pEntry = pThread->pFirst; |
| 176 | + pEntry->nIndent = 1; |
| 177 | + forumentry_add_to_display(pThread, pEntry); |
| 178 | + forumthread_display_order(pThread, pEntry, pEntry->fpid, 2); |
| 179 | + } |
| 171 | 180 | |
| 172 | 181 | /* Return the result */ |
| 173 | 182 | return pThread; |
| 174 | 183 | } |
| 175 | 184 | |
| | @@ -199,11 +208,11 @@ |
| 199 | 208 | if( froot==0 ){ |
| 200 | 209 | fossil_fatal("Not a forum post: \"%s\"", zName); |
| 201 | 210 | } |
| 202 | 211 | fossil_print("fpid = %d\n", fpid); |
| 203 | 212 | fossil_print("froot = %d\n", froot); |
| 204 | | - pThread = forumthread_create(froot); |
| 213 | + pThread = forumthread_create(froot, 1); |
| 205 | 214 | fossil_print("Chronological:\n"); |
| 206 | 215 | /* 123456789 123456789 123456789 123456789 123456789 */ |
| 207 | 216 | fossil_print(" fpid firt fprev mfirt pLeaf\n"); |
| 208 | 217 | for(p=pThread->pFirst; p; p=p->pNext){ |
| 209 | 218 | fossil_print("%9d %9d %9d %9d %9d\n", |
| | @@ -256,42 +265,151 @@ |
| 256 | 265 | |
| 257 | 266 | /* |
| 258 | 267 | ** Display all posts in a forum thread in chronological order |
| 259 | 268 | */ |
| 260 | 269 | static void forum_display_chronological(int froot, int target){ |
| 261 | | - Stmt q; |
| 262 | | - db_prepare(&q, |
| 263 | | - "SELECT fpid, fprev, firt, uuid, datetime(fmtime,'unixepoch')\n" |
| 264 | | - " FROM forumpost, blob\n" |
| 265 | | - " WHERE froot=%d AND rid=fpid\n" |
| 266 | | - " ORDER BY fmtime", froot); |
| 267 | | - while( db_step(&q)==SQLITE_ROW ){ |
| 268 | | - int fpid = db_column_int(&q, 0); |
| 269 | | - int fprev = db_column_int(&q, 1); |
| 270 | | - int firt = db_column_int(&q, 2); |
| 271 | | - const char *zUuid = db_column_text(&q, 3); |
| 272 | | - const char *zDate = db_column_text(&q, 4); |
| 273 | | - Manifest *pPost = manifest_get(fpid, CFTYPE_FORUM, 0); |
| 270 | + ForumThread *pThread = forumthread_create(froot, 0); |
| 271 | + ForumEntry *p; |
| 272 | + for(p=pThread->pFirst; p; p=p->pNext){ |
| 273 | + char *zDate; |
| 274 | + Manifest *pPost; |
| 275 | + |
| 276 | + pPost = manifest_get(p->fpid, CFTYPE_FORUM, 0); |
| 274 | 277 | if( pPost==0 ) continue; |
| 275 | | - if( fpid==target ){ |
| 276 | | - @ <div id="forum%d(fpid)" class="forumTime forumSel"> |
| 278 | + if( p->fpid==target ){ |
| 279 | + @ <div id="forum%d(p->fpid)" class="forumTime forumSel"> |
| 280 | + }else if( p->pLeaf!=0 ){ |
| 281 | + @ <div id="forum%d(p->fpid)" class="forumTime forumObs"> |
| 282 | + }else{ |
| 283 | + @ <div id="forum%d(p->fpid)" class="forumTime"> |
| 284 | + } |
| 285 | + if( pPost->zThreadTitle ){ |
| 286 | + @ <h1>%h(pPost->zThreadTitle)</h1> |
| 287 | + } |
| 288 | + zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate); |
| 289 | + @ <p>By %h(pPost->zUser) on %h(zDate) (%d(p->fpid)) |
| 290 | + fossil_free(zDate); |
| 291 | + if( p->pEdit ){ |
| 292 | + @ edit of %z(href("%R/forumthread/%S?t",p->pEdit->zUuid))%d(p->fprev)</a> |
| 293 | + } |
| 294 | + if( p->firt ){ |
| 295 | + ForumEntry *pIrt = p->pPrev; |
| 296 | + while( pIrt && pIrt->fpid!=p->firt ) pIrt = pIrt->pPrev; |
| 297 | + if( pIrt ){ |
| 298 | + @ reply to %z(href("%R/forumthread/%S?t",pIrt->zUuid))%d(p->firt)</a> |
| 299 | + } |
| 300 | + } |
| 301 | + if( p->pLeaf ){ |
| 302 | + @ updated by %z(href("%R/forumthread/%S?t",p->pLeaf->zUuid))\ |
| 303 | + @ %d(p->pLeaf->fpid)</a> |
| 304 | + } |
| 305 | + if( g.perm.Debug ){ |
| 306 | + @ <span class="debug">\ |
| 307 | + @ <a href="%R/artifact/%h(p->zUuid)">artifact</a></span> |
| 308 | + } |
| 309 | + if( p->fpid!=target ){ |
| 310 | + @ %z(href("%R/forumthread/%S?t",p->zUuid))[link]</a> |
| 311 | + } |
| 312 | + forum_render(0, pPost->zMimetype, pPost->zWiki, 0); |
| 313 | + if( g.perm.WrForum && p->pLeaf==0 ){ |
| 314 | + int sameUser = login_is_individual() |
| 315 | + && fossil_strcmp(pPost->zUser, g.zLogin)==0; |
| 316 | + int isPrivate = content_is_private(p->fpid); |
| 317 | + @ <p><form action="%R/forumedit" method="POST"> |
| 318 | + @ <input type="hidden" name="fpid" value="%s(p->zUuid)"> |
| 319 | + if( !isPrivate ){ |
| 320 | + /* Reply and Edit are only available if the post has already |
| 321 | + ** been approved */ |
| 322 | + @ <input type="submit" name="reply" value="Reply"> |
| 323 | + if( g.perm.Admin || sameUser ){ |
| 324 | + @ <input type="submit" name="edit" value="Edit"> |
| 325 | + @ <input type="submit" name="nullout" value="Delete"> |
| 326 | + } |
| 327 | + }else if( g.perm.ModForum ){ |
| 328 | + /* Provide moderators with moderation buttons for posts that |
| 329 | + ** are pending moderation */ |
| 330 | + @ <input type="submit" name="approve" value="Approve"> |
| 331 | + @ <input type="submit" name="reject" value="Reject"> |
| 332 | + }else if( sameUser ){ |
| 333 | + /* A post that is pending moderation can be deleted by the |
| 334 | + ** person who originally submitted the post */ |
| 335 | + @ <input type="submit" name="reject" value="Delete"> |
| 336 | + } |
| 337 | + @ </form></p> |
| 338 | + } |
| 339 | + manifest_destroy(pPost); |
| 340 | + @ </div> |
| 341 | + } |
| 342 | + forumthread_delete(pThread); |
| 343 | +} |
| 344 | + |
| 345 | +/* |
| 346 | +** Display all messages in a forumthread with indentation. |
| 347 | +*/ |
| 348 | +static int forum_display_hierarchical(int froot, int target){ |
| 349 | + ForumThread *pThread; |
| 350 | + ForumEntry *p; |
| 351 | + Manifest *pPost, *pOPost; |
| 352 | + int fpid; |
| 353 | + const char *zUuid; |
| 354 | + char *zDate; |
| 355 | + const char *zSel; |
| 356 | + |
| 357 | + pThread = forumthread_create(froot, 1); |
| 358 | + for(p=pThread->pFirst; p; p=p->pNext){ |
| 359 | + if( p->fpid==target ){ |
| 360 | + while( p->pEdit ) p = p->pEdit; |
| 361 | + target = p->fpid; |
| 362 | + break; |
| 363 | + } |
| 364 | + } |
| 365 | + for(p=pThread->pDisplay; p; p=p->pDisplay){ |
| 366 | + pOPost = manifest_get(p->fpid, CFTYPE_FORUM, 0); |
| 367 | + if( p->pLeaf ){ |
| 368 | + fpid = p->pLeaf->fpid; |
| 369 | + zUuid = p->pLeaf->zUuid; |
| 370 | + pPost = manifest_get(fpid, CFTYPE_FORUM, 0); |
| 371 | + }else{ |
| 372 | + fpid = p->fpid; |
| 373 | + zUuid = p->zUuid; |
| 374 | + pPost = pOPost; |
| 375 | + } |
| 376 | + zSel = p->fpid==target ? " forumSel" : ""; |
| 377 | + if( p->nIndent==1 ){ |
| 378 | + @ <div id='forum(%d(fpid)' class='forumHierRoot%s(zSel)'> |
| 277 | 379 | }else{ |
| 278 | | - @ <div id="forum%d(fpid)" class="forumTime"> |
| 380 | + @ <div id='forum%d(fpid)' class='forumHier%s(zSel)' \ |
| 381 | + @ style='margin-left: %d((p->nIndent-1)*3)ex;'> |
| 279 | 382 | } |
| 383 | + pPost = manifest_get(fpid, CFTYPE_FORUM, 0); |
| 384 | + if( pPost==0 ) continue; |
| 280 | 385 | if( pPost->zThreadTitle ){ |
| 281 | 386 | @ <h1>%h(pPost->zThreadTitle)</h1> |
| 282 | 387 | } |
| 283 | | - @ <p>By %h(pPost->zUser) on %h(zDate) (%d(fpid)) |
| 284 | | - if( fprev ){ |
| 285 | | - @ edit of %d(fprev) |
| 286 | | - } |
| 287 | | - if( firt ){ |
| 288 | | - @ reply to %d(firt) |
| 289 | | - } |
| 388 | + zDate = db_text(0, "SELECT datetime(%.17g)", pOPost->rDate); |
| 389 | + @ <p>By %h(pOPost->zUser) on %h(zDate) |
| 390 | + fossil_free(zDate); |
| 290 | 391 | if( g.perm.Debug ){ |
| 291 | 392 | @ <span class="debug">\ |
| 292 | | - @ <a href="%R/artifact/%h(zUuid)">artifact</a></span> |
| 393 | + @ <a href="%R/artifact/%h(p->zUuid)">(%d(p->fpid))</a></span> |
| 394 | + } |
| 395 | + if( p->pLeaf ){ |
| 396 | + zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate); |
| 397 | + if( fossil_strcmp(pOPost->zUser,pPost->zUser)==0 ){ |
| 398 | + @ and edited on %h(zDate) |
| 399 | + }else{ |
| 400 | + @ as edited by %h(pPost->zUser) on %h(zDate) |
| 401 | + } |
| 402 | + fossil_free(zDate); |
| 403 | + if( g.perm.Debug ){ |
| 404 | + @ <span class="debug">\ |
| 405 | + @ <a href="%R/artifact/%h(p->pLeaf->zUuid)">(%d(fpid))</a></span> |
| 406 | + } |
| 407 | + manifest_destroy(pOPost); |
| 408 | + } |
| 409 | + if( fpid!=target ){ |
| 410 | + @ %z(href("%R/forumthread/%S",zUuid))[link]</a> |
| 293 | 411 | } |
| 294 | 412 | forum_render(0, pPost->zMimetype, pPost->zWiki, 0); |
| 295 | 413 | if( g.perm.WrForum ){ |
| 296 | 414 | int sameUser = login_is_individual() |
| 297 | 415 | && fossil_strcmp(pPost->zUser, g.zLogin)==0; |
| | @@ -319,109 +437,10 @@ |
| 319 | 437 | @ </form></p> |
| 320 | 438 | } |
| 321 | 439 | manifest_destroy(pPost); |
| 322 | 440 | @ </div> |
| 323 | 441 | } |
| 324 | | - db_finalize(&q); |
| 325 | | -} |
| 326 | | - |
| 327 | | -/* |
| 328 | | -** Display all messages in a forumthread with indentation. |
| 329 | | -*/ |
| 330 | | -static int forum_display_hierarchical(int froot, int target){ |
| 331 | | - ForumThread *pThread; |
| 332 | | - ForumEntry *p; |
| 333 | | - Manifest *pPost, *pOPost; |
| 334 | | - int fpid; |
| 335 | | - char *zDate; |
| 336 | | - char *zUuid; |
| 337 | | - const char *zSel; |
| 338 | | - |
| 339 | | - pThread = forumthread_create(froot); |
| 340 | | - for(p=pThread->pFirst; p; p=p->pNext){ |
| 341 | | - if( p->fpid==target ){ |
| 342 | | - while( p->pEdit ) p = p->pEdit; |
| 343 | | - target = p->fpid; |
| 344 | | - break; |
| 345 | | - } |
| 346 | | - } |
| 347 | | - for(p=pThread->pDisplay; p; p=p->pDisplay){ |
| 348 | | - pOPost = manifest_get(p->fpid, CFTYPE_FORUM, 0); |
| 349 | | - if( p->pLeaf ){ |
| 350 | | - fpid = p->pLeaf->fpid; |
| 351 | | - pPost = manifest_get(fpid, CFTYPE_FORUM, 0); |
| 352 | | - }else{ |
| 353 | | - fpid = p->fpid; |
| 354 | | - pPost = pOPost; |
| 355 | | - } |
| 356 | | - zSel = p->fpid==target ? " forumSel" : ""; |
| 357 | | - if( p->nIndent==1 ){ |
| 358 | | - @ <div id='forum(%d(fpid)' class='forumHierRoot%s(zSel)'> |
| 359 | | - }else{ |
| 360 | | - @ <div id='forum%d(fpid)' class='forumHier%s(zSel)' \ |
| 361 | | - @ style='margin-left: %d((p->nIndent-1)*3)ex;'> |
| 362 | | - } |
| 363 | | - pPost = manifest_get(fpid, CFTYPE_FORUM, 0); |
| 364 | | - if( pPost==0 ) continue; |
| 365 | | - if( pPost->zThreadTitle ){ |
| 366 | | - @ <h1>%h(pPost->zThreadTitle)</h1> |
| 367 | | - } |
| 368 | | - zDate = db_text(0, "SELECT datetime(%.17g)", pOPost->rDate); |
| 369 | | - @ <p>By %h(pOPost->zUser) on %h(zDate) |
| 370 | | - fossil_free(zDate); |
| 371 | | - zUuid = rid_to_uuid(p->fpid); |
| 372 | | - if( g.perm.Debug ){ |
| 373 | | - @ <span class="debug">\ |
| 374 | | - @ <a href="%R/artifact/%h(zUuid)">(%d(p->fpid))</a></span> |
| 375 | | - } |
| 376 | | - if( p->pLeaf ){ |
| 377 | | - zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate); |
| 378 | | - if( fossil_strcmp(pOPost->zUser,pPost->zUser)==0 ){ |
| 379 | | - @ and edited on %h(zDate) |
| 380 | | - }else{ |
| 381 | | - @ as edited by %h(pPost->zUser) on %h(zDate) |
| 382 | | - } |
| 383 | | - fossil_free(zDate); |
| 384 | | - fossil_free(zUuid); |
| 385 | | - zUuid = rid_to_uuid(fpid); |
| 386 | | - if( g.perm.Debug ){ |
| 387 | | - @ <span class="debug">\ |
| 388 | | - @ <a href="%R/artifact/%h(zUuid)">(%d(fpid))</a></span> |
| 389 | | - } |
| 390 | | - manifest_destroy(pOPost); |
| 391 | | - } |
| 392 | | - forum_render(0, pPost->zMimetype, pPost->zWiki, 0); |
| 393 | | - if( g.perm.WrForum ){ |
| 394 | | - int sameUser = login_is_individual() |
| 395 | | - && fossil_strcmp(pPost->zUser, g.zLogin)==0; |
| 396 | | - int isPrivate = content_is_private(fpid); |
| 397 | | - @ <p><form action="%R/forumedit" method="POST"> |
| 398 | | - @ <input type="hidden" name="fpid" value="%s(zUuid)"> |
| 399 | | - if( !isPrivate ){ |
| 400 | | - /* Reply and Edit are only available if the post has already |
| 401 | | - ** been approved */ |
| 402 | | - @ <input type="submit" name="reply" value="Reply"> |
| 403 | | - if( g.perm.Admin || sameUser ){ |
| 404 | | - @ <input type="submit" name="edit" value="Edit"> |
| 405 | | - @ <input type="submit" name="nullout" value="Delete"> |
| 406 | | - } |
| 407 | | - }else if( g.perm.ModForum ){ |
| 408 | | - /* Provide moderators with moderation buttons for posts that |
| 409 | | - ** are pending moderation */ |
| 410 | | - @ <input type="submit" name="approve" value="Approve"> |
| 411 | | - @ <input type="submit" name="reject" value="Reject"> |
| 412 | | - }else if( sameUser ){ |
| 413 | | - /* A post that is pending moderation can be deleted by the |
| 414 | | - ** person who originally submitted the post */ |
| 415 | | - @ <input type="submit" name="reject" value="Delete"> |
| 416 | | - } |
| 417 | | - @ </form></p> |
| 418 | | - } |
| 419 | | - manifest_destroy(pPost); |
| 420 | | - fossil_free(zUuid); |
| 421 | | - @ </div> |
| 422 | | - } |
| 423 | 442 | forumthread_delete(pThread); |
| 424 | 443 | return target; |
| 425 | 444 | } |
| 426 | 445 | |
| 427 | 446 | /* |
| | @@ -430,10 +449,11 @@ |
| 430 | 449 | ** Show all forum messages associated with a particular message thread. |
| 431 | 450 | ** |
| 432 | 451 | ** Query parameters: |
| 433 | 452 | ** |
| 434 | 453 | ** name=X The hash of the first post of the thread. REQUIRED |
| 454 | +** t Show a chronologic listing instead of hierarchical |
| 435 | 455 | */ |
| 436 | 456 | void forumthread_page(void){ |
| 437 | 457 | int fpid; |
| 438 | 458 | int froot; |
| 439 | 459 | const char *zName = P("name"); |
| | @@ -461,11 +481,11 @@ |
| 461 | 481 | forum_display_chronological(froot, fpid); |
| 462 | 482 | }else{ |
| 463 | 483 | if( g.perm.Debug ){ |
| 464 | 484 | style_submenu_element("Chronological", "%R/forumthread/%s?t", zName); |
| 465 | 485 | } |
| 466 | | - fpid = forum_display_hierarchical(froot, fpid); |
| 486 | + forum_display_hierarchical(froot, fpid); |
| 467 | 487 | } |
| 468 | 488 | style_load_js("forum.js"); |
| 469 | 489 | style_footer(); |
| 470 | 490 | } |
| 471 | 491 | |
| 472 | 492 | |