Fossil SCM
Improved titles for forum posts that show the original poster and the latest editor if they are different people.
Commit
9543ddbef25dcb49fd6fcfb290494449f008c1532065042cdb41793cbb9ed2df
Parent
7ea825864da0be1…
1 file changed
+83
-54
+83
-54
| --- src/forum.c | ||
| +++ src/forum.c | ||
| @@ -35,10 +35,12 @@ | ||
| 35 | 35 | struct ForumPost { |
| 36 | 36 | int fpid; /* rid for this post */ |
| 37 | 37 | int sid; /* Serial ID number */ |
| 38 | 38 | int rev; /* Revision number */ |
| 39 | 39 | char *zUuid; /* Artifact hash */ |
| 40 | + char *zDisplayName; /* Name of user who wrote this post */ | |
| 41 | + double rDate; /* Date for this post */ | |
| 40 | 42 | ForumPost *pIrt; /* This post replies to pIrt */ |
| 41 | 43 | ForumPost *pEditHead; /* Original, unedited post */ |
| 42 | 44 | ForumPost *pEditTail; /* Most recent edit for this post */ |
| 43 | 45 | ForumPost *pEditNext; /* This post is edited by pEditNext */ |
| 44 | 46 | ForumPost *pEditPrev; /* This post is an edit of pEditPrev */ |
| @@ -84,10 +86,11 @@ | ||
| 84 | 86 | static void forumthread_delete(ForumThread *pThread){ |
| 85 | 87 | ForumPost *pPost, *pNext; |
| 86 | 88 | for(pPost=pThread->pFirst; pPost; pPost = pNext){ |
| 87 | 89 | pNext = pPost->pNext; |
| 88 | 90 | fossil_free(pPost->zUuid); |
| 91 | + fossil_free(pPost->zDisplayName); | |
| 89 | 92 | fossil_free(pPost); |
| 90 | 93 | } |
| 91 | 94 | fossil_free(pThread); |
| 92 | 95 | } |
| 93 | 96 | |
| @@ -163,11 +166,11 @@ | ||
| 163 | 166 | int sid = 1; |
| 164 | 167 | int firt, fprev; |
| 165 | 168 | pThread = fossil_malloc( sizeof(*pThread) ); |
| 166 | 169 | memset(pThread, 0, sizeof(*pThread)); |
| 167 | 170 | db_prepare(&q, |
| 168 | - "SELECT fpid, firt, fprev, (SELECT uuid FROM blob WHERE rid=fpid)" | |
| 171 | + "SELECT fpid, firt, fprev, (SELECT uuid FROM blob WHERE rid=fpid), fmtime" | |
| 169 | 172 | " FROM forumpost" |
| 170 | 173 | " WHERE froot=%d ORDER BY fmtime", |
| 171 | 174 | froot |
| 172 | 175 | ); |
| 173 | 176 | while( db_step(&q)==SQLITE_ROW ){ |
| @@ -175,10 +178,11 @@ | ||
| 175 | 178 | memset(pPost, 0, sizeof(*pPost)); |
| 176 | 179 | pPost->fpid = db_column_int(&q, 0); |
| 177 | 180 | firt = db_column_int(&q, 1); |
| 178 | 181 | fprev = db_column_int(&q, 2); |
| 179 | 182 | pPost->zUuid = fossil_strdup(db_column_text(&q,3)); |
| 183 | + pPost->rDate = db_column_double(&q,4); | |
| 180 | 184 | if( !fprev ) pPost->sid = sid++; |
| 181 | 185 | pPost->pPrev = pThread->pLast; |
| 182 | 186 | pPost->pNext = 0; |
| 183 | 187 | if( pThread->pLast==0 ){ |
| 184 | 188 | pThread->pFirst = pPost; |
| @@ -367,11 +371,11 @@ | ||
| 367 | 371 | ** name just be the login. |
| 368 | 372 | ** |
| 369 | 373 | ** Space to hold the returned name is obtained from fossil_strdup() or |
| 370 | 374 | ** mprintf() and should be freed by the caller. |
| 371 | 375 | */ |
| 372 | -char *display_name_from_login(const char *zLogin){ | |
| 376 | +static char *display_name_from_login(const char *zLogin){ | |
| 373 | 377 | static Stmt q; |
| 374 | 378 | char *zResult; |
| 375 | 379 | db_static_prepare(&q, |
| 376 | 380 | "SELECT display_name(info) FROM user WHERE login=$login" |
| 377 | 381 | ); |
| @@ -387,10 +391,34 @@ | ||
| 387 | 391 | zResult = fossil_strdup(zLogin); |
| 388 | 392 | } |
| 389 | 393 | db_reset(&q); |
| 390 | 394 | return zResult; |
| 391 | 395 | } |
| 396 | + | |
| 397 | +/* | |
| 398 | +** Compute and return the display name for a ForumPost. If | |
| 399 | +** pManifest is not NULL, then it is a Manifest object for the post. | |
| 400 | +** if pManifest is NULL, this routine has to fetch and parse the | |
| 401 | +** Manifest object for itself. | |
| 402 | +** | |
| 403 | +** Memory to hold the display name is attached to p->zDisplayName | |
| 404 | +** and will be freed together with the ForumPost object p when it | |
| 405 | +** is freed. | |
| 406 | +*/ | |
| 407 | +static char *forum_post_display_name(ForumPost *p, Manifest *pManifest){ | |
| 408 | + Manifest *pToFree = 0; | |
| 409 | + if( p->zDisplayName ) return p->zDisplayName; | |
| 410 | + if( pManifest==0 ){ | |
| 411 | + pManifest = pToFree = manifest_get(p->fpid, CFTYPE_FORUM, 0); | |
| 412 | + if( pManifest==0 ) return "(unknown)"; | |
| 413 | + } | |
| 414 | + p->zDisplayName = display_name_from_login(pManifest->zUser); | |
| 415 | + if( pToFree ) manifest_destroy(pToFree); | |
| 416 | + if( p->zDisplayName==0 ) return "(unknown)"; | |
| 417 | + return p->zDisplayName; | |
| 418 | +} | |
| 419 | + | |
| 392 | 420 | |
| 393 | 421 | /* |
| 394 | 422 | ** Display a single post in a forum thread. |
| 395 | 423 | */ |
| 396 | 424 | static void forum_display_post( |
| @@ -400,29 +428,23 @@ | ||
| 400 | 428 | int bUnf, /* True to leave the post unformatted */ |
| 401 | 429 | int bHist, /* True if showing edit history */ |
| 402 | 430 | int bSelect, /* True if this is the selected post */ |
| 403 | 431 | char *zQuery /* Common query string */ |
| 404 | 432 | ){ |
| 405 | - char *zDisplayName; /* The display name */ | |
| 433 | + char *zPosterName; /* Name of user who originally made this post */ | |
| 434 | + char *zEditorName; /* Name of user who provided the current edit */ | |
| 406 | 435 | char *zDate; /* The time/date string */ |
| 407 | 436 | char *zHist; /* History query string */ |
| 408 | - Manifest *pOriginal; /* Original post artifact */ | |
| 409 | - Manifest *pRevised; /* Revised post artifact (may be same as pOriginal) */ | |
| 437 | + Manifest *pManifest; /* Manifest comprising the current post */ | |
| 410 | 438 | int bPrivate; /* True for posts awaiting moderation */ |
| 411 | 439 | int bSameUser; /* True if author is also the reader */ |
| 412 | 440 | int iIndent; /* Indent level */ |
| 413 | 441 | const char *zMimetype;/* Formatting MIME type */ |
| 414 | 442 | |
| 415 | - /* Get the original and revised artifacts for the post. Abort if either is | |
| 416 | - ** not found (e.g. shunned). */ | |
| 417 | - if( p->pEditHead ){ | |
| 418 | - pOriginal = manifest_get(p->pEditHead->fpid, CFTYPE_FORUM, 0); | |
| 419 | - pRevised = manifest_get(p->fpid, CFTYPE_FORUM, 0); | |
| 420 | - }else{ | |
| 421 | - pOriginal = pRevised = manifest_get(p->fpid, CFTYPE_FORUM, 0); | |
| 422 | - } | |
| 423 | - if( !pOriginal || !pRevised ) return; | |
| 443 | + /* Get the manifest for the post. Abort if not found (e.g. shunned). */ | |
| 444 | + pManifest = manifest_get(p->fpid, CFTYPE_FORUM, 0); | |
| 445 | + if( !pManifest ) return; | |
| 424 | 446 | |
| 425 | 447 | /* When not in raw mode, create the border around the post. */ |
| 426 | 448 | if( !bRaw ){ |
| 427 | 449 | /* Open the <div> enclosing the post. Set the class string to mark the post |
| 428 | 450 | ** as selected and/or obsolete. */ |
| @@ -434,55 +456,72 @@ | ||
| 434 | 456 | @ style='margin-left: %d(iIndent*iIndentScale)ex' |
| 435 | 457 | } |
| 436 | 458 | @ > |
| 437 | 459 | |
| 438 | 460 | /* If this is the first post (or an edit thereof), emit the thread title. */ |
| 439 | - if( pRevised->zThreadTitle ){ | |
| 440 | - @ <h1>%h(pRevised->zThreadTitle)</h1> | |
| 461 | + if( pManifest->zThreadTitle ){ | |
| 462 | + @ <h1>%h(pManifest->zThreadTitle)</h1> | |
| 441 | 463 | } |
| 442 | 464 | |
| 443 | - /* Emit the serial number, revision number, author, and date. */ | |
| 444 | - zDisplayName = display_name_from_login(pOriginal->zUser); | |
| 445 | - zDate = db_text(0, "SELECT datetime(%.17g)", pOriginal->rDate); | |
| 446 | - @ <h3 class='forumPostHdr'>(%d(p->sid)\ | |
| 447 | - if( p->nEdit ){ | |
| 448 | - @ .%.*d(fossil_num_digits(p->nEdit))(p->rev)\ | |
| 449 | - } | |
| 450 | - @ ) By %h(zDisplayName) on %h(zDate) | |
| 451 | - fossil_free(zDisplayName); | |
| 465 | + /* Begin emitting the header line. The forum of the title | |
| 466 | + ** varies depending on whether: | |
| 467 | + ** * The post is uneditted | |
| 468 | + ** * The post was last editted by the original author | |
| 469 | + ** * The post was last editted by a different person | |
| 470 | + */ | |
| 471 | + if( p->pEditHead ){ | |
| 472 | + zDate = db_text(0, "SELECT datetime(%.17g)", p->pEditHead->rDate); | |
| 473 | + }else{ | |
| 474 | + zPosterName = forum_post_display_name(p, pManifest); | |
| 475 | + zEditorName = zPosterName; | |
| 476 | + } | |
| 477 | + zDate = db_text(0, "SELECT datetime(%.17g)", p->rDate); | |
| 478 | + if( p->pEditPrev ){ | |
| 479 | + zPosterName = forum_post_display_name(p->pEditHead, 0); | |
| 480 | + zEditorName = forum_post_display_name(p, pManifest); | |
| 481 | + zHist = bHist ? "" : "&hist"; | |
| 482 | + @ <h3 class='forumPostHdr'>(%d(p->sid)\ | |
| 483 | + @ .%0*d(fossil_num_digits(p->nEdit))(p->rev)) \ | |
| 484 | + if( fossil_strcmp(zPosterName, zEditorName)==0 ){ | |
| 485 | + @ By %h(zPosterName) on %h(zDate) editted from \ | |
| 486 | + @ %z(href("%R/forumpost/%S?%s%s",p->pEditPrev->zUuid,zQuery,zHist))\ | |
| 487 | + @ %d(p->sid).%0*d(fossil_num_digits(p->nEdit))(p->pEditPrev->rev)</a> | |
| 488 | + }else{ | |
| 489 | + @ Originally by %h(zPosterName) \ | |
| 490 | + @ with edits by %h(zEditorName) on %h(zDate) from \ | |
| 491 | + @ %z(href("%R/forumpost/%S?%s%s",p->pEditPrev->zUuid,zQuery,zHist))\ | |
| 492 | + @ %d(p->sid).%0*d(fossil_num_digits(p->nEdit))(p->pEditPrev->rev)</a> | |
| 493 | + } | |
| 494 | + }else{ | |
| 495 | + zPosterName = forum_post_display_name(p, pManifest); | |
| 496 | + @ <h3 class='forumPostHdr'>(%d(p->sid)) \ | |
| 497 | + @ By %h(zPosterName) on %h(zDate) | |
| 498 | + } | |
| 452 | 499 | fossil_free(zDate); |
| 500 | + | |
| 453 | 501 | |
| 454 | 502 | /* If debugging is enabled, link to the artifact page. */ |
| 455 | 503 | if( g.perm.Debug ){ |
| 456 | 504 | @ <span class="debug">\ |
| 457 | 505 | @ <a href="%R/artifact/%h(p->zUuid)">(artifact-%d(p->fpid))</a></span> |
| 458 | 506 | } |
| 459 | 507 | |
| 460 | - /* If this is an edit, refer back to the old version. Be sure "hist" is in | |
| 461 | - ** the query string so the old version will actually be shown. */ | |
| 462 | - if( p->pEditPrev ){ | |
| 463 | - zHist = bHist ? "" : "&hist"; | |
| 464 | - @ edit of \ | |
| 465 | - @ %z(href("%R/forumpost/%S?%s%s",p->pEditPrev->zUuid,zQuery,zHist))\ | |
| 466 | - @ %d(p->sid).%.*d(fossil_num_digits(p->nEdit))(p->pEditPrev->rev)</a> | |
| 467 | - } | |
| 468 | - | |
| 469 | 508 | /* If this is a reply, refer back to the parent post. */ |
| 470 | 509 | if( p->pIrt ){ |
| 471 | 510 | @ in reply to %z(href("%R/forumpost/%S?%s",p->pIrt->zUuid,zQuery))\ |
| 472 | 511 | @ %d(p->pIrt->sid)\ |
| 473 | 512 | if( p->pIrt->nEdit ){ |
| 474 | - @ .%.*d(fossil_num_digits(p->pIrt->nEdit))(p->pIrt->rev)\ | |
| 513 | + @ .%0*d(fossil_num_digits(p->pIrt->nEdit))(p->pIrt->rev)\ | |
| 475 | 514 | } |
| 476 | 515 | @ </a> |
| 477 | 516 | } |
| 478 | 517 | |
| 479 | 518 | /* If this post was later edited, refer forward to the next edit. */ |
| 480 | 519 | if( p->pEditNext ){ |
| 481 | 520 | @ updated by %z(href("%R/forumpost/%S?%s",p->pEditNext->zUuid,zQuery))\ |
| 482 | 521 | @ %d(p->pEditNext->sid)\ |
| 483 | - @ .%.*d(fossil_num_digits(p->nEdit))(p->pEditNext->rev)</a> | |
| 522 | + @ .%0*d(fossil_num_digits(p->nEdit))(p->pEditNext->rev)</a> | |
| 484 | 523 | } |
| 485 | 524 | |
| 486 | 525 | /* Provide a link to select the individual post. */ |
| 487 | 526 | if( !bSelect ){ |
| 488 | 527 | @ %z(href("%R/forumpost/%S?%s",p->zUuid,zQuery))[link]</a> |
| @@ -490,37 +529,28 @@ | ||
| 490 | 529 | |
| 491 | 530 | /* Provide a link to the raw source code. */ |
| 492 | 531 | if( !bUnf ){ |
| 493 | 532 | @ %z(href("%R/forumpost/%S?raw",p->zUuid))[source]</a> |
| 494 | 533 | } |
| 495 | - | |
| 496 | - /* If this is an edit, identify the editor and date. */ | |
| 497 | - if( p->pEditPrev ){ | |
| 498 | - zDisplayName = display_name_from_login(pRevised->zUser); | |
| 499 | - zDate = db_text(0, "SELECT datetime(%.17g)", pRevised->rDate); | |
| 500 | - @ <br>Edited by %h(zDisplayName) on %h(zDate) | |
| 501 | - fossil_free(zDisplayName); | |
| 502 | - fossil_free(zDate); | |
| 503 | - } | |
| 504 | 534 | @ </h3> |
| 505 | 535 | } |
| 506 | 536 | |
| 507 | 537 | /* Check if this post is approved, also if it's by the current user. */ |
| 508 | 538 | bPrivate = content_is_private(p->fpid); |
| 509 | 539 | bSameUser = login_is_individual() |
| 510 | - && fossil_strcmp(pOriginal->zUser, g.zLogin)==0; | |
| 540 | + && fossil_strcmp(pManifest->zUser, g.zLogin)==0; | |
| 511 | 541 | |
| 512 | 542 | /* Render the post if the user is able to see it. */ |
| 513 | 543 | if( bPrivate && !g.perm.ModForum && !bSameUser ){ |
| 514 | 544 | @ <p><span class="modpending">Awaiting Moderator Approval</span></p> |
| 515 | 545 | }else{ |
| 516 | 546 | if( bRaw || bUnf || p->pEditTail ){ |
| 517 | 547 | zMimetype = "text/plain"; |
| 518 | 548 | }else{ |
| 519 | - zMimetype = pRevised->zMimetype; | |
| 549 | + zMimetype = pManifest->zMimetype; | |
| 520 | 550 | } |
| 521 | - forum_render(0, zMimetype, pRevised->zWiki, 0, !bRaw); | |
| 551 | + forum_render(0, zMimetype, pManifest->zWiki, 0, !bRaw); | |
| 522 | 552 | } |
| 523 | 553 | |
| 524 | 554 | /* When not in raw mode, finish creating the border around the post. */ |
| 525 | 555 | if( !bRaw ){ |
| 526 | 556 | /* If the user is able to write to the forum and if this post has not been |
| @@ -539,16 +569,16 @@ | ||
| 539 | 569 | /* Allow moderators to approve or reject pending posts. Also allow |
| 540 | 570 | ** forum supervisors to mark non-special users as trusted and therefore |
| 541 | 571 | ** able to post unmoderated. */ |
| 542 | 572 | @ <input type="submit" name="approve" value="Approve"> |
| 543 | 573 | @ <input type="submit" name="reject" value="Reject"> |
| 544 | - if( g.perm.AdminForum && !login_is_special(pOriginal->zUser) ){ | |
| 574 | + if( g.perm.AdminForum && !login_is_special(pManifest->zUser) ){ | |
| 545 | 575 | @ <br><label><input type="checkbox" name="trust"> |
| 546 | - @ Trust user "%h(pOriginal->zUser)" so that future posts by \ | |
| 547 | - @ "%h(pOriginal->zUser)" do not require moderation. | |
| 576 | + @ Trust user "%h(pManifest->zUser)" so that future posts by \ | |
| 577 | + @ "%h(pManifest->zUser)" do not require moderation. | |
| 548 | 578 | @ </label> |
| 549 | - @ <input type="hidden" name="trustuser" value="%h(pOriginal->zUser)"> | |
| 579 | + @ <input type="hidden" name="trustuser" value="%h(pManifest->zUser)"> | |
| 550 | 580 | } |
| 551 | 581 | }else if( bSameUser ){ |
| 552 | 582 | /* Allow users to delete (reject) their own pending posts. */ |
| 553 | 583 | @ <input type="submit" name="reject" value="Delete"> |
| 554 | 584 | } |
| @@ -556,12 +586,11 @@ | ||
| 556 | 586 | } |
| 557 | 587 | @ </div> |
| 558 | 588 | } |
| 559 | 589 | |
| 560 | 590 | /* Clean up. */ |
| 561 | - if( pRevised!=pOriginal ) manifest_destroy(pRevised); | |
| 562 | - manifest_destroy(pOriginal); | |
| 591 | + manifest_destroy(pManifest); | |
| 563 | 592 | } |
| 564 | 593 | |
| 565 | 594 | /* |
| 566 | 595 | ** Possible display modes for forum_display_thread(). |
| 567 | 596 | */ |
| 568 | 597 |
| --- src/forum.c | |
| +++ src/forum.c | |
| @@ -35,10 +35,12 @@ | |
| 35 | struct ForumPost { |
| 36 | int fpid; /* rid for this post */ |
| 37 | int sid; /* Serial ID number */ |
| 38 | int rev; /* Revision number */ |
| 39 | char *zUuid; /* Artifact hash */ |
| 40 | ForumPost *pIrt; /* This post replies to pIrt */ |
| 41 | ForumPost *pEditHead; /* Original, unedited post */ |
| 42 | ForumPost *pEditTail; /* Most recent edit for this post */ |
| 43 | ForumPost *pEditNext; /* This post is edited by pEditNext */ |
| 44 | ForumPost *pEditPrev; /* This post is an edit of pEditPrev */ |
| @@ -84,10 +86,11 @@ | |
| 84 | static void forumthread_delete(ForumThread *pThread){ |
| 85 | ForumPost *pPost, *pNext; |
| 86 | for(pPost=pThread->pFirst; pPost; pPost = pNext){ |
| 87 | pNext = pPost->pNext; |
| 88 | fossil_free(pPost->zUuid); |
| 89 | fossil_free(pPost); |
| 90 | } |
| 91 | fossil_free(pThread); |
| 92 | } |
| 93 | |
| @@ -163,11 +166,11 @@ | |
| 163 | int sid = 1; |
| 164 | int firt, fprev; |
| 165 | pThread = fossil_malloc( sizeof(*pThread) ); |
| 166 | memset(pThread, 0, sizeof(*pThread)); |
| 167 | db_prepare(&q, |
| 168 | "SELECT fpid, firt, fprev, (SELECT uuid FROM blob WHERE rid=fpid)" |
| 169 | " FROM forumpost" |
| 170 | " WHERE froot=%d ORDER BY fmtime", |
| 171 | froot |
| 172 | ); |
| 173 | while( db_step(&q)==SQLITE_ROW ){ |
| @@ -175,10 +178,11 @@ | |
| 175 | memset(pPost, 0, sizeof(*pPost)); |
| 176 | pPost->fpid = db_column_int(&q, 0); |
| 177 | firt = db_column_int(&q, 1); |
| 178 | fprev = db_column_int(&q, 2); |
| 179 | pPost->zUuid = fossil_strdup(db_column_text(&q,3)); |
| 180 | if( !fprev ) pPost->sid = sid++; |
| 181 | pPost->pPrev = pThread->pLast; |
| 182 | pPost->pNext = 0; |
| 183 | if( pThread->pLast==0 ){ |
| 184 | pThread->pFirst = pPost; |
| @@ -367,11 +371,11 @@ | |
| 367 | ** name just be the login. |
| 368 | ** |
| 369 | ** Space to hold the returned name is obtained from fossil_strdup() or |
| 370 | ** mprintf() and should be freed by the caller. |
| 371 | */ |
| 372 | char *display_name_from_login(const char *zLogin){ |
| 373 | static Stmt q; |
| 374 | char *zResult; |
| 375 | db_static_prepare(&q, |
| 376 | "SELECT display_name(info) FROM user WHERE login=$login" |
| 377 | ); |
| @@ -387,10 +391,34 @@ | |
| 387 | zResult = fossil_strdup(zLogin); |
| 388 | } |
| 389 | db_reset(&q); |
| 390 | return zResult; |
| 391 | } |
| 392 | |
| 393 | /* |
| 394 | ** Display a single post in a forum thread. |
| 395 | */ |
| 396 | static void forum_display_post( |
| @@ -400,29 +428,23 @@ | |
| 400 | int bUnf, /* True to leave the post unformatted */ |
| 401 | int bHist, /* True if showing edit history */ |
| 402 | int bSelect, /* True if this is the selected post */ |
| 403 | char *zQuery /* Common query string */ |
| 404 | ){ |
| 405 | char *zDisplayName; /* The display name */ |
| 406 | char *zDate; /* The time/date string */ |
| 407 | char *zHist; /* History query string */ |
| 408 | Manifest *pOriginal; /* Original post artifact */ |
| 409 | Manifest *pRevised; /* Revised post artifact (may be same as pOriginal) */ |
| 410 | int bPrivate; /* True for posts awaiting moderation */ |
| 411 | int bSameUser; /* True if author is also the reader */ |
| 412 | int iIndent; /* Indent level */ |
| 413 | const char *zMimetype;/* Formatting MIME type */ |
| 414 | |
| 415 | /* Get the original and revised artifacts for the post. Abort if either is |
| 416 | ** not found (e.g. shunned). */ |
| 417 | if( p->pEditHead ){ |
| 418 | pOriginal = manifest_get(p->pEditHead->fpid, CFTYPE_FORUM, 0); |
| 419 | pRevised = manifest_get(p->fpid, CFTYPE_FORUM, 0); |
| 420 | }else{ |
| 421 | pOriginal = pRevised = manifest_get(p->fpid, CFTYPE_FORUM, 0); |
| 422 | } |
| 423 | if( !pOriginal || !pRevised ) return; |
| 424 | |
| 425 | /* When not in raw mode, create the border around the post. */ |
| 426 | if( !bRaw ){ |
| 427 | /* Open the <div> enclosing the post. Set the class string to mark the post |
| 428 | ** as selected and/or obsolete. */ |
| @@ -434,55 +456,72 @@ | |
| 434 | @ style='margin-left: %d(iIndent*iIndentScale)ex' |
| 435 | } |
| 436 | @ > |
| 437 | |
| 438 | /* If this is the first post (or an edit thereof), emit the thread title. */ |
| 439 | if( pRevised->zThreadTitle ){ |
| 440 | @ <h1>%h(pRevised->zThreadTitle)</h1> |
| 441 | } |
| 442 | |
| 443 | /* Emit the serial number, revision number, author, and date. */ |
| 444 | zDisplayName = display_name_from_login(pOriginal->zUser); |
| 445 | zDate = db_text(0, "SELECT datetime(%.17g)", pOriginal->rDate); |
| 446 | @ <h3 class='forumPostHdr'>(%d(p->sid)\ |
| 447 | if( p->nEdit ){ |
| 448 | @ .%.*d(fossil_num_digits(p->nEdit))(p->rev)\ |
| 449 | } |
| 450 | @ ) By %h(zDisplayName) on %h(zDate) |
| 451 | fossil_free(zDisplayName); |
| 452 | fossil_free(zDate); |
| 453 | |
| 454 | /* If debugging is enabled, link to the artifact page. */ |
| 455 | if( g.perm.Debug ){ |
| 456 | @ <span class="debug">\ |
| 457 | @ <a href="%R/artifact/%h(p->zUuid)">(artifact-%d(p->fpid))</a></span> |
| 458 | } |
| 459 | |
| 460 | /* If this is an edit, refer back to the old version. Be sure "hist" is in |
| 461 | ** the query string so the old version will actually be shown. */ |
| 462 | if( p->pEditPrev ){ |
| 463 | zHist = bHist ? "" : "&hist"; |
| 464 | @ edit of \ |
| 465 | @ %z(href("%R/forumpost/%S?%s%s",p->pEditPrev->zUuid,zQuery,zHist))\ |
| 466 | @ %d(p->sid).%.*d(fossil_num_digits(p->nEdit))(p->pEditPrev->rev)</a> |
| 467 | } |
| 468 | |
| 469 | /* If this is a reply, refer back to the parent post. */ |
| 470 | if( p->pIrt ){ |
| 471 | @ in reply to %z(href("%R/forumpost/%S?%s",p->pIrt->zUuid,zQuery))\ |
| 472 | @ %d(p->pIrt->sid)\ |
| 473 | if( p->pIrt->nEdit ){ |
| 474 | @ .%.*d(fossil_num_digits(p->pIrt->nEdit))(p->pIrt->rev)\ |
| 475 | } |
| 476 | @ </a> |
| 477 | } |
| 478 | |
| 479 | /* If this post was later edited, refer forward to the next edit. */ |
| 480 | if( p->pEditNext ){ |
| 481 | @ updated by %z(href("%R/forumpost/%S?%s",p->pEditNext->zUuid,zQuery))\ |
| 482 | @ %d(p->pEditNext->sid)\ |
| 483 | @ .%.*d(fossil_num_digits(p->nEdit))(p->pEditNext->rev)</a> |
| 484 | } |
| 485 | |
| 486 | /* Provide a link to select the individual post. */ |
| 487 | if( !bSelect ){ |
| 488 | @ %z(href("%R/forumpost/%S?%s",p->zUuid,zQuery))[link]</a> |
| @@ -490,37 +529,28 @@ | |
| 490 | |
| 491 | /* Provide a link to the raw source code. */ |
| 492 | if( !bUnf ){ |
| 493 | @ %z(href("%R/forumpost/%S?raw",p->zUuid))[source]</a> |
| 494 | } |
| 495 | |
| 496 | /* If this is an edit, identify the editor and date. */ |
| 497 | if( p->pEditPrev ){ |
| 498 | zDisplayName = display_name_from_login(pRevised->zUser); |
| 499 | zDate = db_text(0, "SELECT datetime(%.17g)", pRevised->rDate); |
| 500 | @ <br>Edited by %h(zDisplayName) on %h(zDate) |
| 501 | fossil_free(zDisplayName); |
| 502 | fossil_free(zDate); |
| 503 | } |
| 504 | @ </h3> |
| 505 | } |
| 506 | |
| 507 | /* Check if this post is approved, also if it's by the current user. */ |
| 508 | bPrivate = content_is_private(p->fpid); |
| 509 | bSameUser = login_is_individual() |
| 510 | && fossil_strcmp(pOriginal->zUser, g.zLogin)==0; |
| 511 | |
| 512 | /* Render the post if the user is able to see it. */ |
| 513 | if( bPrivate && !g.perm.ModForum && !bSameUser ){ |
| 514 | @ <p><span class="modpending">Awaiting Moderator Approval</span></p> |
| 515 | }else{ |
| 516 | if( bRaw || bUnf || p->pEditTail ){ |
| 517 | zMimetype = "text/plain"; |
| 518 | }else{ |
| 519 | zMimetype = pRevised->zMimetype; |
| 520 | } |
| 521 | forum_render(0, zMimetype, pRevised->zWiki, 0, !bRaw); |
| 522 | } |
| 523 | |
| 524 | /* When not in raw mode, finish creating the border around the post. */ |
| 525 | if( !bRaw ){ |
| 526 | /* If the user is able to write to the forum and if this post has not been |
| @@ -539,16 +569,16 @@ | |
| 539 | /* Allow moderators to approve or reject pending posts. Also allow |
| 540 | ** forum supervisors to mark non-special users as trusted and therefore |
| 541 | ** able to post unmoderated. */ |
| 542 | @ <input type="submit" name="approve" value="Approve"> |
| 543 | @ <input type="submit" name="reject" value="Reject"> |
| 544 | if( g.perm.AdminForum && !login_is_special(pOriginal->zUser) ){ |
| 545 | @ <br><label><input type="checkbox" name="trust"> |
| 546 | @ Trust user "%h(pOriginal->zUser)" so that future posts by \ |
| 547 | @ "%h(pOriginal->zUser)" do not require moderation. |
| 548 | @ </label> |
| 549 | @ <input type="hidden" name="trustuser" value="%h(pOriginal->zUser)"> |
| 550 | } |
| 551 | }else if( bSameUser ){ |
| 552 | /* Allow users to delete (reject) their own pending posts. */ |
| 553 | @ <input type="submit" name="reject" value="Delete"> |
| 554 | } |
| @@ -556,12 +586,11 @@ | |
| 556 | } |
| 557 | @ </div> |
| 558 | } |
| 559 | |
| 560 | /* Clean up. */ |
| 561 | if( pRevised!=pOriginal ) manifest_destroy(pRevised); |
| 562 | manifest_destroy(pOriginal); |
| 563 | } |
| 564 | |
| 565 | /* |
| 566 | ** Possible display modes for forum_display_thread(). |
| 567 | */ |
| 568 |
| --- src/forum.c | |
| +++ src/forum.c | |
| @@ -35,10 +35,12 @@ | |
| 35 | struct ForumPost { |
| 36 | int fpid; /* rid for this post */ |
| 37 | int sid; /* Serial ID number */ |
| 38 | int rev; /* Revision number */ |
| 39 | char *zUuid; /* Artifact hash */ |
| 40 | char *zDisplayName; /* Name of user who wrote this post */ |
| 41 | double rDate; /* Date for this post */ |
| 42 | ForumPost *pIrt; /* This post replies to pIrt */ |
| 43 | ForumPost *pEditHead; /* Original, unedited post */ |
| 44 | ForumPost *pEditTail; /* Most recent edit for this post */ |
| 45 | ForumPost *pEditNext; /* This post is edited by pEditNext */ |
| 46 | ForumPost *pEditPrev; /* This post is an edit of pEditPrev */ |
| @@ -84,10 +86,11 @@ | |
| 86 | static void forumthread_delete(ForumThread *pThread){ |
| 87 | ForumPost *pPost, *pNext; |
| 88 | for(pPost=pThread->pFirst; pPost; pPost = pNext){ |
| 89 | pNext = pPost->pNext; |
| 90 | fossil_free(pPost->zUuid); |
| 91 | fossil_free(pPost->zDisplayName); |
| 92 | fossil_free(pPost); |
| 93 | } |
| 94 | fossil_free(pThread); |
| 95 | } |
| 96 | |
| @@ -163,11 +166,11 @@ | |
| 166 | int sid = 1; |
| 167 | int firt, fprev; |
| 168 | pThread = fossil_malloc( sizeof(*pThread) ); |
| 169 | memset(pThread, 0, sizeof(*pThread)); |
| 170 | db_prepare(&q, |
| 171 | "SELECT fpid, firt, fprev, (SELECT uuid FROM blob WHERE rid=fpid), fmtime" |
| 172 | " FROM forumpost" |
| 173 | " WHERE froot=%d ORDER BY fmtime", |
| 174 | froot |
| 175 | ); |
| 176 | while( db_step(&q)==SQLITE_ROW ){ |
| @@ -175,10 +178,11 @@ | |
| 178 | memset(pPost, 0, sizeof(*pPost)); |
| 179 | pPost->fpid = db_column_int(&q, 0); |
| 180 | firt = db_column_int(&q, 1); |
| 181 | fprev = db_column_int(&q, 2); |
| 182 | pPost->zUuid = fossil_strdup(db_column_text(&q,3)); |
| 183 | pPost->rDate = db_column_double(&q,4); |
| 184 | if( !fprev ) pPost->sid = sid++; |
| 185 | pPost->pPrev = pThread->pLast; |
| 186 | pPost->pNext = 0; |
| 187 | if( pThread->pLast==0 ){ |
| 188 | pThread->pFirst = pPost; |
| @@ -367,11 +371,11 @@ | |
| 371 | ** name just be the login. |
| 372 | ** |
| 373 | ** Space to hold the returned name is obtained from fossil_strdup() or |
| 374 | ** mprintf() and should be freed by the caller. |
| 375 | */ |
| 376 | static char *display_name_from_login(const char *zLogin){ |
| 377 | static Stmt q; |
| 378 | char *zResult; |
| 379 | db_static_prepare(&q, |
| 380 | "SELECT display_name(info) FROM user WHERE login=$login" |
| 381 | ); |
| @@ -387,10 +391,34 @@ | |
| 391 | zResult = fossil_strdup(zLogin); |
| 392 | } |
| 393 | db_reset(&q); |
| 394 | return zResult; |
| 395 | } |
| 396 | |
| 397 | /* |
| 398 | ** Compute and return the display name for a ForumPost. If |
| 399 | ** pManifest is not NULL, then it is a Manifest object for the post. |
| 400 | ** if pManifest is NULL, this routine has to fetch and parse the |
| 401 | ** Manifest object for itself. |
| 402 | ** |
| 403 | ** Memory to hold the display name is attached to p->zDisplayName |
| 404 | ** and will be freed together with the ForumPost object p when it |
| 405 | ** is freed. |
| 406 | */ |
| 407 | static char *forum_post_display_name(ForumPost *p, Manifest *pManifest){ |
| 408 | Manifest *pToFree = 0; |
| 409 | if( p->zDisplayName ) return p->zDisplayName; |
| 410 | if( pManifest==0 ){ |
| 411 | pManifest = pToFree = manifest_get(p->fpid, CFTYPE_FORUM, 0); |
| 412 | if( pManifest==0 ) return "(unknown)"; |
| 413 | } |
| 414 | p->zDisplayName = display_name_from_login(pManifest->zUser); |
| 415 | if( pToFree ) manifest_destroy(pToFree); |
| 416 | if( p->zDisplayName==0 ) return "(unknown)"; |
| 417 | return p->zDisplayName; |
| 418 | } |
| 419 | |
| 420 | |
| 421 | /* |
| 422 | ** Display a single post in a forum thread. |
| 423 | */ |
| 424 | static void forum_display_post( |
| @@ -400,29 +428,23 @@ | |
| 428 | int bUnf, /* True to leave the post unformatted */ |
| 429 | int bHist, /* True if showing edit history */ |
| 430 | int bSelect, /* True if this is the selected post */ |
| 431 | char *zQuery /* Common query string */ |
| 432 | ){ |
| 433 | char *zPosterName; /* Name of user who originally made this post */ |
| 434 | char *zEditorName; /* Name of user who provided the current edit */ |
| 435 | char *zDate; /* The time/date string */ |
| 436 | char *zHist; /* History query string */ |
| 437 | Manifest *pManifest; /* Manifest comprising the current post */ |
| 438 | int bPrivate; /* True for posts awaiting moderation */ |
| 439 | int bSameUser; /* True if author is also the reader */ |
| 440 | int iIndent; /* Indent level */ |
| 441 | const char *zMimetype;/* Formatting MIME type */ |
| 442 | |
| 443 | /* Get the manifest for the post. Abort if not found (e.g. shunned). */ |
| 444 | pManifest = manifest_get(p->fpid, CFTYPE_FORUM, 0); |
| 445 | if( !pManifest ) return; |
| 446 | |
| 447 | /* When not in raw mode, create the border around the post. */ |
| 448 | if( !bRaw ){ |
| 449 | /* Open the <div> enclosing the post. Set the class string to mark the post |
| 450 | ** as selected and/or obsolete. */ |
| @@ -434,55 +456,72 @@ | |
| 456 | @ style='margin-left: %d(iIndent*iIndentScale)ex' |
| 457 | } |
| 458 | @ > |
| 459 | |
| 460 | /* If this is the first post (or an edit thereof), emit the thread title. */ |
| 461 | if( pManifest->zThreadTitle ){ |
| 462 | @ <h1>%h(pManifest->zThreadTitle)</h1> |
| 463 | } |
| 464 | |
| 465 | /* Begin emitting the header line. The forum of the title |
| 466 | ** varies depending on whether: |
| 467 | ** * The post is uneditted |
| 468 | ** * The post was last editted by the original author |
| 469 | ** * The post was last editted by a different person |
| 470 | */ |
| 471 | if( p->pEditHead ){ |
| 472 | zDate = db_text(0, "SELECT datetime(%.17g)", p->pEditHead->rDate); |
| 473 | }else{ |
| 474 | zPosterName = forum_post_display_name(p, pManifest); |
| 475 | zEditorName = zPosterName; |
| 476 | } |
| 477 | zDate = db_text(0, "SELECT datetime(%.17g)", p->rDate); |
| 478 | if( p->pEditPrev ){ |
| 479 | zPosterName = forum_post_display_name(p->pEditHead, 0); |
| 480 | zEditorName = forum_post_display_name(p, pManifest); |
| 481 | zHist = bHist ? "" : "&hist"; |
| 482 | @ <h3 class='forumPostHdr'>(%d(p->sid)\ |
| 483 | @ .%0*d(fossil_num_digits(p->nEdit))(p->rev)) \ |
| 484 | if( fossil_strcmp(zPosterName, zEditorName)==0 ){ |
| 485 | @ By %h(zPosterName) on %h(zDate) editted from \ |
| 486 | @ %z(href("%R/forumpost/%S?%s%s",p->pEditPrev->zUuid,zQuery,zHist))\ |
| 487 | @ %d(p->sid).%0*d(fossil_num_digits(p->nEdit))(p->pEditPrev->rev)</a> |
| 488 | }else{ |
| 489 | @ Originally by %h(zPosterName) \ |
| 490 | @ with edits by %h(zEditorName) on %h(zDate) from \ |
| 491 | @ %z(href("%R/forumpost/%S?%s%s",p->pEditPrev->zUuid,zQuery,zHist))\ |
| 492 | @ %d(p->sid).%0*d(fossil_num_digits(p->nEdit))(p->pEditPrev->rev)</a> |
| 493 | } |
| 494 | }else{ |
| 495 | zPosterName = forum_post_display_name(p, pManifest); |
| 496 | @ <h3 class='forumPostHdr'>(%d(p->sid)) \ |
| 497 | @ By %h(zPosterName) on %h(zDate) |
| 498 | } |
| 499 | fossil_free(zDate); |
| 500 | |
| 501 | |
| 502 | /* If debugging is enabled, link to the artifact page. */ |
| 503 | if( g.perm.Debug ){ |
| 504 | @ <span class="debug">\ |
| 505 | @ <a href="%R/artifact/%h(p->zUuid)">(artifact-%d(p->fpid))</a></span> |
| 506 | } |
| 507 | |
| 508 | /* If this is a reply, refer back to the parent post. */ |
| 509 | if( p->pIrt ){ |
| 510 | @ in reply to %z(href("%R/forumpost/%S?%s",p->pIrt->zUuid,zQuery))\ |
| 511 | @ %d(p->pIrt->sid)\ |
| 512 | if( p->pIrt->nEdit ){ |
| 513 | @ .%0*d(fossil_num_digits(p->pIrt->nEdit))(p->pIrt->rev)\ |
| 514 | } |
| 515 | @ </a> |
| 516 | } |
| 517 | |
| 518 | /* If this post was later edited, refer forward to the next edit. */ |
| 519 | if( p->pEditNext ){ |
| 520 | @ updated by %z(href("%R/forumpost/%S?%s",p->pEditNext->zUuid,zQuery))\ |
| 521 | @ %d(p->pEditNext->sid)\ |
| 522 | @ .%0*d(fossil_num_digits(p->nEdit))(p->pEditNext->rev)</a> |
| 523 | } |
| 524 | |
| 525 | /* Provide a link to select the individual post. */ |
| 526 | if( !bSelect ){ |
| 527 | @ %z(href("%R/forumpost/%S?%s",p->zUuid,zQuery))[link]</a> |
| @@ -490,37 +529,28 @@ | |
| 529 | |
| 530 | /* Provide a link to the raw source code. */ |
| 531 | if( !bUnf ){ |
| 532 | @ %z(href("%R/forumpost/%S?raw",p->zUuid))[source]</a> |
| 533 | } |
| 534 | @ </h3> |
| 535 | } |
| 536 | |
| 537 | /* Check if this post is approved, also if it's by the current user. */ |
| 538 | bPrivate = content_is_private(p->fpid); |
| 539 | bSameUser = login_is_individual() |
| 540 | && fossil_strcmp(pManifest->zUser, g.zLogin)==0; |
| 541 | |
| 542 | /* Render the post if the user is able to see it. */ |
| 543 | if( bPrivate && !g.perm.ModForum && !bSameUser ){ |
| 544 | @ <p><span class="modpending">Awaiting Moderator Approval</span></p> |
| 545 | }else{ |
| 546 | if( bRaw || bUnf || p->pEditTail ){ |
| 547 | zMimetype = "text/plain"; |
| 548 | }else{ |
| 549 | zMimetype = pManifest->zMimetype; |
| 550 | } |
| 551 | forum_render(0, zMimetype, pManifest->zWiki, 0, !bRaw); |
| 552 | } |
| 553 | |
| 554 | /* When not in raw mode, finish creating the border around the post. */ |
| 555 | if( !bRaw ){ |
| 556 | /* If the user is able to write to the forum and if this post has not been |
| @@ -539,16 +569,16 @@ | |
| 569 | /* Allow moderators to approve or reject pending posts. Also allow |
| 570 | ** forum supervisors to mark non-special users as trusted and therefore |
| 571 | ** able to post unmoderated. */ |
| 572 | @ <input type="submit" name="approve" value="Approve"> |
| 573 | @ <input type="submit" name="reject" value="Reject"> |
| 574 | if( g.perm.AdminForum && !login_is_special(pManifest->zUser) ){ |
| 575 | @ <br><label><input type="checkbox" name="trust"> |
| 576 | @ Trust user "%h(pManifest->zUser)" so that future posts by \ |
| 577 | @ "%h(pManifest->zUser)" do not require moderation. |
| 578 | @ </label> |
| 579 | @ <input type="hidden" name="trustuser" value="%h(pManifest->zUser)"> |
| 580 | } |
| 581 | }else if( bSameUser ){ |
| 582 | /* Allow users to delete (reject) their own pending posts. */ |
| 583 | @ <input type="submit" name="reject" value="Delete"> |
| 584 | } |
| @@ -556,12 +586,11 @@ | |
| 586 | } |
| 587 | @ </div> |
| 588 | } |
| 589 | |
| 590 | /* Clean up. */ |
| 591 | manifest_destroy(pManifest); |
| 592 | } |
| 593 | |
| 594 | /* |
| 595 | ** Possible display modes for forum_display_thread(). |
| 596 | */ |
| 597 |