Fossil

Check-in [32fc62e681]
Login

Check-in [32fc62e681]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Initial (untested) code for creating the control artifact for closing and re-opening forum threads. Extend test-forumthread's tree view to show thread closure.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | forumpost-locking
Files: files | file ages | folders
SHA3-256: 32fc62e68160783ab85ed1ceeb0ab3d211028aeb6f8d3c9e0959821321f89bd0
User & Date: stephan 2023-02-21 10:30:22.137
Context
2023-02-22
04:46
Rework forumpost closure to always apply to the first artifact in an edit chain to enable consistent behavior across the whole chain and responses to arbitrary versions within that chain. Add rudimentary UI elements for closing/re-opening posts, but their layout needs to be revisited (noting that they need to be in a separate form from the main editor so that closing/re-opening introduces only a smalll control artifact instead of a whole forumpost artifact). ... (check-in: cc6ca4e110 user: stephan tags: forumpost-locking)
2023-02-21
10:30
Initial (untested) code for creating the control artifact for closing and re-opening forum threads. Extend test-forumthread's tree view to show thread closure. ... (check-in: 32fc62e681 user: stephan tags: forumpost-locking)
09:52
Add db_add_unsent() and replace numerous "INSERT OR IGNORE INTO unset" statements with that. ... (check-in: 98d4ee73d7 user: stephan tags: forumpost-locking)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/default.css.
905
906
907
908
909
910
911



912
913
914
915
916
917
918
  opacity: 0.7;
}
div.forumClosed > *:first-child::before {
  content: "[CLOSED] ";
  color: red;
  opacity: 0.7;
}



.forum div > form {
  margin: 0.5em 0;
}
.forum-post-collapser {
  /* Common style for the bottom-of-post and right-of-post
     expand/collapse widgets. */
  font-size: 0.8em;







>
>
>







905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
  opacity: 0.7;
}
div.forumClosed > *:first-child::before {
  content: "[CLOSED] ";
  color: red;
  opacity: 0.7;
}
/*div.forumClosed > div.forumPostBody {
  filter: blur(5px);
}*/
.forum div > form {
  margin: 0.5em 0;
}
.forum-post-collapser {
  /* Common style for the bottom-of-post and right-of-post
     expand/collapse widgets. */
  font-size: 0.8em;
Changes to src/forum.c.
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
** locked and later unlocked. The return value is the tagxref.rowid
** value of the tagxref entry which applies the "closed" tag, or 0 if
** no active tag is found.
**
** If bCheckParents is true then p's thread parents are checked
** (recursively) for closure, else only p is checked.
*/
int forum_post_is_closed(ForumPost *p, int bCheckParents){
  if( !p ) return 0;
  if( p->pEditTail ) p = p->pEditTail;
  if( p->iClosed || !bCheckParents ) return p->iClosed;
  else if( p->pIrt ){
    return forum_post_is_closed(p->pIrt->pEditTail
                                ? p->pIrt->pEditTail : p->pIrt,
                                bCheckParents);







|







88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
** locked and later unlocked. The return value is the tagxref.rowid
** value of the tagxref entry which applies the "closed" tag, or 0 if
** no active tag is found.
**
** If bCheckParents is true then p's thread parents are checked
** (recursively) for closure, else only p is checked.
*/
static int forum_post_is_closed(ForumPost *p, int bCheckParents){
  if( !p ) return 0;
  if( p->pEditTail ) p = p->pEditTail;
  if( p->iClosed || !bCheckParents ) return p->iClosed;
  else if( p->pIrt ){
    return forum_post_is_closed(p->pIrt->pEditTail
                                ? p->pIrt->pEditTail : p->pIrt,
                                bCheckParents);
140
141
142
143
144
145
146


























































































147
148
149
150
151
152
153
  rid = SQLITE_ROW==db_step(&qIrt) ? db_column_int(&qIrt, 0) : 0;
  db_reset(&qIrt);
  if( rid ){
    rc = forum_rid_is_closed(rid, 1);
  }
  return rc>0 ? -rc : rc;
}



























































































/*
** If iClosed is true and the current user has admin privileges, this
** renders either a checkbox to unlock forum post fpid (if iClosed>0)
** or a SPAN.warning element that the given post inherits the CLOSED
** status from a parent post (if iClosed<0). If neither of the initial
** conditions is true, this is a no-op.







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
  rid = SQLITE_ROW==db_step(&qIrt) ? db_column_int(&qIrt, 0) : 0;
  db_reset(&qIrt);
  if( rid ){
    rc = forum_rid_is_closed(rid, 1);
  }
  return rc>0 ? -rc : rc;
}

/*
** UNTESTED!
**
** Closes or re-opens the given forum RID via addition of a new
** control artifact into the repository.
**
** If doClose is true then a propagating "closed" tag is added, except
** as noted below, with the given optional zReason string as the tag's
** value. If doClose is false then any active "closed" tag on frid is
** cancelled, except as noted below. zReason is ignored if doClose is
** false or if zReason is NULL or starts with a NUL byte.
**
** This function only adds a "closed" tag to frid if
** forum_rid_is_closed() indicates that frid is not closed. If a
** parent post is already closed, no tag is added. Similarly, it will
** only remove a "closed" tag from a post which has its own "closed"
** tag, and will not remove an inherited one from a parent post.
**
** If doClose is true and frid is closed (directly or inherited), this
** is a no-op. Likewise, if doClose is false and frid itself is not
** closed (not accounting for an inherited closed tag), this is a
** no-op.
**
** Returns true if it actually creates a new tag, else false. Fails
** fatally on error. If it returns true then any ForumPost::iClosed
** values from previously loaded posts are invalidated if they refer
** to the amended post or a response to it.
**
** Sidebars:
**
** - Unless the caller has a transaction open, via
**   db_begin_transaction(), there is a very tiny race condition
**   window during which the caller's idea of whether or not the forum
**   post is closed may differ from the current repository state.
**
** - This routine assumes that frid really does refer to a forum post.
**
** - This routine assumes that frid is not private or pending
**   moderation.
**
** - Closure of a forum post requires a propagating "closed" tag to
**   account for how edits of posts are handled. This differs from
**   closure of a branch, where a non-propagating tag is used.
*/
/*static*/ int forumpost_close(int frid, int doClose, const char *zReason){
  Blob artifact = BLOB_INITIALIZER;  /* Output artifact */
  Blob cksum = BLOB_INITIALIZER;     /* Z-card */
  int iClosed;                       /* true if frid is closed */
  int trid;                          /* RID of new control artifact */

  db_begin_transaction();
  iClosed = forum_rid_is_closed(frid, 1);
  if( (iClosed && doClose
      /* Already closed, noting that in the case of (iClosed<0), it's
      ** actually a parent which is closed. */)
      || (iClosed<=0 && !doClose
          /* This entry is not closed, but a parent post may be. */) ){
    db_end_transaction(0);
    return 0;
  }
  if( doClose==0 || (zReason && !zReason[0]) ){
    zReason = 0;
  }
  blob_appendf(&artifact, "D %z\n", date_in_standard_format( "now" ));
  blob_appendf(&artifact,
               "T %cclosed %z%s%F\n",
               doClose ? '*' : '-', rid_to_uuid(frid),
               zReason ? " " : "", zReason ? zReason : "");
  blob_appendf(&artifact, "U %F\n", login_name());
  md5sum_blob(&artifact, &cksum);
  blob_appendf(&artifact, "Z %b\n", &cksum);
  blob_reset(&cksum);
  trid = content_put_ex(&artifact, 0, 0, 0, 0);
  if( trid==0 ){
    fossil_fatal("Error saving tag artifact: %s", g.zErrMsg);
  }
  if( manifest_crosslink(trid, &artifact,
                         MC_NONE /*MC_PERMIT_HOOKS?*/)==0 ){
    fossil_fatal("%s", g.zErrMsg);
  }
  assert( blob_is_reset(&artifact) );
  db_add_unsent(trid);
  /* Potential TODO: if (iClosed>0) then we could find the initial tag
  ** artifact and content_deltify(thatRid,&trid,1,0). Given the tiny
  ** size of these artifacts, however, that would save little space,
  ** if any. */
  db_end_transaction(0);
  return 1;
}

/*
** If iClosed is true and the current user has admin privileges, this
** renders either a checkbox to unlock forum post fpid (if iClosed>0)
** or a SPAN.warning element that the given post inherits the CLOSED
** status from a parent post (if iClosed<0). If neither of the initial
** conditions is true, this is a no-op.
405
406
407
408
409
410
411
412
413
414
415




416
417
418
419
420
421
422
       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->pEditTail ){
      fossil_print("%d->%d\n", p->fpid, p->pEditTail->fpid);
    }else{
      fossil_print("%d\n", p->fpid);
    }




  }
  forumthread_delete(pThread);
}

/*
** Render a forum post for display
*/







|

|

>
>
>
>







495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
       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->pEditTail ){
      fossil_print("%d->%d", p->fpid, p->pEditTail->fpid);
    }else{
      fossil_print("%d", p->fpid);
    }
    if( p->iClosed ){
      fossil_print(" [closed%s]", p->iClosed<0 ? " via parent" : "");
    }
    fossil_print("\n");
  }
  forumthread_delete(pThread);
}

/*
** Render a forum post for display
*/
Changes to src/tag.c.
908
909
910
911
912
913
914




915
916
917
918
919
920
921


/*
** Returns tagxref.rowid if the given blob.rid has a tagxref.rid entry
** of an active (non-cancelled) tag matching the given rid and tag
** name string, else returns 0. Note that this function does not
** distinguish between a non-existent tag and a cancelled tag.




*/
int rid_has_active_tag_name(int rid, const char *zTagName){
  static Stmt q = empty_Stmt_m;
  int rc;

  assert( 0 != zTagName );
  if( !q.pStmt ){







>
>
>
>







908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925


/*
** Returns tagxref.rowid if the given blob.rid has a tagxref.rid entry
** of an active (non-cancelled) tag matching the given rid and tag
** name string, else returns 0. Note that this function does not
** distinguish between a non-existent tag and a cancelled tag.
**
** Design note: the return value is the tagxref.rowid because that
** gives us an easy way to fetch the value of the tag later on, if
** needed.
*/
int rid_has_active_tag_name(int rid, const char *zTagName){
  static Stmt q = empty_Stmt_m;
  int rc;

  assert( 0 != zTagName );
  if( !q.pStmt ){