Index: src/forum.c
==================================================================
--- src/forum.c
+++ src/forum.c
@@ -26,44 +26,45 @@
*/
#define DEFAULT_FORUM_MIMETYPE "text/x-markdown"
#if INTERFACE
/*
-** Each instance of the following object represents a single message -
+** Each instance of the following object represents a single message -
** either the initial post, an edit to a post, a reply, or an edit to
** a reply.
*/
-struct ForumEntry {
- int fpid; /* rid for this entry */
- int fprev; /* zero if initial entry. non-zero if an edit */
- int firt; /* This entry replies to firt */
- int mfirt; /* Root in-reply-to */
- int nReply; /* Number of replies to this entry */
+struct ForumPost {
+ int fpid; /* rid for this post */
int sid; /* Serial ID number */
+ int rev; /* Revision number */
char *zUuid; /* Artifact hash */
- ForumEntry *pLeaf; /* Most recent edit for this entry */
- ForumEntry *pEdit; /* This entry is an edit of pEdit */
- ForumEntry *pNext; /* Next in chronological order */
- ForumEntry *pPrev; /* Previous in chronological order */
- ForumEntry *pDisplay; /* Next in display order */
- int nIndent; /* Number of levels of indentation for this entry */
+ ForumPost *pIrt; /* This post replies to pIrt */
+ ForumPost *pEditHead; /* Original, unedited post */
+ ForumPost *pEditTail; /* Most recent edit for this post */
+ ForumPost *pEditNext; /* This post is edited by pEditNext */
+ ForumPost *pEditPrev; /* This post is an edit of pEditPrev */
+ ForumPost *pNext; /* Next in chronological order */
+ ForumPost *pPrev; /* Previous in chronological order */
+ ForumPost *pDisplay; /* Next in display order */
+ int nEdit; /* Number of edits to this post */
+ int nIndent; /* Number of levels of indentation for this post */
};
/*
** A single instance of the following tracks all entries for a thread.
*/
struct ForumThread {
- ForumEntry *pFirst; /* First entry in chronological order */
- ForumEntry *pLast; /* Last entry in chronological order */
- ForumEntry *pDisplay; /* Entries in display order */
- ForumEntry *pTail; /* Last on the display list */
+ ForumPost *pFirst; /* First post in chronological order */
+ ForumPost *pLast; /* Last post in chronological order */
+ ForumPost *pDisplay; /* Entries in display order */
+ ForumPost *pTail; /* Last on the display list */
int mxIndent; /* Maximum indentation level */
};
#endif /* INTERFACE */
/*
-** Return true if the forum entry with the given rid has been
+** Return true if the forum post with the given rid has been
** subsequently edited.
*/
int forum_rid_has_been_edited(int rid){
static Stmt q;
int res;
@@ -79,41 +80,39 @@
/*
** Delete a complete ForumThread and all its entries.
*/
static void forumthread_delete(ForumThread *pThread){
- ForumEntry *pEntry, *pNext;
- for(pEntry=pThread->pFirst; pEntry; pEntry = pNext){
- pNext = pEntry->pNext;
- fossil_free(pEntry->zUuid);
- fossil_free(pEntry);
+ ForumPost *pPost, *pNext;
+ for(pPost=pThread->pFirst; pPost; pPost = pNext){
+ pNext = pPost->pNext;
+ fossil_free(pPost->zUuid);
+ fossil_free(pPost);
}
fossil_free(pThread);
}
-#if 0 /* not used */
/*
-** Search a ForumEntry list forwards looking for the entry with fpid
+** Search a ForumPost list forwards looking for the post with fpid
*/
-static ForumEntry *forumentry_forward(ForumEntry *p, int fpid){
+static ForumPost *forumpost_forward(ForumPost *p, int fpid){
while( p && p->fpid!=fpid ) p = p->pNext;
return p;
}
-#endif
/*
-** Search backwards for a ForumEntry
+** Search backwards for a ForumPost
*/
-static ForumEntry *forumentry_backward(ForumEntry *p, int fpid){
+static ForumPost *forumpost_backward(ForumPost *p, int fpid){
while( p && p->fpid!=fpid ) p = p->pPrev;
return p;
}
/*
-** Add an entry to the display list
+** Add a post to the display list
*/
-static void forumentry_add_to_display(ForumThread *pThread, ForumEntry *p){
+static void forumpost_add_to_display(ForumThread *pThread, ForumPost *p){
if( pThread->pDisplay==0 ){
pThread->pDisplay = p;
}else{
pThread->pTail->pDisplay = p;
}
@@ -120,108 +119,112 @@
pThread->pTail = p;
}
/*
** Extend the display list for pThread by adding all entries that
-** reference fpid. The first such entry will be no earlier then
-** entry "p".
+** reference fpid. The first such post will be no earlier then
+** post "p".
*/
static void forumthread_display_order(
ForumThread *pThread, /* The complete thread */
- ForumEntry *pBase /* Add replies to this entry */
+ ForumPost *pBase /* Add replies to this post */
){
- ForumEntry *p;
- ForumEntry *pPrev = 0;
+ ForumPost *p;
+ ForumPost *pPrev = 0;
+ ForumPost *pBaseIrt;
for(p=pBase->pNext; p; p=p->pNext){
- if( p->fprev==0 && p->mfirt==pBase->fpid ){
- if( pPrev ){
- pPrev->nIndent = pBase->nIndent + 1;
- forumentry_add_to_display(pThread, pPrev);
- forumthread_display_order(pThread, pPrev);
- }
- pBase->nReply++;
- pPrev = p;
+ if( !p->pEditPrev && p->pIrt ){
+ pBaseIrt = p->pIrt->pEditHead ? p->pIrt->pEditHead : p->pIrt;
+ if( pBaseIrt==pBase ){
+ if( pPrev ){
+ pPrev->nIndent = pBase->nIndent + 1;
+ forumpost_add_to_display(pThread, pPrev);
+ forumthread_display_order(pThread, pPrev);
+ }
+ pPrev = p;
+ }
}
}
if( pPrev ){
pPrev->nIndent = pBase->nIndent + 1;
if( pPrev->nIndent>pThread->mxIndent ) pThread->mxIndent = pPrev->nIndent;
- forumentry_add_to_display(pThread, pPrev);
+ forumpost_add_to_display(pThread, pPrev);
forumthread_display_order(pThread, pPrev);
}
}
/*
** Construct a ForumThread object given the root record id.
*/
static ForumThread *forumthread_create(int froot, int computeHierarchy){
ForumThread *pThread;
- ForumEntry *pEntry;
+ ForumPost *pPost;
+ ForumPost *p;
Stmt q;
int sid = 1;
- Bag seen = Bag_INIT;
+ int firt, fprev;
pThread = fossil_malloc( sizeof(*pThread) );
memset(pThread, 0, sizeof(*pThread));
db_prepare(&q,
"SELECT fpid, firt, fprev, (SELECT uuid FROM blob WHERE rid=fpid)"
" FROM forumpost"
" WHERE froot=%d ORDER BY fmtime",
froot
);
while( db_step(&q)==SQLITE_ROW ){
- pEntry = fossil_malloc( sizeof(*pEntry) );
- memset(pEntry, 0, sizeof(*pEntry));
- pEntry->fpid = db_column_int(&q, 0);
- pEntry->firt = db_column_int(&q, 1);
- pEntry->fprev = db_column_int(&q, 2);
- pEntry->zUuid = fossil_strdup(db_column_text(&q,3));
- pEntry->mfirt = pEntry->firt;
- pEntry->sid = sid++;
- pEntry->pPrev = pThread->pLast;
- pEntry->pNext = 0;
- bag_insert(&seen, pEntry->fpid);
+ pPost = fossil_malloc( sizeof(*pPost) );
+ memset(pPost, 0, sizeof(*pPost));
+ pPost->fpid = db_column_int(&q, 0);
+ firt = db_column_int(&q, 1);
+ fprev = db_column_int(&q, 2);
+ pPost->zUuid = fossil_strdup(db_column_text(&q,3));
+ if( !fprev ) pPost->sid = sid++;
+ pPost->pPrev = pThread->pLast;
+ pPost->pNext = 0;
if( pThread->pLast==0 ){
- pThread->pFirst = pEntry;
- }else{
- pThread->pLast->pNext = pEntry;
- }
- if( pEntry->firt && !bag_find(&seen,pEntry->firt) ){
- pEntry->firt = froot;
- pEntry->mfirt = froot;
- }
- pThread->pLast = pEntry;
- }
- db_finalize(&q);
- bag_clear(&seen);
-
- /* Establish which entries are the latest edit. After this loop
- ** completes, entries that have non-NULL pLeaf should not be
- ** displayed.
- */
- for(pEntry=pThread->pFirst; pEntry; pEntry=pEntry->pNext){
- if( pEntry->fprev ){
- ForumEntry *pBase = 0, *p;
- p = forumentry_backward(pEntry->pPrev, pEntry->fprev);
- pEntry->pEdit = p;
- while( p ){
- pBase = p;
- p->pLeaf = pEntry;
- p = pBase->pEdit;
- }
- for(p=pEntry->pNext; p; p=p->pNext){
- if( p->mfirt==pEntry->fpid ) p->mfirt = pBase->fpid;
- }
- }
- }
+ pThread->pFirst = pPost;
+ }else{
+ pThread->pLast->pNext = pPost;
+ }
+ pThread->pLast = pPost;
+
+ /* Find the in-reply-to post. Default to the topic post if the replied-to
+ ** post cannot be found. */
+ if( firt ){
+ pPost->pIrt = pThread->pFirst;
+ for(p=pThread->pFirst; p; p=p->pNext){
+ if( p->fpid==firt ){
+ pPost->pIrt = p;
+ break;
+ }
+ }
+ }
+
+ /* Maintain the linked list of post edits. */
+ if( fprev ){
+ p = forumpost_backward(pPost->pPrev, fprev);
+ p->pEditNext = pPost;
+ pPost->sid = p->sid;
+ pPost->rev = p->rev+1;
+ pPost->nEdit = p->nEdit+1;
+ pPost->pEditPrev = p;
+ pPost->pEditHead = p->pEditHead ? p->pEditHead : p;
+ for(; p; p=p->pEditPrev ){
+ p->nEdit = pPost->nEdit;
+ p->pEditTail = pPost;
+ }
+ }
+ }
+ db_finalize(&q);
if( computeHierarchy ){
/* Compute the hierarchical display order */
- pEntry = pThread->pFirst;
- pEntry->nIndent = 1;
+ pPost = pThread->pFirst;
+ pPost->nIndent = 1;
pThread->mxIndent = 1;
- forumentry_add_to_display(pThread, pEntry);
- forumthread_display_order(pThread, pEntry);
+ forumpost_add_to_display(pThread, pPost);
+ forumthread_display_order(pThread, pPost);
}
/* Return the result */
return pThread;
}
@@ -265,11 +268,11 @@
void forumthread_cmd(void){
int fpid;
int froot;
const char *zName;
ForumThread *pThread;
- ForumEntry *p;
+ ForumPost *p;
db_find_and_open_repository(0,0);
verify_all_options();
if( g.argc==2 ){
forum_thread_list();
@@ -293,21 +296,22 @@
pThread = forumthread_create(froot, 1);
fossil_print("Chronological:\n");
fossil_print(
/* 0 1 2 3 4 5 6 7 */
/* 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123 */
- " sid fpid firt fprev mfirt pLeaf nReply hash\n");
+ " sid rev fpid pIrt pEditPrev pEditTail hash\n");
for(p=pThread->pFirst; p; p=p->pNext){
- fossil_print("%4d %9d %9d %9d %9d %9d %6d %8.8s\n", p->sid,
- p->fpid, p->firt, p->fprev, p->mfirt, p->pLeaf ? p->pLeaf->fpid : 0,
- p->nReply, p->zUuid);
+ fossil_print("%4d %4d %9d %9d %9d %9d %8.8s\n", p->sid, p->rev,
+ p->fpid, p->pIrt ? p->pIrt->fpid : 0,
+ p->pEditPrev ? p->pEditPrev->fpid : 0,
+ p->pEditTail ? p->pEditTail->fpid : 0, p->zUuid);
}
fossil_print("\nDisplay\n");
for(p=pThread->pDisplay; p; p=p->pDisplay){
fossil_print("%*s", (p->nIndent-1)*3, "");
- if( p->pLeaf ){
- fossil_print("%d->%d\n", p->fpid, p->pLeaf->fpid);
+ if( p->pEditTail ){
+ fossil_print("%d->%d\n", p->fpid, p->pEditTail->fpid);
}else{
fossil_print("%d\n", p->fpid);
}
}
forumthread_delete(pThread);
@@ -352,28 +356,10 @@
if( zClass ){
@
}
}
-/*
-** Generate the buttons in the display that allow a forum supervisor to
-** mark a user as trusted. Only do this if:
-**
-** (1) The poster is an individual, not a special user like "anonymous"
-** (2) The current user has Forum Supervisor privilege
-*/
-static void generateTrustControls(Manifest *pPost){
- if( !g.perm.AdminForum ) return;
- if( login_is_special(pPost->zUser) ) return;
- @
- @
- @
-}
-
/*
** Compute a display name from a login name.
**
** If the input login is found in the USER table, then check the USER.INFO
** field to see if it has display-name followed by an email address.
@@ -403,413 +389,326 @@
db_reset(&q);
return zResult;
}
/*
-** Display all posts in a forum thread in chronological order
+** Display a single post in a forum thread.
*/
-static void forum_display_chronological(int froot, int target, int bRawMode){
- ForumThread *pThread = forumthread_create(froot, 0);
- ForumEntry *p;
- int notAnon = login_is_individual();
- char cMode = bRawMode ? 'r' : 'c';
- for(p=pThread->pFirst; p; p=p->pNext){
- char *zDate;
- Manifest *pPost;
- int isPrivate; /* True for posts awaiting moderation */
- int sameUser; /* True if author is also the reader */
- const char *zUuid;
- char *zDisplayName; /* The display name */
- int sid;
-
- pPost = manifest_get(p->fpid, CFTYPE_FORUM, 0);
- if( pPost==0 ) continue;
- if( p->fpid==target ){
- @
Awaiting Moderator Approval
- }else{ - const char *zMimetype; - if( bRawMode ){ - zMimetype = "text/plain"; - }else if( p->pLeaf!=0 ){ - zMimetype = "text/plain"; - }else{ - zMimetype = pPost->zMimetype; - } - forum_render(0, zMimetype, pPost->zWiki, 0, 1); - } - if( g.perm.WrForum && p->pLeaf==0 ){ - int sameUser = login_is_individual() - && fossil_strcmp(pPost->zUser, g.zLogin)==0; + } + + /* Check if this post is approved, also if it's by the current user. */ + bPrivate = content_is_private(p->fpid); + bSameUser = login_is_individual() + && fossil_strcmp(pManifest->zUser, g.zLogin)==0; + + /* Render the post if the user is able to see it. */ + if( bPrivate && !g.perm.ModForum && !bSameUser ){ + @Awaiting Moderator Approval
+ }else{ + if( bRaw || bUnf || p->pEditTail ){ + zMimetype = "text/plain"; + }else{ + zMimetype = pManifest->zMimetype; + } + forum_render(0, zMimetype, pManifest->zWiki, 0, !bRaw); + } + + /* When not in raw mode, finish creating the border around the post. */ + if( !bRaw ){ + /* If the user is able to write to the forum and if this post has not been + ** edited, create a form with various interaction buttons. */ + if( g.perm.WrForum && !p->pEditTail ){ @ } - manifest_destroy(pPost); @sid | fpid | firt | fprev | mfirt | pLeaf | nReply | hash + @ | ||
---|---|---|---|---|---|---|---|---|---|
sid | rev | fpid | pIrt | pEditHead | pEditTail\ + @ | pEditNext | pEditPrev | pDisplay | hash for(p=pThread->pFirst; p; p=p->pNext){ - @ |
%d(p->sid) | %d(p->fpid) | %d(p->firt)\ - @ | %d(p->fprev) | %d(p->mfirt)\ - @ | %d(p->pLeaf?p->pLeaf->fpid:0) | %d(p->nReply)\ + @ | |||
%d(p->sid) | %d(p->rev) | %d(p->fpid)\ + @ | %d(p->pIrt ? p->pIrt->fpid : 0)\ + @ | %d(p->pEditHead ? p->pEditHead->fpid : 0)\ + @ | %d(p->pEditTail ? p->pEditTail->fpid : 0)\ + @ | %d(p->pEditNext ? p->pEditNext->fpid : 0)\ + @ | %d(p->pEditPrev ? p->pEditPrev->fpid : 0)\ + @ | %d(p->pDisplay ? p->pDisplay->fpid : 0)\ @ | %S(p->zUuid) |
Awaiting Moderator Approval
- }else{ - forum_render(0, bRawMode?"text/plain":pPost->zMimetype, pPost->zWiki, - 0, 1); - } - if( g.perm.WrForum && p->pLeaf==0 ){ - int sameUser = login_is_individual() - && fossil_strcmp(pPost->zUser, g.zLogin)==0; - @ - } - manifest_destroy(pPost); - @Awaiting Moderator Approval
- }else{ - forum_render(0, pPost->zMimetype, pPost->zWiki, 0, 1); - } - if( g.perm.WrForum ){ - @ - } - manifest_destroy(pPost); - @No such forum post: %h(zName) - }else{ - int isPrivate = content_is_private(fpid); - int notAnon = login_is_individual(); - int sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0; - if( isPrivate && !g.perm.ModForum && !sameUser ){ - @
Awaiting Moderator Approval
- }else{ - forum_render(0, "text/plain", pPost->zWiki, 0, 0); - } - manifest_destroy(pPost); - } - }else if( zMode[0]=='c' ){ - style_submenu_element("Hierarchical", "%R/%s/%s?t=h", g.zPath, zName); - style_submenu_element("Unformatted", "%R/%s/%s?t=r", g.zPath, zName); - forum_display_chronological(froot, fpid, 0); - }else if( zMode[0]=='r' ){ - style_submenu_element("Chronological", "%R/%s/%s?t=c", g.zPath, zName); - style_submenu_element("Hierarchical", "%R/%s/%s?t=h", g.zPath, zName); - forum_display_chronological(froot, fpid, 1); - }else if( zMode[0]=='y' ){ - style_header("Edit History Of A Forum Post"); - style_submenu_element("Complete Thread", "%R/%s/%s?t=a", g.zPath, zName); - forum_display_history(froot, fpid, 1); - }else{ - style_submenu_element("Chronological", "%R/%s/%s?t=c", g.zPath, zName); - style_submenu_element("Unformatted", "%R/%s/%s?t=r", g.zPath, zName); - forum_display_hierarchical(froot, fpid); - } - forumpost_emit_page_js(); + + /* Decode the mode parameters. */ + if( bRaw ){ + mode = FD_RAW; + bUnf = 1; + bHist = 0; + cgi_replace_query_parameter("unf", "on"); + cgi_delete_query_parameter("hist"); + cgi_delete_query_parameter("raw"); + }else{ + switch( *zMode ){ + case 'a': mode = cgi_from_mobile() ? FD_CHRONO : FD_HIER; break; + case 'c': mode = FD_CHRONO; break; + case 'h': mode = FD_HIER; break; + case 's': mode = FD_SINGLE; break; + case 'r': mode = FD_CHRONO; break; + case 'y': mode = FD_SINGLE; break; + default: webpage_error("Invalid thread mode: \"%s\"", zMode); + } + if( *zMode=='r' || *zMode=='y') { + bUnf = 1; + bHist = 1; + cgi_replace_query_parameter("t", mode==FD_CHRONO ? "c" : "s"); + cgi_replace_query_parameter("unf", "on"); + cgi_replace_query_parameter("hist", "on"); + } + } + + /* Define the page header. */ + zThreadTitle = db_text("", + "SELECT" + " substr(event.comment,instr(event.comment,':')+2)" + " FROM forumpost, event" + " WHERE event.objid=forumpost.fpid" + " AND forumpost.fpid=%d;", + fpid + ); + style_header("%s%s", zThreadTitle, *zThreadTitle ? "" : "Forum"); + fossil_free(zThreadTitle); + if( mode!=FD_CHRONO ){ + style_submenu_element("Chronological", "%R/%s/%s?t=c%s%s", g.zPath, zName, + bUnf ? "&unf" : "", bHist ? "&hist" : ""); + } + if( mode!=FD_HIER ){ + style_submenu_element("Hierarchical", "%R/%s/%s?t=h%s%s", g.zPath, zName, + bUnf ? "&unf" : "", bHist ? "&hist" : ""); + } + style_submenu_checkbox("unf", "Unformatted", 0, 0); + style_submenu_checkbox("hist", "History", 0, 0); + + /* Display the thread. */ + forum_display_thread(froot, fpid, mode, bUnf, bHist); + + /* Emit Forum Javascript. */ + style_emit_script_fossil_bootstrap(1); + builtin_request_js("forum.js"); + builtin_request_js("fossil.dom.js"); + builtin_request_js("fossil.page.forumpost.js"); + + /* Emit the page style. */ style_footer(); } /* ** Return true if a forum post should be moderated. @@ -949,11 +868,11 @@ webpage_assert( (zTitle==0)+(iInReplyTo==0)==1 ); blob_init(&x, 0, 0); zDate = date_in_standard_format("now"); blob_appendf(&x, "D %s\n", zDate); fossil_free(zDate); - zG = db_text(0, + zG = db_text(0, "SELECT uuid FROM blob, forumpost" " WHERE blob.rid==forumpost.froot" " AND forumpost.fpid=%d", iBasis); if( zG ){ blob_appendf(&x, "G %s\n", zG); @@ -1017,11 +936,11 @@ } /* ** Paint the form elements for entering a Forum post */ -static void forum_entry_widget( +static void forum_post_widget( const char *zTitle, const char *zMimetype, const char *zContent ){ if( zTitle ){ @@ -1126,11 +1045,11 @@ } style_header("New Forum Thread"); @