Fossil SCM

Unify and regularize forum display code

andygoth 2020-08-22 02:38 andygoth-forum-refactor
Commit 6999639bbb87f642816887e2e743209ebb45d3302818f8e94535f070b98bc4de
1 file changed +352 -449
+352 -449
--- src/forum.c
+++ src/forum.c
@@ -89,19 +89,17 @@
8989
fossil_free(pPost);
9090
}
9191
fossil_free(pThread);
9292
}
9393
94
-#if 0 /* not used */
9594
/*
9695
** Search a ForumPost list forwards looking for the post with fpid
9796
*/
9897
static ForumPost *forumpost_forward(ForumPost *p, int fpid){
9998
while( p && p->fpid!=fpid ) p = p->pNext;
10099
return p;
101100
}
102
-#endif
103101
104102
/*
105103
** Search backwards for a ForumPost
106104
*/
107105
static ForumPost *forumpost_backward(ForumPost *p, int fpid){
@@ -358,28 +356,10 @@
358356
if( zClass ){
359357
@ </div>
360358
}
361359
}
362360
363
-/*
364
-** Generate the buttons in the display that allow a forum supervisor to
365
-** mark a user as trusted. Only do this if:
366
-**
367
-** (1) The poster is an individual, not a special user like "anonymous"
368
-** (2) The current user has Forum Supervisor privilege
369
-*/
370
-static void generateTrustControls(Manifest *pPost){
371
- if( !g.perm.AdminForum ) return;
372
- if( login_is_special(pPost->zUser) ) return;
373
- @ <br>
374
- @ <label><input type="checkbox" name="trust">
375
- @ Trust user "%h(pPost->zUser)"
376
- @ so that future posts by "%h(pPost->zUser)" do not require moderation.
377
- @ </label>
378
- @ <input type="hidden" name="trustuser" value="%h(pPost->zUser)">
379
-}
380
-
381361
/*
382362
** Compute a display name from a login name.
383363
**
384364
** If the input login is found in the USER table, then check the USER.INFO
385365
** field to see if it has display-name followed by an email address.
@@ -409,421 +389,324 @@
409389
db_reset(&q);
410390
return zResult;
411391
}
412392
413393
/*
414
-** Display all posts in a forum thread in chronological order
415
-*/
416
-static void forum_display_chronological(int froot, int target, int bRawMode){
417
- ForumThread *pThread = forumthread_create(froot, 0);
418
- ForumPost *p;
419
- int notAnon = login_is_individual();
420
- char cMode = bRawMode ? 'r' : 'c';
421
- for(p=pThread->pFirst; p; p=p->pNext){
422
- char *zDate;
423
- Manifest *pPost;
424
- int isPrivate; /* True for posts awaiting moderation */
425
- int sameUser; /* True if author is also the reader */
426
- const char *zUuid;
427
- char *zDisplayName; /* The display name */
428
-
429
- pPost = manifest_get(p->fpid, CFTYPE_FORUM, 0);
430
- if( pPost==0 ) continue;
431
- if( p->fpid==target ){
432
- @ <div id="forum%d(p->fpid)" class="forumTime forumSel">
433
- }else if( p->pEditTail!=0 ){
434
- @ <div id="forum%d(p->fpid)" class="forumTime forumObs">
435
- }else{
436
- @ <div id="forum%d(p->fpid)" class="forumTime">
437
- }
438
- if( pPost->zThreadTitle ){
439
- @ <h1>%h(pPost->zThreadTitle)</h1>
440
- }
441
- zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate);
442
- zDisplayName = display_name_from_login(pPost->zUser);
443
- @ <h3 class='forumPostHdr'>(%d(p->sid)\
444
- if( p->nEdit ){
445
- @ .%.*d(fossil_num_digits(p->nEdit))(p->rev)\
446
- }
447
- @ ) By %h(zDisplayName) on %h(zDate)
448
- fossil_free(zDisplayName);
449
- fossil_free(zDate);
450
- if( p->pEditPrev ){
451
- @ edit of %z(href("%R/forumpost/%S?t=%c",p->pEditPrev->zUuid,cMode))\
452
- @ %d(p->pEditPrev->sid)\
453
- @ .%.*d(fossil_num_digits(p->nEdit))(p->pEditPrev->rev)</a>
454
- }
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
- if( p->pIrt ){
460
- @ in reply to %z(href("%R/forumpost/%S?t=%c",p->pIrt->zUuid,cMode))\
461
- @ %d(p->pIrt->sid)\
462
- if( p->pIrt->nEdit ){
463
- @ .%.*d(fossil_num_digits(p->pIrt->nEdit))(p->pIrt->rev)\
464
- }
465
- @ </a>
466
- }
467
- zUuid = p->zUuid;
468
- if( p->pEditTail ){
469
- @ updated by %z(href("%R/forumpost/%S?t=%c",p->pEditTail->zUuid,cMode))\
470
- @ %d(p->pEditTail->sid)\
471
- @ .%.*d(fossil_num_digits(p->nEdit))(p->pEditTail->rev)</a>
472
- zUuid = p->pEditTail->zUuid;
473
- }
474
- if( p->fpid!=target ){
475
- @ %z(href("%R/forumpost/%S?t=%c",zUuid,cMode))[link]</a>
476
- }
477
- if( !bRawMode ){
478
- @ %z(href("%R/forumpost/%S?raw",zUuid))[source]</a>
479
- }
480
- isPrivate = content_is_private(p->fpid);
481
- sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0;
482
- @ </h3>
483
- if( isPrivate && !g.perm.ModForum && !sameUser ){
484
- @ <p><span class="modpending">Awaiting Moderator Approval</span></p>
485
- }else{
486
- const char *zMimetype;
487
- if( bRawMode ){
488
- zMimetype = "text/plain";
489
- }else if( p->pEditTail!=0 ){
490
- zMimetype = "text/plain";
491
- }else{
492
- zMimetype = pPost->zMimetype;
493
- }
494
- forum_render(0, zMimetype, pPost->zWiki, 0, 1);
495
- }
496
- if( g.perm.WrForum && p->pEditTail==0 ){
497
- int sameUser = login_is_individual()
498
- && fossil_strcmp(pPost->zUser, g.zLogin)==0;
499
- @ <div><form action="%R/forumedit" method="POST">
500
- @ <input type="hidden" name="fpid" value="%s(p->zUuid)">
501
- if( !isPrivate ){
502
- /* Reply and Edit are only available if the post has already
503
- ** been approved */
504
- @ <input type="submit" name="reply" value="Reply">
505
- if( g.perm.Admin || sameUser ){
506
- @ <input type="submit" name="edit" value="Edit">
507
- @ <input type="submit" name="nullout" value="Delete">
508
- }
509
- }else if( g.perm.ModForum ){
510
- /* Provide moderators with moderation buttons for posts that
511
- ** are pending moderation */
512
- @ <input type="submit" name="approve" value="Approve">
513
- @ <input type="submit" name="reject" value="Reject">
514
- generateTrustControls(pPost);
515
- }else if( sameUser ){
516
- /* A post that is pending moderation can be deleted by the
517
- ** person who originally submitted the post */
518
- @ <input type="submit" name="reject" value="Delete">
519
- }
520
- @ </form></div>
521
- }
522
- manifest_destroy(pPost);
523
- @ </div>
524
- }
525
-
526
- /* Undocumented "threadtable" query parameter causes thread table
527
- ** to be displayed for debugging purposes.
528
- */
529
- if( PB("threadtable") ){
530
- @ <hr>
531
- @ <table border="1" cellpadding="3" cellspacing="0">
532
- @ <tr><th>sid<th>rev<th>fpid<th>pIrt<th>pEditHead<th>pEditTail\
533
- @ <th>pEditNext<th>pEditPrev<th>hash
534
- for(p=pThread->pFirst; p; p=p->pNext){
535
- @ <tr><td>%d(p->sid)<td>%d(p->rev)<td>%d(p->fpid)\
536
- @ <td>%d(p->pIrt?p->pIrt->fpid:0)\
537
- @ <td>%d(p->pEditHead?p->pEditHead->fpid:0)\
538
- @ <td>%d(p->pEditTail?p->pEditTail->fpid:0)\
539
- @ <td>%d(p->pEditNext?p->pEditNext->fpid:0)\
540
- @ <td>%d(p->pEditPrev?p->pEditPrev->fpid:0)\
541
- @ <td>%S(p->zUuid)</tr>
542
- }
543
- @ </table>
544
- }
545
-
546
- forumthread_delete(pThread);
547
-}
548
-/*
549
-** Display all the edit history of post "target".
550
-*/
551
-static void forum_display_history(int froot, int target, int bRawMode){
552
- ForumThread *pThread = forumthread_create(froot, 0);
553
- ForumPost *p;
554
- int notAnon = login_is_individual();
555
- char cMode = bRawMode ? 'r' : 'c';
556
- ForumPost *pEditTail = 0;
557
- int cnt = 0;
558
- for(p=pThread->pFirst; p; p=p->pNext){
559
- if( p->fpid==target ){
560
- pEditTail = p->pEditTail ? p->pEditTail : p;
561
- break;
562
- }
563
- }
564
- for(p=pThread->pFirst; p; p=p->pNext){
565
- char *zDate;
566
- Manifest *pPost;
567
- int isPrivate; /* True for posts awaiting moderation */
568
- int sameUser; /* True if author is also the reader */
569
- const char *zUuid;
570
- char *zDisplayName; /* The display name */
571
-
572
- if( p->fpid!=pEditTail->fpid && p->pEditTail!=pEditTail ) continue;
573
- cnt++;
574
- pPost = manifest_get(p->fpid, CFTYPE_FORUM, 0);
575
- if( pPost==0 ) continue;
576
- @ <div id="forum%d(p->fpid)" class="forumTime">
577
- zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate);
578
- zDisplayName = display_name_from_login(pPost->zUser);
394
+** Display a single post in a forum thread.
395
+*/
396
+static void forum_display_post(
397
+ ForumPost *p, /* Forum post to display */
398
+ int iIndentScale, /* Indent scale factor */
399
+ int bRaw, /* True to omit the border */
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 *pManifest; /* Manifest comprising the current post */
409
+ int bPrivate; /* True for posts awaiting moderation */
410
+ int bSameUser; /* True if author is also the reader */
411
+ int iIndent; /* Indent level */
412
+ const char *zMimetype;/* Formatting MIME type */
413
+
414
+ /* Get the manifest for the post. Abort if not found (e.g. shunned). */
415
+ pManifest = manifest_get(p->fpid, CFTYPE_FORUM, 0);
416
+ if( !pManifest ) return;
417
+
418
+ /* When not in raw mode, create the border around the post. */
419
+ if( !bRaw ){
420
+ /* Open the <div> enclosing the post. Set the class string to mark the post
421
+ ** as selected and/or obsolete. */
422
+ iIndent = (p->pEditHead ? p->pEditHead->nIndent : p->nIndent)-1;
423
+ @ <div id='forum%d(p->fpid)' class='forumTime\
424
+ @ %s(bSelect ? " forumSel" : "")\
425
+ @ %s(p->pEditTail ? " forumObs" : "")'\
426
+ if( iIndent && iIndentScale ){
427
+ @ style='margin-left: %d(iIndent*iIndentScale)ex'
428
+ }
429
+ @ >
430
+
431
+ /* If this is the first post (or an edit thereof), emit the thread title. */
432
+ if( pManifest->zThreadTitle ){
433
+ @ <h1>%h(pManifest->zThreadTitle)</h1>
434
+ }
435
+
436
+ /* Emit the serial number, revision number, author, and date. */
437
+ zDisplayName = display_name_from_login(pManifest->zUser);
438
+ zDate = db_text(0, "SELECT datetime(%.17g)", pManifest->rDate);
579439
@ <h3 class='forumPostHdr'>(%d(p->sid)\
580440
if( p->nEdit ){
581441
@ .%.*d(fossil_num_digits(p->nEdit))(p->rev)\
582442
}
583443
@ ) By %h(zDisplayName) on %h(zDate)
584444
fossil_free(zDisplayName);
585445
fossil_free(zDate);
446
+
447
+ /* If this is an edit, refer back to the old version. Be sure "hist" is in
448
+ ** the query string so the old version will actually be shown. */
449
+ if( p->pEditPrev ){
450
+ zHist = bHist ? "" : "&hist";
451
+ @ edit of \
452
+ @ %z(href("%R/forumpost/%S?%s%s",p->pEditPrev->zUuid,zQuery,zHist))\
453
+ @ %d(p->sid).%.*d(fossil_num_digits(p->nEdit))(p->pEditPrev->rev)</a>
454
+ }
455
+
456
+ /* If debugging is enabled, link to the artifact page. */
586457
if( g.perm.Debug ){
587458
@ <span class="debug">\
588459
@ <a href="%R/artifact/%h(p->zUuid)">(artifact-%d(p->fpid))</a></span>
589460
}
590
- if( p->pIrt && cnt==1 ){
591
- @ in reply to %z(href("%R/forumpost/%S?t=%c",p->pIrt->zUuid,cMode))\
461
+
462
+ /* If this is a reply, refer back to the parent post. */
463
+ if( p->pIrt ){
464
+ @ in reply to %z(href("%R/forumpost/%S?%s",p->pIrt->zUuid,zQuery))\
592465
@ %d(p->pIrt->sid)\
593466
if( p->pIrt->nEdit ){
594467
@ .%.*d(fossil_num_digits(p->pIrt->nEdit))(p->pIrt->rev)\
595468
}
596469
@ </a>
597470
}
598
- zUuid = p->zUuid;
599
- @ %z(href("%R/forumpost/%S?t=c",zUuid))[link]</a>
600
- if( !bRawMode ){
601
- @ %z(href("%R/forumpost/%S?raw",zUuid))[source]</a>
602
- }
603
- isPrivate = content_is_private(p->fpid);
604
- sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0;
605
- @ </h3>
606
- if( isPrivate && !g.perm.ModForum && !sameUser ){
607
- @ <p><span class="modpending">Awaiting Moderator Approval</span></p>
608
- }else{
609
- forum_render(0, bRawMode?"text/plain":pPost->zMimetype, pPost->zWiki,
610
- 0, 1);
611
- }
612
- if( g.perm.WrForum && p->pEditTail==0 ){
613
- int sameUser = login_is_individual()
614
- && fossil_strcmp(pPost->zUser, g.zLogin)==0;
615
- @ <div><form action="%R/forumedit" method="POST">
616
- @ <input type="hidden" name="fpid" value="%s(p->zUuid)">
617
- if( !isPrivate ){
618
- /* Reply and Edit are only available if the post has already
619
- ** been approved */
620
- @ <input type="submit" name="reply" value="Reply">
621
- if( g.perm.Admin || sameUser ){
622
- @ <input type="submit" name="edit" value="Edit">
623
- @ <input type="submit" name="nullout" value="Delete">
624
- }
625
- }else if( g.perm.ModForum ){
626
- /* Provide moderators with moderation buttons for posts that
627
- ** are pending moderation */
628
- @ <input type="submit" name="approve" value="Approve">
629
- @ <input type="submit" name="reject" value="Reject">
630
- generateTrustControls(pPost);
631
- }else if( sameUser ){
632
- /* A post that is pending moderation can be deleted by the
633
- ** person who originally submitted the post */
634
- @ <input type="submit" name="reject" value="Delete">
635
- }
636
- @ </form></div>
637
- }
638
- manifest_destroy(pPost);
639
- @ </div>
640
- }
641
- forumthread_delete(pThread);
642
-}
643
-
644
-/*
645
-** Display all messages in a forumthread with indentation.
646
-*/
647
-static int forum_display_hierarchical(int froot, int target){
648
- ForumThread *pThread;
649
- ForumPost *p;
650
- Manifest *pPost, *pOPost;
651
- int fpid;
652
- const char *zUuid;
653
- char *zDate;
654
- const char *zSel;
655
- int notAnon = login_is_individual();
656
- int iIndentScale = 4;
657
-
658
- pThread = forumthread_create(froot, 1);
659
- for(p=pThread->pFirst; p; p=p->pNext){
660
- if( p->fpid==target ){
661
- if( p->pEditHead ) p = p->pEditHead;
662
- target = p->fpid;
663
- break;
664
- }
665
- }
666
- while( iIndentScale>1 && iIndentScale*pThread->mxIndent>25 ){
667
- iIndentScale--;
668
- }
669
- for(p=pThread->pDisplay; p; p=p->pDisplay){
670
- int isPrivate; /* True for posts awaiting moderation */
671
- int sameUser; /* True if reader is also the poster */
672
- char *zDisplayName; /* User name to be displayed */
673
- pOPost = manifest_get(p->fpid, CFTYPE_FORUM, 0);
674
- if( p->pEditTail ){
675
- fpid = p->pEditTail->fpid;
676
- zUuid = p->pEditTail->zUuid;
677
- pPost = manifest_get(fpid, CFTYPE_FORUM, 0);
678
- }else{
679
- fpid = p->fpid;
680
- zUuid = p->zUuid;
681
- pPost = pOPost;
682
- }
683
- zSel = p->fpid==target ? " forumSel" : "";
684
- if( p->nIndent==1 ){
685
- @ <div id='forum%d(fpid)' class='forumHierRoot%s(zSel)'>
686
- }else{
687
- @ <div id='forum%d(fpid)' class='forumHier%s(zSel)' \
688
- @ style='margin-left: %d((p->nIndent-1)*iIndentScale)ex;'>
689
- }
690
- if( pPost==0 ) continue;
691
- if( pPost->zThreadTitle ){
692
- @ <h1>%h(pPost->zThreadTitle)</h1>
693
- }
694
- zDate = db_text(0, "SELECT datetime(%.17g)", pOPost->rDate);
695
- zDisplayName = display_name_from_login(pOPost->zUser);
696
- @ <h3 class='forumPostHdr'>\
697
- @ (%d(p->sid)) By %h(zDisplayName) on %h(zDate)
698
- fossil_free(zDisplayName);
699
- fossil_free(zDate);
700
- if( g.perm.Debug ){
701
- @ <span class="debug">\
702
- @ <a href="%R/artifact/%h(p->zUuid)">(artifact-%d(p->fpid))</a></span>
703
- }
704
- if( p->pEditTail ){
705
- zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate);
706
- if( fossil_strcmp(pOPost->zUser,pPost->zUser)==0 ){
707
- @ and edited on %h(zDate)
708
- }else{
709
- @ as edited by %h(pPost->zUser) on %h(zDate)
710
- }
711
- fossil_free(zDate);
712
- if( g.perm.Debug ){
713
- @ <span class="debug">\
714
- @ <a href="%R/artifact/%h(p->pEditTail->zUuid)">\
715
- @ (artifact-%d(p->pEditTail->fpid))</a></span>
716
- }
717
- @ %z(href("%R/forumpost/%S?t=y",p->zUuid))[history]</a>
718
- manifest_destroy(pOPost);
719
- }
720
- if( fpid!=target ){
721
- @ %z(href("%R/forumpost/%S",zUuid))[link]</a>
722
- }
723
- @ %z(href("%R/forumpost/%S?raw",zUuid))[source]</a>
724
- if( p->pIrt ){
725
- @ in reply to %z(href("%R/forumpost/%S?t=h",p->pIrt->zUuid))\
726
- @ %d(p->pIrt->sid)</a>
727
- }
728
- @ </h3>
729
- isPrivate = content_is_private(fpid);
730
- sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0;
731
- if( isPrivate && !g.perm.ModForum && !sameUser ){
732
- @ <p><span class="modpending">Awaiting Moderator Approval</span></p>
733
- }else{
734
- forum_render(0, pPost->zMimetype, pPost->zWiki, 0, 1);
735
- }
736
- if( g.perm.WrForum ){
737
- @ <div><form action="%R/forumedit" method="POST">
738
- @ <input type="hidden" name="fpid" value="%s(zUuid)">
739
- if( !isPrivate ){
740
- /* Reply and Edit are only available if the post has already
741
- ** been approved */
742
- @ <input type="submit" name="reply" value="Reply">
743
- if( g.perm.Admin || sameUser ){
744
- @ <input type="submit" name="edit" value="Edit">
745
- @ <input type="submit" name="nullout" value="Delete">
746
- }
747
- }else if( g.perm.ModForum ){
748
- /* Provide moderators with moderation buttons for posts that
749
- ** are pending moderation */
750
- @ <input type="submit" name="approve" value="Approve">
751
- @ <input type="submit" name="reject" value="Reject">
752
- generateTrustControls(pPost);
753
- }else if( sameUser ){
754
- /* A post that is pending moderation can be deleted by the
755
- ** person who originally submitted the post */
756
- @ <input type="submit" name="reject" value="Delete">
757
- }
758
- @ </form></div>
759
- }
760
- manifest_destroy(pPost);
761
- @ </div>
762
- }
763
- forumthread_delete(pThread);
764
- return target;
765
-}
766
-
767
-/*
768
-** Emits all JS code required by /forumpost.
769
-*/
770
-static void forumpost_emit_page_js(){
771
- static int once = 0;
772
- if(0==once){
773
- once = 1;
774
- style_emit_script_fossil_bootstrap(1);
775
- builtin_request_js("forum.js");
776
- builtin_request_js("fossil.dom.js");
777
- builtin_request_js("fossil.page.forumpost.js");
778
- }
471
+
472
+ /* If this post was later edited, refer forward to the new version. */
473
+ if( p->pEditTail ){
474
+ @ updated by %z(href("%R/forumpost/%S?%s",p->pEditTail->zUuid,zQuery))\
475
+ @ %d(p->pEditTail->sid)\
476
+ @ .%.*d(fossil_num_digits(p->nEdit))(p->pEditTail->rev)</a>
477
+ }
478
+
479
+ /* Provide a link to select the individual post. */
480
+ if( !bSelect ){
481
+ @ %z(href("%R/forumpost/%S?%s",p->zUuid,zQuery))[link]</a>
482
+ }
483
+
484
+ /* Provide a link to the raw source code. */
485
+ if( !bUnf ){
486
+ @ %z(href("%R/forumpost/%S?raw",p->zUuid))[source]</a>
487
+ }
488
+ @ </h3>
489
+ }
490
+
491
+ /* Check if this post is approved, also if it's by the current user. */
492
+ bPrivate = content_is_private(p->fpid);
493
+ bSameUser = login_is_individual()
494
+ && fossil_strcmp(pManifest->zUser, g.zLogin)==0;
495
+
496
+ /* Render the post if the user is able to see it. */
497
+ if( bPrivate && !g.perm.ModForum && !bSameUser ){
498
+ @ <p><span class="modpending">Awaiting Moderator Approval</span></p>
499
+ }else{
500
+ if( bRaw || bUnf || p->pEditTail ){
501
+ zMimetype = "text/plain";
502
+ }else{
503
+ zMimetype = pManifest->zMimetype;
504
+ }
505
+ forum_render(0, zMimetype, pManifest->zWiki, 0, !bRaw);
506
+ }
507
+
508
+ /* When not in raw mode, finish creating the border around the post. */
509
+ if( !bRaw ){
510
+ /* If the user is able to write to the forum and if this post has not been
511
+ ** edited, create a form with various interaction buttons. */
512
+ if( g.perm.WrForum && !p->pEditTail ){
513
+ @ <div><form action="%R/forumedit" method="POST">
514
+ @ <input type="hidden" name="fpid" value="%s(p->zUuid)">
515
+ if( !bPrivate ){
516
+ /* Reply and Edit are only available if the post has been approved. */
517
+ @ <input type="submit" name="reply" value="Reply">
518
+ if( g.perm.Admin || bSameUser ){
519
+ @ <input type="submit" name="edit" value="Edit">
520
+ @ <input type="submit" name="nullout" value="Delete">
521
+ }
522
+ }else if( g.perm.ModForum ){
523
+ /* Allow moderators to approve or reject pending posts. Also allow
524
+ ** forum supervisors to mark non-special users as trusted and therefore
525
+ ** able to post unmoderated. */
526
+ @ <input type="submit" name="approve" value="Approve">
527
+ @ <input type="submit" name="reject" value="Reject">
528
+ if( g.perm.AdminForum && !login_is_special(pManifest->zUser) ){
529
+ @ <br><label><input type="checkbox" name="trust">
530
+ @ Trust user "%h(pManifest->zUser)" so that future posts by \
531
+ @ "%h(pManifest->zUser)" do not require moderation.
532
+ @ </label>
533
+ @ <input type="hidden" name="trustuser" value="%h(pManifest->zUser)">
534
+ }
535
+ }else if( bSameUser ){
536
+ /* Allow users to delete (reject) their own pending posts. */
537
+ @ <input type="submit" name="reject" value="Delete">
538
+ }
539
+ @ </form></div>
540
+ }
541
+ @ </div>
542
+ }
543
+
544
+ /* Clean up. */
545
+ manifest_destroy(pManifest);
546
+}
547
+
548
+/*
549
+** Possible display modes for forum_display_thread().
550
+*/
551
+enum {
552
+ FD_RAW, /* Like FD_SINGLE, but additionally omit the border, force
553
+ ** unformatted mode, and inhibit history mode */
554
+ FD_SINGLE, /* Render a single post and (optionally) its edit history */
555
+ FD_CHRONO, /* Render all posts in chronological order */
556
+ FD_HIER, /* Render all posts in an indented hierarchy */
557
+};
558
+
559
+/*
560
+** Display a forum thread. If mode is FD_RAW or FD_SINGLE, display only a
561
+** single post from the thread and (optionally) its edit history.
562
+*/
563
+static void forum_display_thread(
564
+ int froot, /* Forum thread root post ID */
565
+ int fpid, /* Selected forum post ID, or 0 if none selected */
566
+ int mode, /* Forum display mode, one of the FD_* enumerations */
567
+ int bUnf, /* True if rendering unformatted */
568
+ int bHist /* True if showing edit history, ignored for FD_RAW */
569
+){
570
+ ForumThread *pThread; /* Thread structure */
571
+ ForumPost *pSelect; /* Currently selected post, or NULL if none */
572
+ ForumPost *p; /* Post iterator pointer */
573
+ char *zQuery; /* Common query string */
574
+ int iIndentScale = 4; /* Indent scale factor, measured in "ex" units */
575
+
576
+ /* In raw mode, force unformatted display and disable history. */
577
+ if( mode == FD_RAW ){
578
+ bUnf = 1;
579
+ bHist = 0;
580
+ }
581
+
582
+ /* Thread together the posts and (optionally) compute the hierarchy. */
583
+ pThread = forumthread_create(froot, mode==FD_HIER);
584
+
585
+ /* Compute the appropriate indent scaling. */
586
+ if( mode==FD_HIER ){
587
+ iIndentScale = 4;
588
+ while( iIndentScale>1 && iIndentScale*pThread->mxIndent>25 ){
589
+ iIndentScale--;
590
+ }
591
+ }else{
592
+ iIndentScale = 0;
593
+ }
594
+
595
+ /* Find the selected post, or (if history not shown) its latest edit. */
596
+ pSelect = fpid ? forumpost_forward(pThread->pFirst, fpid) : 0;
597
+ if( !bHist && pSelect && pSelect->pEditTail ){
598
+ pSelect = pSelect->pEditTail;
599
+ }
600
+
601
+ /* When displaying only a single post, abort if no post was selected or the
602
+ ** selected forum post does not exist in the thread. Otherwise proceed to
603
+ ** display the entire thread without marking any posts as selected. */
604
+ if( !pSelect && (mode==FD_RAW || mode==FD_SINGLE) ){
605
+ return;
606
+ }
607
+
608
+ /* Create the common query string to append to nearly all post links. */
609
+ zQuery = mode==FD_RAW ? 0 : mprintf("t=%c%s%s",
610
+ mode==FD_SINGLE ? 's' : mode==FD_CHRONO ? 'c' : 'h',
611
+ bUnf ? "&unf" : "", bHist ? "&hist" : "");
612
+
613
+ /* Identify which post to display first. If history is shown, start with the
614
+ ** original, unedited post. Otherwise advance to the post's latest edit. */
615
+ if( mode==FD_RAW || mode==FD_SINGLE ){
616
+ p = pSelect;
617
+ if( bHist && p->pEditHead ) p = p->pEditHead;
618
+ }else{
619
+ p = mode==FD_CHRONO ? pThread->pFirst : pThread->pDisplay;
620
+ if( !bHist && p->pEditTail ) p = p->pEditTail;
621
+ }
622
+
623
+ /* Display the appropriate subset of posts in sequence. */
624
+ while( p ){
625
+ /* Display the post. */
626
+ forum_display_post(p, iIndentScale, mode==FD_RAW,
627
+ bUnf, bHist, p==pSelect, zQuery);
628
+
629
+ /* Advance to the next post in the thread. */
630
+ if( mode==FD_CHRONO ){
631
+ /* Chronological mode: display posts (optionally including edits) in their
632
+ ** original commit order. */
633
+ if( bHist ){
634
+ p = p->pNext;
635
+ }else{
636
+ if( p->pEditHead ) p = p->pEditHead;
637
+ do p = p->pNext; while( p && p->sid<=p->pPrev->sid );
638
+ if( p && p->pEditTail ) p = p->pEditTail;
639
+ }
640
+ }else if( bHist && p->pEditNext ){
641
+ /* Hierarchical and single mode: display each post's edits in sequence. */
642
+ p = p->pEditNext;
643
+ }else if( mode==FD_HIER ){
644
+ /* Hierarchical mode: after displaying with each post (optionally
645
+ ** including edits), go to the next post in computed display order. */
646
+ p = p->pEditHead ? p->pEditHead->pDisplay : p->pDisplay;
647
+ if( !bHist && p && p->pEditTail ) p = p->pEditTail;
648
+ }else{
649
+ /* Single and raw mode: terminate after displaying the selected post and
650
+ ** (optionally) its edits. */
651
+ break;
652
+ }
653
+ }
654
+
655
+ /* Undocumented "threadtable" query parameter causes thread table to be
656
+ ** displayed for debugging purposes. */
657
+ if( PB("threadtable") ){
658
+ @ <hr>
659
+ @ <table border="1" cellpadding="3" cellspacing="0">
660
+ @ <tr><th>sid<th>rev<th>fpid<th>pIrt<th>pEditHead<th>pEditTail\
661
+ @ <th>pEditNext<th>pEditPrev<th>pDisplay<th>hash
662
+ for(p=pThread->pFirst; p; p=p->pNext){
663
+ @ <tr><td>%d(p->sid)<td>%d(p->rev)<td>%d(p->fpid)\
664
+ @ <td>%d(p->pIrt ? p->pIrt->fpid : 0)\
665
+ @ <td>%d(p->pEditHead ? p->pEditHead->fpid : 0)\
666
+ @ <td>%d(p->pEditTail ? p->pEditTail->fpid : 0)\
667
+ @ <td>%d(p->pEditNext ? p->pEditNext->fpid : 0)\
668
+ @ <td>%d(p->pEditPrev ? p->pEditPrev->fpid : 0)\
669
+ @ <td>%d(p->pDisplay ? p->pDisplay->fpid : 0)\
670
+ @ <td>%S(p->zUuid)</tr>
671
+ }
672
+ @ </table>
673
+ }
674
+
675
+ /* Clean up. */
676
+ forumthread_delete(pThread);
677
+ fossil_free(zQuery);
779678
}
780679
781680
/*
782681
** WEBPAGE: forumpost
783682
**
784683
** Show a single forum posting. The posting is shown in context with
785
-** it's entire thread. The selected posting is enclosed within
684
+** its entire thread. The selected posting is enclosed within
786685
** <div class='forumSel'>...</div>. Javascript is used to move the
787686
** selected posting into view after the page loads.
788687
**
789688
** Query parameters:
790689
**
791
-** name=X REQUIRED. The hash of the post to display
792
-** t=MODE Display mode.
793
-** 'c' for chronological
794
-** 'h' for hierarchical
795
-** 'a' for automatic
796
-** 'r' for raw
797
-** 'y' for history of post X only
798
-** raw If present, show only the post specified and
799
-** show its original unformatted source text.
690
+** name=X REQUIRED. The hash of the post to display.
691
+** t=a Automatic display mode, i.e. hierarchical for
692
+** desktop and chronological for mobile. This is the
693
+** default if the "t" query parameter is omitted.
694
+** t=c Show posts in the order they were written.
695
+** t=h Show posts usin hierarchical indenting.
696
+** t=s Show only the post specified by "name=X".
697
+** t=r Alias for "t=c&unf&hist".
698
+** t=y Alias for "t=s&unf&hist".
699
+** raw Alias for "t=s&unf". Additionally, omit the border
700
+** around the post, and ignore "t" and "hist".
701
+** unf Show the original, unformatted source text.
702
+** hist Show edit history in addition to current posts.
800703
*/
801704
void forumpost_page(void){
802705
forumthread_page();
803706
}
804707
805
-/*
806
-** Add an appropriate style_header() to include title of the
807
-** given forum post.
808
-*/
809
-static int forumthread_page_header(int froot, int fpid){
810
- char *zThreadTitle = 0;
811
-
812
- zThreadTitle = db_text("",
813
- "SELECT"
814
- " substr(event.comment,instr(event.comment,':')+2)"
815
- " FROM forumpost, event"
816
- " WHERE event.objid=forumpost.fpid"
817
- " AND forumpost.fpid=%d;",
818
- fpid
819
- );
820
- style_header("%s%s", zThreadTitle, zThreadTitle[0] ? "" : "Forum");
821
- fossil_free(zThreadTitle);
822
- return 0;
823
-}
824
-
825708
/*
826709
** WEBPAGE: forumthread
827710
**
828711
** Show all forum messages associated with a particular message thread.
829712
** The result is basically the same as /forumpost except that none of
@@ -830,24 +713,28 @@
830713
** the postings in the thread are selected.
831714
**
832715
** Query parameters:
833716
**
834717
** name=X REQUIRED. The hash of any post of the thread.
835
-** t=MODE Display mode. MODE is...
836
-** 'c' for chronological, or
837
-** 'h' for hierarchical, or
838
-** 'a' for automatic, or
839
-** 'r' for raw.
840
-** raw Show only the post given by name= and show it unformatted
841
-** hist Show only the edit history for the name= post
718
+** t=a Automatic display mode, i.e. hierarchical for
719
+** desktop and chronological for mobile. This is the
720
+** default if the "t" query parameter is omitted.
721
+** t=c Show posts in the order they were written.
722
+** t=h Show posts using hierarchical indenting.
723
+** unf Show the original, unformatted source text.
724
+** hist Show edit history in addition to current posts.
842725
*/
843726
void forumthread_page(void){
844727
int fpid;
845728
int froot;
729
+ char *zThreadTitle;
846730
const char *zName = P("name");
847731
const char *zMode = PD("t","a");
848732
int bRaw = PB("raw");
733
+ int bUnf = PB("unf");
734
+ int bHist = PB("hist");
735
+ int mode;
849736
login_check_credentials();
850737
if( !g.perm.RdForum ){
851738
login_needed(g.anon.RdForum);
852739
return;
853740
}
@@ -861,54 +748,70 @@
861748
froot = db_int(0, "SELECT froot FROM forumpost WHERE fpid=%d", fpid);
862749
if( froot==0 ){
863750
webpage_error("Not a forum post: \"%s\"", zName);
864751
}
865752
if( fossil_strcmp(g.zPath,"forumthread")==0 ) fpid = 0;
866
- if( zMode[0]=='a' ){
867
- if( cgi_from_mobile() ){
868
- zMode = "c"; /* Default to chronological on mobile */
869
- }else{
870
- zMode = "h";
871
- }
872
- }
873
- if( zMode[0]!='y' ){
874
- forumthread_page_header(froot, fpid);
875
- }
876
- if( bRaw && fpid ){
877
- Manifest *pPost;
878
- pPost = manifest_get(fpid, CFTYPE_FORUM, 0);
879
- if( pPost==0 ){
880
- @ <p>No such forum post: %h(zName)
881
- }else{
882
- int isPrivate = content_is_private(fpid);
883
- int notAnon = login_is_individual();
884
- int sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0;
885
- if( isPrivate && !g.perm.ModForum && !sameUser ){
886
- @ <p><span class="modpending">Awaiting Moderator Approval</span></p>
887
- }else{
888
- forum_render(0, "text/plain", pPost->zWiki, 0, 0);
889
- }
890
- manifest_destroy(pPost);
891
- }
892
- }else if( zMode[0]=='c' ){
893
- style_submenu_element("Hierarchical", "%R/%s/%s?t=h", g.zPath, zName);
894
- style_submenu_element("Unformatted", "%R/%s/%s?t=r", g.zPath, zName);
895
- forum_display_chronological(froot, fpid, 0);
896
- }else if( zMode[0]=='r' ){
897
- style_submenu_element("Chronological", "%R/%s/%s?t=c", g.zPath, zName);
898
- style_submenu_element("Hierarchical", "%R/%s/%s?t=h", g.zPath, zName);
899
- forum_display_chronological(froot, fpid, 1);
900
- }else if( zMode[0]=='y' ){
901
- style_header("Edit History Of A Forum Post");
902
- style_submenu_element("Complete Thread", "%R/%s/%s?t=a", g.zPath, zName);
903
- forum_display_history(froot, fpid, 1);
904
- }else{
905
- style_submenu_element("Chronological", "%R/%s/%s?t=c", g.zPath, zName);
906
- style_submenu_element("Unformatted", "%R/%s/%s?t=r", g.zPath, zName);
907
- forum_display_hierarchical(froot, fpid);
908
- }
909
- forumpost_emit_page_js();
753
+
754
+ /* Decode the mode parameters. */
755
+ if( bRaw ){
756
+ mode = FD_RAW;
757
+ bUnf = 1;
758
+ bHist = 0;
759
+ cgi_replace_query_parameter("unf", "on");
760
+ cgi_delete_query_parameter("hist");
761
+ cgi_delete_query_parameter("raw");
762
+ }else{
763
+ switch( *zMode ){
764
+ case 'a': mode = cgi_from_mobile() ? FD_CHRONO : FD_HIER; break;
765
+ case 'c': mode = FD_CHRONO; break;
766
+ case 'h': mode = FD_HIER; break;
767
+ case 's': mode = FD_SINGLE; break;
768
+ case 'r': mode = FD_CHRONO; break;
769
+ case 'y': mode = FD_SINGLE; break;
770
+ default: webpage_error("Invalid thread mode: \"%s\"", zMode);
771
+ }
772
+ if( *zMode=='r' || *zMode=='y') {
773
+ bUnf = 1;
774
+ bHist = 1;
775
+ cgi_replace_query_parameter("t", mode==FD_CHRONO ? "c" : "s");
776
+ cgi_replace_query_parameter("unf", "on");
777
+ cgi_replace_query_parameter("hist", "on");
778
+ }
779
+ }
780
+
781
+ /* Define the page header. */
782
+ zThreadTitle = db_text("",
783
+ "SELECT"
784
+ " substr(event.comment,instr(event.comment,':')+2)"
785
+ " FROM forumpost, event"
786
+ " WHERE event.objid=forumpost.fpid"
787
+ " AND forumpost.fpid=%d;",
788
+ fpid
789
+ );
790
+ style_header("%s%s", zThreadTitle, *zThreadTitle ? "" : "Forum");
791
+ fossil_free(zThreadTitle);
792
+ if( mode!=FD_CHRONO ){
793
+ style_submenu_element("Chronological", "%R/%s/%s?t=c%s%s", g.zPath, zName,
794
+ bUnf ? "&unf" : "", bHist ? "&hist" : "");
795
+ }
796
+ if( mode!=FD_HIER ){
797
+ style_submenu_element("Hierarchical", "%R/%s/%s?t=h%s%s", g.zPath, zName,
798
+ bUnf ? "&unf" : "", bHist ? "&hist" : "");
799
+ }
800
+ style_submenu_checkbox("unf", "Unformatted", 0, 0);
801
+ style_submenu_checkbox("hist", "History", 0, 0);
802
+
803
+ /* Display the thread. */
804
+ forum_display_thread(froot, fpid, mode, bUnf, bHist);
805
+
806
+ /* Emit Forum Javascript. */
807
+ style_emit_script_fossil_bootstrap(1);
808
+ builtin_request_js("forum.js");
809
+ builtin_request_js("fossil.dom.js");
810
+ builtin_request_js("fossil.page.forumpost.js");
811
+
812
+ /* Emit the page style. */
910813
style_footer();
911814
}
912815
913816
/*
914817
** Return true if a forum post should be moderated.
915818
--- src/forum.c
+++ src/forum.c
@@ -89,19 +89,17 @@
89 fossil_free(pPost);
90 }
91 fossil_free(pThread);
92 }
93
94 #if 0 /* not used */
95 /*
96 ** Search a ForumPost list forwards looking for the post with fpid
97 */
98 static ForumPost *forumpost_forward(ForumPost *p, int fpid){
99 while( p && p->fpid!=fpid ) p = p->pNext;
100 return p;
101 }
102 #endif
103
104 /*
105 ** Search backwards for a ForumPost
106 */
107 static ForumPost *forumpost_backward(ForumPost *p, int fpid){
@@ -358,28 +356,10 @@
358 if( zClass ){
359 @ </div>
360 }
361 }
362
363 /*
364 ** Generate the buttons in the display that allow a forum supervisor to
365 ** mark a user as trusted. Only do this if:
366 **
367 ** (1) The poster is an individual, not a special user like "anonymous"
368 ** (2) The current user has Forum Supervisor privilege
369 */
370 static void generateTrustControls(Manifest *pPost){
371 if( !g.perm.AdminForum ) return;
372 if( login_is_special(pPost->zUser) ) return;
373 @ <br>
374 @ <label><input type="checkbox" name="trust">
375 @ Trust user "%h(pPost->zUser)"
376 @ so that future posts by "%h(pPost->zUser)" do not require moderation.
377 @ </label>
378 @ <input type="hidden" name="trustuser" value="%h(pPost->zUser)">
379 }
380
381 /*
382 ** Compute a display name from a login name.
383 **
384 ** If the input login is found in the USER table, then check the USER.INFO
385 ** field to see if it has display-name followed by an email address.
@@ -409,421 +389,324 @@
409 db_reset(&q);
410 return zResult;
411 }
412
413 /*
414 ** Display all posts in a forum thread in chronological order
415 */
416 static void forum_display_chronological(int froot, int target, int bRawMode){
417 ForumThread *pThread = forumthread_create(froot, 0);
418 ForumPost *p;
419 int notAnon = login_is_individual();
420 char cMode = bRawMode ? 'r' : 'c';
421 for(p=pThread->pFirst; p; p=p->pNext){
422 char *zDate;
423 Manifest *pPost;
424 int isPrivate; /* True for posts awaiting moderation */
425 int sameUser; /* True if author is also the reader */
426 const char *zUuid;
427 char *zDisplayName; /* The display name */
428
429 pPost = manifest_get(p->fpid, CFTYPE_FORUM, 0);
430 if( pPost==0 ) continue;
431 if( p->fpid==target ){
432 @ <div id="forum%d(p->fpid)" class="forumTime forumSel">
433 }else if( p->pEditTail!=0 ){
434 @ <div id="forum%d(p->fpid)" class="forumTime forumObs">
435 }else{
436 @ <div id="forum%d(p->fpid)" class="forumTime">
437 }
438 if( pPost->zThreadTitle ){
439 @ <h1>%h(pPost->zThreadTitle)</h1>
440 }
441 zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate);
442 zDisplayName = display_name_from_login(pPost->zUser);
443 @ <h3 class='forumPostHdr'>(%d(p->sid)\
444 if( p->nEdit ){
445 @ .%.*d(fossil_num_digits(p->nEdit))(p->rev)\
446 }
447 @ ) By %h(zDisplayName) on %h(zDate)
448 fossil_free(zDisplayName);
449 fossil_free(zDate);
450 if( p->pEditPrev ){
451 @ edit of %z(href("%R/forumpost/%S?t=%c",p->pEditPrev->zUuid,cMode))\
452 @ %d(p->pEditPrev->sid)\
453 @ .%.*d(fossil_num_digits(p->nEdit))(p->pEditPrev->rev)</a>
454 }
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 if( p->pIrt ){
460 @ in reply to %z(href("%R/forumpost/%S?t=%c",p->pIrt->zUuid,cMode))\
461 @ %d(p->pIrt->sid)\
462 if( p->pIrt->nEdit ){
463 @ .%.*d(fossil_num_digits(p->pIrt->nEdit))(p->pIrt->rev)\
464 }
465 @ </a>
466 }
467 zUuid = p->zUuid;
468 if( p->pEditTail ){
469 @ updated by %z(href("%R/forumpost/%S?t=%c",p->pEditTail->zUuid,cMode))\
470 @ %d(p->pEditTail->sid)\
471 @ .%.*d(fossil_num_digits(p->nEdit))(p->pEditTail->rev)</a>
472 zUuid = p->pEditTail->zUuid;
473 }
474 if( p->fpid!=target ){
475 @ %z(href("%R/forumpost/%S?t=%c",zUuid,cMode))[link]</a>
476 }
477 if( !bRawMode ){
478 @ %z(href("%R/forumpost/%S?raw",zUuid))[source]</a>
479 }
480 isPrivate = content_is_private(p->fpid);
481 sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0;
482 @ </h3>
483 if( isPrivate && !g.perm.ModForum && !sameUser ){
484 @ <p><span class="modpending">Awaiting Moderator Approval</span></p>
485 }else{
486 const char *zMimetype;
487 if( bRawMode ){
488 zMimetype = "text/plain";
489 }else if( p->pEditTail!=0 ){
490 zMimetype = "text/plain";
491 }else{
492 zMimetype = pPost->zMimetype;
493 }
494 forum_render(0, zMimetype, pPost->zWiki, 0, 1);
495 }
496 if( g.perm.WrForum && p->pEditTail==0 ){
497 int sameUser = login_is_individual()
498 && fossil_strcmp(pPost->zUser, g.zLogin)==0;
499 @ <div><form action="%R/forumedit" method="POST">
500 @ <input type="hidden" name="fpid" value="%s(p->zUuid)">
501 if( !isPrivate ){
502 /* Reply and Edit are only available if the post has already
503 ** been approved */
504 @ <input type="submit" name="reply" value="Reply">
505 if( g.perm.Admin || sameUser ){
506 @ <input type="submit" name="edit" value="Edit">
507 @ <input type="submit" name="nullout" value="Delete">
508 }
509 }else if( g.perm.ModForum ){
510 /* Provide moderators with moderation buttons for posts that
511 ** are pending moderation */
512 @ <input type="submit" name="approve" value="Approve">
513 @ <input type="submit" name="reject" value="Reject">
514 generateTrustControls(pPost);
515 }else if( sameUser ){
516 /* A post that is pending moderation can be deleted by the
517 ** person who originally submitted the post */
518 @ <input type="submit" name="reject" value="Delete">
519 }
520 @ </form></div>
521 }
522 manifest_destroy(pPost);
523 @ </div>
524 }
525
526 /* Undocumented "threadtable" query parameter causes thread table
527 ** to be displayed for debugging purposes.
528 */
529 if( PB("threadtable") ){
530 @ <hr>
531 @ <table border="1" cellpadding="3" cellspacing="0">
532 @ <tr><th>sid<th>rev<th>fpid<th>pIrt<th>pEditHead<th>pEditTail\
533 @ <th>pEditNext<th>pEditPrev<th>hash
534 for(p=pThread->pFirst; p; p=p->pNext){
535 @ <tr><td>%d(p->sid)<td>%d(p->rev)<td>%d(p->fpid)\
536 @ <td>%d(p->pIrt?p->pIrt->fpid:0)\
537 @ <td>%d(p->pEditHead?p->pEditHead->fpid:0)\
538 @ <td>%d(p->pEditTail?p->pEditTail->fpid:0)\
539 @ <td>%d(p->pEditNext?p->pEditNext->fpid:0)\
540 @ <td>%d(p->pEditPrev?p->pEditPrev->fpid:0)\
541 @ <td>%S(p->zUuid)</tr>
542 }
543 @ </table>
544 }
545
546 forumthread_delete(pThread);
547 }
548 /*
549 ** Display all the edit history of post "target".
550 */
551 static void forum_display_history(int froot, int target, int bRawMode){
552 ForumThread *pThread = forumthread_create(froot, 0);
553 ForumPost *p;
554 int notAnon = login_is_individual();
555 char cMode = bRawMode ? 'r' : 'c';
556 ForumPost *pEditTail = 0;
557 int cnt = 0;
558 for(p=pThread->pFirst; p; p=p->pNext){
559 if( p->fpid==target ){
560 pEditTail = p->pEditTail ? p->pEditTail : p;
561 break;
562 }
563 }
564 for(p=pThread->pFirst; p; p=p->pNext){
565 char *zDate;
566 Manifest *pPost;
567 int isPrivate; /* True for posts awaiting moderation */
568 int sameUser; /* True if author is also the reader */
569 const char *zUuid;
570 char *zDisplayName; /* The display name */
571
572 if( p->fpid!=pEditTail->fpid && p->pEditTail!=pEditTail ) continue;
573 cnt++;
574 pPost = manifest_get(p->fpid, CFTYPE_FORUM, 0);
575 if( pPost==0 ) continue;
576 @ <div id="forum%d(p->fpid)" class="forumTime">
577 zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate);
578 zDisplayName = display_name_from_login(pPost->zUser);
579 @ <h3 class='forumPostHdr'>(%d(p->sid)\
580 if( p->nEdit ){
581 @ .%.*d(fossil_num_digits(p->nEdit))(p->rev)\
582 }
583 @ ) By %h(zDisplayName) on %h(zDate)
584 fossil_free(zDisplayName);
585 fossil_free(zDate);
 
 
 
 
 
 
 
 
 
 
 
586 if( g.perm.Debug ){
587 @ <span class="debug">\
588 @ <a href="%R/artifact/%h(p->zUuid)">(artifact-%d(p->fpid))</a></span>
589 }
590 if( p->pIrt && cnt==1 ){
591 @ in reply to %z(href("%R/forumpost/%S?t=%c",p->pIrt->zUuid,cMode))\
 
 
592 @ %d(p->pIrt->sid)\
593 if( p->pIrt->nEdit ){
594 @ .%.*d(fossil_num_digits(p->pIrt->nEdit))(p->pIrt->rev)\
595 }
596 @ </a>
597 }
598 zUuid = p->zUuid;
599 @ %z(href("%R/forumpost/%S?t=c",zUuid))[link]</a>
600 if( !bRawMode ){
601 @ %z(href("%R/forumpost/%S?raw",zUuid))[source]</a>
602 }
603 isPrivate = content_is_private(p->fpid);
604 sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0;
605 @ </h3>
606 if( isPrivate && !g.perm.ModForum && !sameUser ){
607 @ <p><span class="modpending">Awaiting Moderator Approval</span></p>
608 }else{
609 forum_render(0, bRawMode?"text/plain":pPost->zMimetype, pPost->zWiki,
610 0, 1);
611 }
612 if( g.perm.WrForum && p->pEditTail==0 ){
613 int sameUser = login_is_individual()
614 && fossil_strcmp(pPost->zUser, g.zLogin)==0;
615 @ <div><form action="%R/forumedit" method="POST">
616 @ <input type="hidden" name="fpid" value="%s(p->zUuid)">
617 if( !isPrivate ){
618 /* Reply and Edit are only available if the post has already
619 ** been approved */
620 @ <input type="submit" name="reply" value="Reply">
621 if( g.perm.Admin || sameUser ){
622 @ <input type="submit" name="edit" value="Edit">
623 @ <input type="submit" name="nullout" value="Delete">
624 }
625 }else if( g.perm.ModForum ){
626 /* Provide moderators with moderation buttons for posts that
627 ** are pending moderation */
628 @ <input type="submit" name="approve" value="Approve">
629 @ <input type="submit" name="reject" value="Reject">
630 generateTrustControls(pPost);
631 }else if( sameUser ){
632 /* A post that is pending moderation can be deleted by the
633 ** person who originally submitted the post */
634 @ <input type="submit" name="reject" value="Delete">
635 }
636 @ </form></div>
637 }
638 manifest_destroy(pPost);
639 @ </div>
640 }
641 forumthread_delete(pThread);
642 }
643
644 /*
645 ** Display all messages in a forumthread with indentation.
646 */
647 static int forum_display_hierarchical(int froot, int target){
648 ForumThread *pThread;
649 ForumPost *p;
650 Manifest *pPost, *pOPost;
651 int fpid;
652 const char *zUuid;
653 char *zDate;
654 const char *zSel;
655 int notAnon = login_is_individual();
656 int iIndentScale = 4;
657
658 pThread = forumthread_create(froot, 1);
659 for(p=pThread->pFirst; p; p=p->pNext){
660 if( p->fpid==target ){
661 if( p->pEditHead ) p = p->pEditHead;
662 target = p->fpid;
663 break;
664 }
665 }
666 while( iIndentScale>1 && iIndentScale*pThread->mxIndent>25 ){
667 iIndentScale--;
668 }
669 for(p=pThread->pDisplay; p; p=p->pDisplay){
670 int isPrivate; /* True for posts awaiting moderation */
671 int sameUser; /* True if reader is also the poster */
672 char *zDisplayName; /* User name to be displayed */
673 pOPost = manifest_get(p->fpid, CFTYPE_FORUM, 0);
674 if( p->pEditTail ){
675 fpid = p->pEditTail->fpid;
676 zUuid = p->pEditTail->zUuid;
677 pPost = manifest_get(fpid, CFTYPE_FORUM, 0);
678 }else{
679 fpid = p->fpid;
680 zUuid = p->zUuid;
681 pPost = pOPost;
682 }
683 zSel = p->fpid==target ? " forumSel" : "";
684 if( p->nIndent==1 ){
685 @ <div id='forum%d(fpid)' class='forumHierRoot%s(zSel)'>
686 }else{
687 @ <div id='forum%d(fpid)' class='forumHier%s(zSel)' \
688 @ style='margin-left: %d((p->nIndent-1)*iIndentScale)ex;'>
689 }
690 if( pPost==0 ) continue;
691 if( pPost->zThreadTitle ){
692 @ <h1>%h(pPost->zThreadTitle)</h1>
693 }
694 zDate = db_text(0, "SELECT datetime(%.17g)", pOPost->rDate);
695 zDisplayName = display_name_from_login(pOPost->zUser);
696 @ <h3 class='forumPostHdr'>\
697 @ (%d(p->sid)) By %h(zDisplayName) on %h(zDate)
698 fossil_free(zDisplayName);
699 fossil_free(zDate);
700 if( g.perm.Debug ){
701 @ <span class="debug">\
702 @ <a href="%R/artifact/%h(p->zUuid)">(artifact-%d(p->fpid))</a></span>
703 }
704 if( p->pEditTail ){
705 zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate);
706 if( fossil_strcmp(pOPost->zUser,pPost->zUser)==0 ){
707 @ and edited on %h(zDate)
708 }else{
709 @ as edited by %h(pPost->zUser) on %h(zDate)
710 }
711 fossil_free(zDate);
712 if( g.perm.Debug ){
713 @ <span class="debug">\
714 @ <a href="%R/artifact/%h(p->pEditTail->zUuid)">\
715 @ (artifact-%d(p->pEditTail->fpid))</a></span>
716 }
717 @ %z(href("%R/forumpost/%S?t=y",p->zUuid))[history]</a>
718 manifest_destroy(pOPost);
719 }
720 if( fpid!=target ){
721 @ %z(href("%R/forumpost/%S",zUuid))[link]</a>
722 }
723 @ %z(href("%R/forumpost/%S?raw",zUuid))[source]</a>
724 if( p->pIrt ){
725 @ in reply to %z(href("%R/forumpost/%S?t=h",p->pIrt->zUuid))\
726 @ %d(p->pIrt->sid)</a>
727 }
728 @ </h3>
729 isPrivate = content_is_private(fpid);
730 sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0;
731 if( isPrivate && !g.perm.ModForum && !sameUser ){
732 @ <p><span class="modpending">Awaiting Moderator Approval</span></p>
733 }else{
734 forum_render(0, pPost->zMimetype, pPost->zWiki, 0, 1);
735 }
736 if( g.perm.WrForum ){
737 @ <div><form action="%R/forumedit" method="POST">
738 @ <input type="hidden" name="fpid" value="%s(zUuid)">
739 if( !isPrivate ){
740 /* Reply and Edit are only available if the post has already
741 ** been approved */
742 @ <input type="submit" name="reply" value="Reply">
743 if( g.perm.Admin || sameUser ){
744 @ <input type="submit" name="edit" value="Edit">
745 @ <input type="submit" name="nullout" value="Delete">
746 }
747 }else if( g.perm.ModForum ){
748 /* Provide moderators with moderation buttons for posts that
749 ** are pending moderation */
750 @ <input type="submit" name="approve" value="Approve">
751 @ <input type="submit" name="reject" value="Reject">
752 generateTrustControls(pPost);
753 }else if( sameUser ){
754 /* A post that is pending moderation can be deleted by the
755 ** person who originally submitted the post */
756 @ <input type="submit" name="reject" value="Delete">
757 }
758 @ </form></div>
759 }
760 manifest_destroy(pPost);
761 @ </div>
762 }
763 forumthread_delete(pThread);
764 return target;
765 }
766
767 /*
768 ** Emits all JS code required by /forumpost.
769 */
770 static void forumpost_emit_page_js(){
771 static int once = 0;
772 if(0==once){
773 once = 1;
774 style_emit_script_fossil_bootstrap(1);
775 builtin_request_js("forum.js");
776 builtin_request_js("fossil.dom.js");
777 builtin_request_js("fossil.page.forumpost.js");
778 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
779 }
780
781 /*
782 ** WEBPAGE: forumpost
783 **
784 ** Show a single forum posting. The posting is shown in context with
785 ** it's entire thread. The selected posting is enclosed within
786 ** <div class='forumSel'>...</div>. Javascript is used to move the
787 ** selected posting into view after the page loads.
788 **
789 ** Query parameters:
790 **
791 ** name=X REQUIRED. The hash of the post to display
792 ** t=MODE Display mode.
793 ** 'c' for chronological
794 ** 'h' for hierarchical
795 ** 'a' for automatic
796 ** 'r' for raw
797 ** 'y' for history of post X only
798 ** raw If present, show only the post specified and
799 ** show its original unformatted source text.
 
 
 
 
800 */
801 void forumpost_page(void){
802 forumthread_page();
803 }
804
805 /*
806 ** Add an appropriate style_header() to include title of the
807 ** given forum post.
808 */
809 static int forumthread_page_header(int froot, int fpid){
810 char *zThreadTitle = 0;
811
812 zThreadTitle = db_text("",
813 "SELECT"
814 " substr(event.comment,instr(event.comment,':')+2)"
815 " FROM forumpost, event"
816 " WHERE event.objid=forumpost.fpid"
817 " AND forumpost.fpid=%d;",
818 fpid
819 );
820 style_header("%s%s", zThreadTitle, zThreadTitle[0] ? "" : "Forum");
821 fossil_free(zThreadTitle);
822 return 0;
823 }
824
825 /*
826 ** WEBPAGE: forumthread
827 **
828 ** Show all forum messages associated with a particular message thread.
829 ** The result is basically the same as /forumpost except that none of
@@ -830,24 +713,28 @@
830 ** the postings in the thread are selected.
831 **
832 ** Query parameters:
833 **
834 ** name=X REQUIRED. The hash of any post of the thread.
835 ** t=MODE Display mode. MODE is...
836 ** 'c' for chronological, or
837 ** 'h' for hierarchical, or
838 ** 'a' for automatic, or
839 ** 'r' for raw.
840 ** raw Show only the post given by name= and show it unformatted
841 ** hist Show only the edit history for the name= post
842 */
843 void forumthread_page(void){
844 int fpid;
845 int froot;
 
846 const char *zName = P("name");
847 const char *zMode = PD("t","a");
848 int bRaw = PB("raw");
 
 
 
849 login_check_credentials();
850 if( !g.perm.RdForum ){
851 login_needed(g.anon.RdForum);
852 return;
853 }
@@ -861,54 +748,70 @@
861 froot = db_int(0, "SELECT froot FROM forumpost WHERE fpid=%d", fpid);
862 if( froot==0 ){
863 webpage_error("Not a forum post: \"%s\"", zName);
864 }
865 if( fossil_strcmp(g.zPath,"forumthread")==0 ) fpid = 0;
866 if( zMode[0]=='a' ){
867 if( cgi_from_mobile() ){
868 zMode = "c"; /* Default to chronological on mobile */
869 }else{
870 zMode = "h";
871 }
872 }
873 if( zMode[0]!='y' ){
874 forumthread_page_header(froot, fpid);
875 }
876 if( bRaw && fpid ){
877 Manifest *pPost;
878 pPost = manifest_get(fpid, CFTYPE_FORUM, 0);
879 if( pPost==0 ){
880 @ <p>No such forum post: %h(zName)
881 }else{
882 int isPrivate = content_is_private(fpid);
883 int notAnon = login_is_individual();
884 int sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0;
885 if( isPrivate && !g.perm.ModForum && !sameUser ){
886 @ <p><span class="modpending">Awaiting Moderator Approval</span></p>
887 }else{
888 forum_render(0, "text/plain", pPost->zWiki, 0, 0);
889 }
890 manifest_destroy(pPost);
891 }
892 }else if( zMode[0]=='c' ){
893 style_submenu_element("Hierarchical", "%R/%s/%s?t=h", g.zPath, zName);
894 style_submenu_element("Unformatted", "%R/%s/%s?t=r", g.zPath, zName);
895 forum_display_chronological(froot, fpid, 0);
896 }else if( zMode[0]=='r' ){
897 style_submenu_element("Chronological", "%R/%s/%s?t=c", g.zPath, zName);
898 style_submenu_element("Hierarchical", "%R/%s/%s?t=h", g.zPath, zName);
899 forum_display_chronological(froot, fpid, 1);
900 }else if( zMode[0]=='y' ){
901 style_header("Edit History Of A Forum Post");
902 style_submenu_element("Complete Thread", "%R/%s/%s?t=a", g.zPath, zName);
903 forum_display_history(froot, fpid, 1);
904 }else{
905 style_submenu_element("Chronological", "%R/%s/%s?t=c", g.zPath, zName);
906 style_submenu_element("Unformatted", "%R/%s/%s?t=r", g.zPath, zName);
907 forum_display_hierarchical(froot, fpid);
908 }
909 forumpost_emit_page_js();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
910 style_footer();
911 }
912
913 /*
914 ** Return true if a forum post should be moderated.
915
--- src/forum.c
+++ src/forum.c
@@ -89,19 +89,17 @@
89 fossil_free(pPost);
90 }
91 fossil_free(pThread);
92 }
93
 
94 /*
95 ** Search a ForumPost list forwards looking for the post with fpid
96 */
97 static ForumPost *forumpost_forward(ForumPost *p, int fpid){
98 while( p && p->fpid!=fpid ) p = p->pNext;
99 return p;
100 }
 
101
102 /*
103 ** Search backwards for a ForumPost
104 */
105 static ForumPost *forumpost_backward(ForumPost *p, int fpid){
@@ -358,28 +356,10 @@
356 if( zClass ){
357 @ </div>
358 }
359 }
360
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
361 /*
362 ** Compute a display name from a login name.
363 **
364 ** If the input login is found in the USER table, then check the USER.INFO
365 ** field to see if it has display-name followed by an email address.
@@ -409,421 +389,324 @@
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(
397 ForumPost *p, /* Forum post to display */
398 int iIndentScale, /* Indent scale factor */
399 int bRaw, /* True to omit the border */
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 *pManifest; /* Manifest comprising the current post */
409 int bPrivate; /* True for posts awaiting moderation */
410 int bSameUser; /* True if author is also the reader */
411 int iIndent; /* Indent level */
412 const char *zMimetype;/* Formatting MIME type */
413
414 /* Get the manifest for the post. Abort if not found (e.g. shunned). */
415 pManifest = manifest_get(p->fpid, CFTYPE_FORUM, 0);
416 if( !pManifest ) return;
417
418 /* When not in raw mode, create the border around the post. */
419 if( !bRaw ){
420 /* Open the <div> enclosing the post. Set the class string to mark the post
421 ** as selected and/or obsolete. */
422 iIndent = (p->pEditHead ? p->pEditHead->nIndent : p->nIndent)-1;
423 @ <div id='forum%d(p->fpid)' class='forumTime\
424 @ %s(bSelect ? " forumSel" : "")\
425 @ %s(p->pEditTail ? " forumObs" : "")'\
426 if( iIndent && iIndentScale ){
427 @ style='margin-left: %d(iIndent*iIndentScale)ex'
428 }
429 @ >
430
431 /* If this is the first post (or an edit thereof), emit the thread title. */
432 if( pManifest->zThreadTitle ){
433 @ <h1>%h(pManifest->zThreadTitle)</h1>
434 }
435
436 /* Emit the serial number, revision number, author, and date. */
437 zDisplayName = display_name_from_login(pManifest->zUser);
438 zDate = db_text(0, "SELECT datetime(%.17g)", pManifest->rDate);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
439 @ <h3 class='forumPostHdr'>(%d(p->sid)\
440 if( p->nEdit ){
441 @ .%.*d(fossil_num_digits(p->nEdit))(p->rev)\
442 }
443 @ ) By %h(zDisplayName) on %h(zDate)
444 fossil_free(zDisplayName);
445 fossil_free(zDate);
446
447 /* If this is an edit, refer back to the old version. Be sure "hist" is in
448 ** the query string so the old version will actually be shown. */
449 if( p->pEditPrev ){
450 zHist = bHist ? "" : "&hist";
451 @ edit of \
452 @ %z(href("%R/forumpost/%S?%s%s",p->pEditPrev->zUuid,zQuery,zHist))\
453 @ %d(p->sid).%.*d(fossil_num_digits(p->nEdit))(p->pEditPrev->rev)</a>
454 }
455
456 /* If debugging is enabled, link to the artifact page. */
457 if( g.perm.Debug ){
458 @ <span class="debug">\
459 @ <a href="%R/artifact/%h(p->zUuid)">(artifact-%d(p->fpid))</a></span>
460 }
461
462 /* If this is a reply, refer back to the parent post. */
463 if( p->pIrt ){
464 @ in reply to %z(href("%R/forumpost/%S?%s",p->pIrt->zUuid,zQuery))\
465 @ %d(p->pIrt->sid)\
466 if( p->pIrt->nEdit ){
467 @ .%.*d(fossil_num_digits(p->pIrt->nEdit))(p->pIrt->rev)\
468 }
469 @ </a>
470 }
471
472 /* If this post was later edited, refer forward to the new version. */
473 if( p->pEditTail ){
474 @ updated by %z(href("%R/forumpost/%S?%s",p->pEditTail->zUuid,zQuery))\
475 @ %d(p->pEditTail->sid)\
476 @ .%.*d(fossil_num_digits(p->nEdit))(p->pEditTail->rev)</a>
477 }
478
479 /* Provide a link to select the individual post. */
480 if( !bSelect ){
481 @ %z(href("%R/forumpost/%S?%s",p->zUuid,zQuery))[link]</a>
482 }
483
484 /* Provide a link to the raw source code. */
485 if( !bUnf ){
486 @ %z(href("%R/forumpost/%S?raw",p->zUuid))[source]</a>
487 }
488 @ </h3>
489 }
490
491 /* Check if this post is approved, also if it's by the current user. */
492 bPrivate = content_is_private(p->fpid);
493 bSameUser = login_is_individual()
494 && fossil_strcmp(pManifest->zUser, g.zLogin)==0;
495
496 /* Render the post if the user is able to see it. */
497 if( bPrivate && !g.perm.ModForum && !bSameUser ){
498 @ <p><span class="modpending">Awaiting Moderator Approval</span></p>
499 }else{
500 if( bRaw || bUnf || p->pEditTail ){
501 zMimetype = "text/plain";
502 }else{
503 zMimetype = pManifest->zMimetype;
504 }
505 forum_render(0, zMimetype, pManifest->zWiki, 0, !bRaw);
506 }
507
508 /* When not in raw mode, finish creating the border around the post. */
509 if( !bRaw ){
510 /* If the user is able to write to the forum and if this post has not been
511 ** edited, create a form with various interaction buttons. */
512 if( g.perm.WrForum && !p->pEditTail ){
513 @ <div><form action="%R/forumedit" method="POST">
514 @ <input type="hidden" name="fpid" value="%s(p->zUuid)">
515 if( !bPrivate ){
516 /* Reply and Edit are only available if the post has been approved. */
517 @ <input type="submit" name="reply" value="Reply">
518 if( g.perm.Admin || bSameUser ){
519 @ <input type="submit" name="edit" value="Edit">
520 @ <input type="submit" name="nullout" value="Delete">
521 }
522 }else if( g.perm.ModForum ){
523 /* Allow moderators to approve or reject pending posts. Also allow
524 ** forum supervisors to mark non-special users as trusted and therefore
525 ** able to post unmoderated. */
526 @ <input type="submit" name="approve" value="Approve">
527 @ <input type="submit" name="reject" value="Reject">
528 if( g.perm.AdminForum && !login_is_special(pManifest->zUser) ){
529 @ <br><label><input type="checkbox" name="trust">
530 @ Trust user "%h(pManifest->zUser)" so that future posts by \
531 @ "%h(pManifest->zUser)" do not require moderation.
532 @ </label>
533 @ <input type="hidden" name="trustuser" value="%h(pManifest->zUser)">
534 }
535 }else if( bSameUser ){
536 /* Allow users to delete (reject) their own pending posts. */
537 @ <input type="submit" name="reject" value="Delete">
538 }
539 @ </form></div>
540 }
541 @ </div>
542 }
543
544 /* Clean up. */
545 manifest_destroy(pManifest);
546 }
547
548 /*
549 ** Possible display modes for forum_display_thread().
550 */
551 enum {
552 FD_RAW, /* Like FD_SINGLE, but additionally omit the border, force
553 ** unformatted mode, and inhibit history mode */
554 FD_SINGLE, /* Render a single post and (optionally) its edit history */
555 FD_CHRONO, /* Render all posts in chronological order */
556 FD_HIER, /* Render all posts in an indented hierarchy */
557 };
558
559 /*
560 ** Display a forum thread. If mode is FD_RAW or FD_SINGLE, display only a
561 ** single post from the thread and (optionally) its edit history.
562 */
563 static void forum_display_thread(
564 int froot, /* Forum thread root post ID */
565 int fpid, /* Selected forum post ID, or 0 if none selected */
566 int mode, /* Forum display mode, one of the FD_* enumerations */
567 int bUnf, /* True if rendering unformatted */
568 int bHist /* True if showing edit history, ignored for FD_RAW */
569 ){
570 ForumThread *pThread; /* Thread structure */
571 ForumPost *pSelect; /* Currently selected post, or NULL if none */
572 ForumPost *p; /* Post iterator pointer */
573 char *zQuery; /* Common query string */
574 int iIndentScale = 4; /* Indent scale factor, measured in "ex" units */
575
576 /* In raw mode, force unformatted display and disable history. */
577 if( mode == FD_RAW ){
578 bUnf = 1;
579 bHist = 0;
580 }
581
582 /* Thread together the posts and (optionally) compute the hierarchy. */
583 pThread = forumthread_create(froot, mode==FD_HIER);
584
585 /* Compute the appropriate indent scaling. */
586 if( mode==FD_HIER ){
587 iIndentScale = 4;
588 while( iIndentScale>1 && iIndentScale*pThread->mxIndent>25 ){
589 iIndentScale--;
590 }
591 }else{
592 iIndentScale = 0;
593 }
594
595 /* Find the selected post, or (if history not shown) its latest edit. */
596 pSelect = fpid ? forumpost_forward(pThread->pFirst, fpid) : 0;
597 if( !bHist && pSelect && pSelect->pEditTail ){
598 pSelect = pSelect->pEditTail;
599 }
600
601 /* When displaying only a single post, abort if no post was selected or the
602 ** selected forum post does not exist in the thread. Otherwise proceed to
603 ** display the entire thread without marking any posts as selected. */
604 if( !pSelect && (mode==FD_RAW || mode==FD_SINGLE) ){
605 return;
606 }
607
608 /* Create the common query string to append to nearly all post links. */
609 zQuery = mode==FD_RAW ? 0 : mprintf("t=%c%s%s",
610 mode==FD_SINGLE ? 's' : mode==FD_CHRONO ? 'c' : 'h',
611 bUnf ? "&unf" : "", bHist ? "&hist" : "");
612
613 /* Identify which post to display first. If history is shown, start with the
614 ** original, unedited post. Otherwise advance to the post's latest edit. */
615 if( mode==FD_RAW || mode==FD_SINGLE ){
616 p = pSelect;
617 if( bHist && p->pEditHead ) p = p->pEditHead;
618 }else{
619 p = mode==FD_CHRONO ? pThread->pFirst : pThread->pDisplay;
620 if( !bHist && p->pEditTail ) p = p->pEditTail;
621 }
622
623 /* Display the appropriate subset of posts in sequence. */
624 while( p ){
625 /* Display the post. */
626 forum_display_post(p, iIndentScale, mode==FD_RAW,
627 bUnf, bHist, p==pSelect, zQuery);
628
629 /* Advance to the next post in the thread. */
630 if( mode==FD_CHRONO ){
631 /* Chronological mode: display posts (optionally including edits) in their
632 ** original commit order. */
633 if( bHist ){
634 p = p->pNext;
635 }else{
636 if( p->pEditHead ) p = p->pEditHead;
637 do p = p->pNext; while( p && p->sid<=p->pPrev->sid );
638 if( p && p->pEditTail ) p = p->pEditTail;
639 }
640 }else if( bHist && p->pEditNext ){
641 /* Hierarchical and single mode: display each post's edits in sequence. */
642 p = p->pEditNext;
643 }else if( mode==FD_HIER ){
644 /* Hierarchical mode: after displaying with each post (optionally
645 ** including edits), go to the next post in computed display order. */
646 p = p->pEditHead ? p->pEditHead->pDisplay : p->pDisplay;
647 if( !bHist && p && p->pEditTail ) p = p->pEditTail;
648 }else{
649 /* Single and raw mode: terminate after displaying the selected post and
650 ** (optionally) its edits. */
651 break;
652 }
653 }
654
655 /* Undocumented "threadtable" query parameter causes thread table to be
656 ** displayed for debugging purposes. */
657 if( PB("threadtable") ){
658 @ <hr>
659 @ <table border="1" cellpadding="3" cellspacing="0">
660 @ <tr><th>sid<th>rev<th>fpid<th>pIrt<th>pEditHead<th>pEditTail\
661 @ <th>pEditNext<th>pEditPrev<th>pDisplay<th>hash
662 for(p=pThread->pFirst; p; p=p->pNext){
663 @ <tr><td>%d(p->sid)<td>%d(p->rev)<td>%d(p->fpid)\
664 @ <td>%d(p->pIrt ? p->pIrt->fpid : 0)\
665 @ <td>%d(p->pEditHead ? p->pEditHead->fpid : 0)\
666 @ <td>%d(p->pEditTail ? p->pEditTail->fpid : 0)\
667 @ <td>%d(p->pEditNext ? p->pEditNext->fpid : 0)\
668 @ <td>%d(p->pEditPrev ? p->pEditPrev->fpid : 0)\
669 @ <td>%d(p->pDisplay ? p->pDisplay->fpid : 0)\
670 @ <td>%S(p->zUuid)</tr>
671 }
672 @ </table>
673 }
674
675 /* Clean up. */
676 forumthread_delete(pThread);
677 fossil_free(zQuery);
678 }
679
680 /*
681 ** WEBPAGE: forumpost
682 **
683 ** Show a single forum posting. The posting is shown in context with
684 ** its entire thread. The selected posting is enclosed within
685 ** <div class='forumSel'>...</div>. Javascript is used to move the
686 ** selected posting into view after the page loads.
687 **
688 ** Query parameters:
689 **
690 ** name=X REQUIRED. The hash of the post to display.
691 ** t=a Automatic display mode, i.e. hierarchical for
692 ** desktop and chronological for mobile. This is the
693 ** default if the "t" query parameter is omitted.
694 ** t=c Show posts in the order they were written.
695 ** t=h Show posts usin hierarchical indenting.
696 ** t=s Show only the post specified by "name=X".
697 ** t=r Alias for "t=c&unf&hist".
698 ** t=y Alias for "t=s&unf&hist".
699 ** raw Alias for "t=s&unf". Additionally, omit the border
700 ** around the post, and ignore "t" and "hist".
701 ** unf Show the original, unformatted source text.
702 ** hist Show edit history in addition to current posts.
703 */
704 void forumpost_page(void){
705 forumthread_page();
706 }
707
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
708 /*
709 ** WEBPAGE: forumthread
710 **
711 ** Show all forum messages associated with a particular message thread.
712 ** The result is basically the same as /forumpost except that none of
@@ -830,24 +713,28 @@
713 ** the postings in the thread are selected.
714 **
715 ** Query parameters:
716 **
717 ** name=X REQUIRED. The hash of any post of the thread.
718 ** t=a Automatic display mode, i.e. hierarchical for
719 ** desktop and chronological for mobile. This is the
720 ** default if the "t" query parameter is omitted.
721 ** t=c Show posts in the order they were written.
722 ** t=h Show posts using hierarchical indenting.
723 ** unf Show the original, unformatted source text.
724 ** hist Show edit history in addition to current posts.
725 */
726 void forumthread_page(void){
727 int fpid;
728 int froot;
729 char *zThreadTitle;
730 const char *zName = P("name");
731 const char *zMode = PD("t","a");
732 int bRaw = PB("raw");
733 int bUnf = PB("unf");
734 int bHist = PB("hist");
735 int mode;
736 login_check_credentials();
737 if( !g.perm.RdForum ){
738 login_needed(g.anon.RdForum);
739 return;
740 }
@@ -861,54 +748,70 @@
748 froot = db_int(0, "SELECT froot FROM forumpost WHERE fpid=%d", fpid);
749 if( froot==0 ){
750 webpage_error("Not a forum post: \"%s\"", zName);
751 }
752 if( fossil_strcmp(g.zPath,"forumthread")==0 ) fpid = 0;
753
754 /* Decode the mode parameters. */
755 if( bRaw ){
756 mode = FD_RAW;
757 bUnf = 1;
758 bHist = 0;
759 cgi_replace_query_parameter("unf", "on");
760 cgi_delete_query_parameter("hist");
761 cgi_delete_query_parameter("raw");
762 }else{
763 switch( *zMode ){
764 case 'a': mode = cgi_from_mobile() ? FD_CHRONO : FD_HIER; break;
765 case 'c': mode = FD_CHRONO; break;
766 case 'h': mode = FD_HIER; break;
767 case 's': mode = FD_SINGLE; break;
768 case 'r': mode = FD_CHRONO; break;
769 case 'y': mode = FD_SINGLE; break;
770 default: webpage_error("Invalid thread mode: \"%s\"", zMode);
771 }
772 if( *zMode=='r' || *zMode=='y') {
773 bUnf = 1;
774 bHist = 1;
775 cgi_replace_query_parameter("t", mode==FD_CHRONO ? "c" : "s");
776 cgi_replace_query_parameter("unf", "on");
777 cgi_replace_query_parameter("hist", "on");
778 }
779 }
780
781 /* Define the page header. */
782 zThreadTitle = db_text("",
783 "SELECT"
784 " substr(event.comment,instr(event.comment,':')+2)"
785 " FROM forumpost, event"
786 " WHERE event.objid=forumpost.fpid"
787 " AND forumpost.fpid=%d;",
788 fpid
789 );
790 style_header("%s%s", zThreadTitle, *zThreadTitle ? "" : "Forum");
791 fossil_free(zThreadTitle);
792 if( mode!=FD_CHRONO ){
793 style_submenu_element("Chronological", "%R/%s/%s?t=c%s%s", g.zPath, zName,
794 bUnf ? "&unf" : "", bHist ? "&hist" : "");
795 }
796 if( mode!=FD_HIER ){
797 style_submenu_element("Hierarchical", "%R/%s/%s?t=h%s%s", g.zPath, zName,
798 bUnf ? "&unf" : "", bHist ? "&hist" : "");
799 }
800 style_submenu_checkbox("unf", "Unformatted", 0, 0);
801 style_submenu_checkbox("hist", "History", 0, 0);
802
803 /* Display the thread. */
804 forum_display_thread(froot, fpid, mode, bUnf, bHist);
805
806 /* Emit Forum Javascript. */
807 style_emit_script_fossil_bootstrap(1);
808 builtin_request_js("forum.js");
809 builtin_request_js("fossil.dom.js");
810 builtin_request_js("fossil.page.forumpost.js");
811
812 /* Emit the page style. */
813 style_footer();
814 }
815
816 /*
817 ** Return true if a forum post should be moderated.
818

Keyboard Shortcuts

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