Fossil

Check-in [2e84fb3e]
Login

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

Overview
Comment:Merged with [82fc5abb60].
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1:2e84fb3e544008c383c28b49580020d608983ca8
User & Date: aku 2008-02-27 04:15:47
Context
2008-02-27
08:00
Fix comment typo, and add comment about possible simplification. check-in: 83ecb5b9 user: aku tags: trunk
04:15
Merged with [82fc5abb60]. check-in: 2e84fb3e user: aku tags: trunk
04:14
Added options --tempdir, -t to redirect the importer to a user-specified directory for temp files and directories. check-in: c5e9fa8d user: aku tags: trunk
2008-02-26
17:57
Cut over to the new timeline code. Remove the older timeline. check-in: 82fc5abb user: drh tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/descendents.c.

194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
  db_prepare(&q,
    "%s"
    "   AND blob.rid IN"
    "       (SELECT cid FROM plink EXCEPT SELECT pid FROM plink)"
    " ORDER BY event.mtime DESC",
    timeline_query_for_www()
  );
  www_print_timeline(&q, 0, 0, 0, 0);
  db_finalize(&q);
  @ <script>
  @ function xin(id){
  @ }
  @ function xout(id){
  @ }
  @ </script>
  style_footer();
}







|









194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
  db_prepare(&q,
    "%s"
    "   AND blob.rid IN"
    "       (SELECT cid FROM plink EXCEPT SELECT pid FROM plink)"
    " ORDER BY event.mtime DESC",
    timeline_query_for_www()
  );
  www_print_timeline(&q);
  db_finalize(&q);
  @ <script>
  @ function xin(id){
  @ }
  @ function xout(id){
  @ }
  @ </script>
  style_footer();
}

Changes to src/info.c.

330
331
332
333
334
335
336
337
338

339
340
341
342
343
344
345
    if( g.okSetup ){
      @ <tr><th>Record ID:</th><td>%d(rid)</td></tr>
    }
    @ <tr><th>Original&nbsp;User:</th><td>%h(db_column_text(&q, 2))</td></tr>
    @ <tr><th>Original&nbsp;Comment:</th><td>%w(db_column_text(&q,3))</td></tr>
    @ </td></tr>
    @ <tr><th>Timelines:</th><td>
    @    <a href="%s(g.zBaseURL)/timeline?e=%d(rid)&r">ancestors</a>
    @    | <a href="%s(g.zBaseURL)/timeline?e=%d(rid)&r&a">descendents</a>

    @ </td></tr>
    @ <tr><th>Commands:</th>
    @   <td>
    @     <a href="%s(g.zBaseURL)/vdiff/%d(rid)">diff</a>
    @     | <a href="%s(g.zBaseURL)/zip/%s(zUuid).zip">ZIP archive</a>
    @     | <a href="%s(g.zBaseURL)/artifact/%d(rid)">manifest</a>
    @   </td>







|
|
>







330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
    if( g.okSetup ){
      @ <tr><th>Record ID:</th><td>%d(rid)</td></tr>
    }
    @ <tr><th>Original&nbsp;User:</th><td>%h(db_column_text(&q, 2))</td></tr>
    @ <tr><th>Original&nbsp;Comment:</th><td>%w(db_column_text(&q,3))</td></tr>
    @ </td></tr>
    @ <tr><th>Timelines:</th><td>
    @    <a href="%s(g.zBaseURL)/timeline?p=%d(rid)">ancestors</a>
    @    | <a href="%s(g.zBaseURL)/timeline?d=%d(rid)">descendents</a>
    @    | <a href="%s(g.zBaseURL)/timeline?d=%d(rid)&p=%d(rid)">both</a>
    @ </td></tr>
    @ <tr><th>Commands:</th>
    @   <td>
    @     <a href="%s(g.zBaseURL)/vdiff/%d(rid)">diff</a>
    @     | <a href="%s(g.zBaseURL)/zip/%s(zUuid).zip">ZIP archive</a>
    @     | <a href="%s(g.zBaseURL)/artifact/%d(rid)">manifest</a>
    @   </td>

Changes to src/stat.c.

Changes to src/timeline.c.

87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
...
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
...
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
...
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271

272
273
274
275
276
277
278
...
279
280
281
282
283
284
285

286
287

288





































289



290
291

292
293
294
295


296
297
298
299
300
301
302
303
304
305
306
307








308
309
310

311
312

313
314
315
316
317
318



319


320
321
322

323
324
325
326
327
328



329
330
331
332
333
334
335
336
337
338


339
340
341
342


343
344
345

346
347
348
349



350
351
352
353



354



355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375





























376
377
378
379
380
381
382
...
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
**    5.  Number of non-merge children
**    6.  Number of parents
**    7.  True if is a leaf
**    8.  background color
**    9.  type ("ci", "w")
*/
void www_print_timeline(
  Stmt *pQuery,
  int *pFirstEvent,
  int *pLastEvent,
  int (*xCallback)(int, Blob*),
  Blob *pArg
 ){
  int cnt = 0;
  int wikiFlags;
  int mxWikiLen;
  Blob comment;
  char zPrevDate[20];
  zPrevDate[0] = 0;

  mxWikiLen = db_get_int("timeline-max-comment", 0);
................................................................................
    int nPChild = db_column_int(pQuery, 5);
    int nParent = db_column_int(pQuery, 6);
    int isLeaf = db_column_int(pQuery, 7);
    const char *zBgClr = db_column_text(pQuery, 8);
    const char *zDate = db_column_text(pQuery, 2);
    const char *zType = db_column_text(pQuery, 9);
    const char *zUser = db_column_text(pQuery, 4);
    if( cnt==0 && pFirstEvent ){
      *pFirstEvent = rid;
    }
    cnt++;
    if( pLastEvent ){
      *pLastEvent = rid;
    }
    db_multi_exec("INSERT OR IGNORE INTO seen VALUES(%d)", rid);
    if( xCallback ){
      xCallback(rid, pArg);
    }
    if( memcmp(zDate, zPrevDate, 10) ){
      sprintf(zPrevDate, "%.10s", zDate);
      @ <tr><td colspan=3>
      @   <div class="divider">%s(zPrevDate)</div>
      @ </td></tr>
    }
    @ <tr>
................................................................................
    blob_reset(&comment);
    @ (by %h(zUser))</td></tr>
  }
  @ </table>
}

/*
** Generate javascript code that records the parents and children
** of the version rid.
*/
static int save_parentage_javascript(int rid, Blob *pOut){
  const char *zSep;
  Stmt q;

  db_prepare(&q, "SELECT pid FROM plink WHERE cid=%d", rid);
  zSep = "";
  blob_appendf(pOut, "parentof[\"m%d\"] = [", rid);
  while( db_step(&q)==SQLITE_ROW ){
    int pid = db_column_int(&q, 0);
    blob_appendf(pOut, "%s\"m%d\"", zSep, pid);
    zSep = ",";
  }
  db_finalize(&q);
  blob_appendf(pOut, "];\n");
  db_prepare(&q, "SELECT cid FROM plink WHERE pid=%d", rid);
  zSep = "";
  blob_appendf(pOut, "childof[\"m%d\"] = [", rid);
  while( db_step(&q)==SQLITE_ROW ){
    int pid = db_column_int(&q, 0);
    blob_appendf(pOut, "%s\"m%d\"", zSep, pid);
    zSep = ",";
  }
  db_finalize(&q);
  blob_appendf(pOut, "];\n");
  return 0;
}

/*
** Return a pointer to a constant string that forms the basis
** for a timeline query for the WWW interface.
*/
const char *timeline_query_for_www(void){
................................................................................
}

/*
** WEBPAGE: timeline
**
** Query parameters:
**
**    d=STARTDATE    date in iso8601 notation.          dflt: newest event
**    n=INTEGER      number of events to show.          dflt: 25
**    e=INTEGER      starting event id.                 dflt: nil
**    u=NAME         show only events from user.        dflt: nil
**    a              show events after and including.   dflt: false
**    r              show only related events.          dflt: false
**    y=TYPE         show only TYPE ('ci' or 'w')       dflt: nil
**    s              show the SQL                       dflt: nil
*/
void page_timeline(void){
  Stmt q;           
  Blob sql;                    /* text of SQL used to generate timeline */
  char *zSQL;                  /* Rendered copy of sql */
  Blob scriptInit;
  char zDate[100];
  const char *zStart = P("d");       /* Starting date */
  int nEntry = atoi(PD("n","20"));   /* Max number of entries on timeline */
  const char *zUser = P("u");        /* All entries by this user if not NULL */
  int objid = atoi(PD("e","0"));     /* Entries related to this event */
  int relatedEvents = P("r")!=0;     /* Must be directly related to of objid */
  int afterFlag = P("a")!=0;         /* After objid if true */
  const char *zType = P("y");        /* Type of events.  All if NULL */
  int firstEvent;              /* First event displayed */
  int lastEvent;               /* Last event displayed */
  Blob desc;                   /* Human readable description of the timeline */
  const char *zEType;          /* Human readable event type */


  /* To view the timeline, must have permission to read project data.
  */
  login_check_credentials();
  if( !g.okRead ){ login_needed(); return; }

  style_header("Timeline");
................................................................................
  if( !g.okHistory &&
      db_exists("SELECT 1 FROM user"
                " WHERE login='anonymous'"
                "   AND cap LIKE '%%h%%'") ){
    @ <p><b>Note:</b> You will be able to access <u>much</u> more
    @ historical information if you <a href="%s(g.zTop)/login">login</a>.</p>
  }

  blob_zero(&sql);
  blob_zero(&desc);

  blob_append(&sql, timeline_query_for_www(), -1);





































  zEType = "events";



  if( zType ){
    blob_appendf(&sql, " AND event.type=%Q", zType);

    if( zType[0]=='c' ){
      zEType = "checkins";
    }else if( zType[0]=='w' ){
      zEType = "wiki edits";


    }
  }
  blob_appendf(&desc, "Timeline of up to %d %s", nEntry, zEType);
  if( zUser ){
    blob_appendf(&sql, " AND event.user=%Q", zUser);
    blob_appendf(&desc, " by user %h", zUser);
  }
  if( objid ){
    char *z = db_text(0, "SELECT datetime(event.mtime, 'localtime') FROM event"
                         " WHERE objid=%d", objid);
    if( z ){
      zStart = z;








    }
  }
  if( zStart ){

    while( isspace(zStart[0]) ){ zStart++; }
    if( zStart[0] ){

      blob_appendf(&sql, 
         " AND event.mtime %s (SELECT julianday(%Q, 'utc'))",
                          afterFlag ? ">=" : "<=", zStart);
      blob_appendf(&desc, " occurring on or %s %h",
          afterFlag ? "after": "before",
          zStart);



    }


  }
  if( relatedEvents && objid ){
    char *zUuid;

    db_multi_exec(
       "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY)"
    );
    zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", objid);
    if( afterFlag ){
      compute_descendents(objid, nEntry);



      blob_appendf(&desc,
         " and decended from <a href='%s/vinfo/%d'>[%.10s]</a>",
         g.zBaseURL, objid, zUuid);
    }else{
      compute_ancestors(objid, nEntry);
      blob_appendf(&desc,
         " and a ancestor of <a href='%s/vinfo/%d'>[%.10s]</a>",
         g.zBaseURL, objid, zUuid);
    }
    blob_append(&sql, " AND event.objid IN ok", -1);


  }
  if( afterFlag ){
    blob_appendf(&sql, " ORDER BY event.mtime ASC LIMIT %d",
                 nEntry);


  }else{
    blob_appendf(&sql, " ORDER BY event.mtime DESC LIMIT %d",
                 nEntry);

  }
  zSQL = blob_str(&sql);
  if( afterFlag ){
    zSQL = mprintf("SELECT * FROM (%s) ORDER BY timestamp DESC", zSQL);



  }
  db_prepare(&q, zSQL);
  if( P("s")!=0 ){
    @ <hr><p>%h(zSQL)</p><hr>



  }



  @ <h2>%b(&desc)</h2>
  blob_reset(&desc);
  blob_zero(&sql);
  if( afterFlag ){
    free(zSQL);
  }
  zDate[0] = 0;
  blob_zero(&scriptInit);
  zDate[0] = 0;
  www_print_timeline(&q, &firstEvent, &lastEvent,
                     save_parentage_javascript, &scriptInit);
  db_finalize(&q);
  /* @ <p>firstEvent=%d(firstEvent) lastEvent=%d(lastEvent)</p> */
  if( zStart==0 ){
    zStart = zDate;
  }
  @ <script>
  @ var parentof = new Object();
  @ var childof = new Object();
  cgi_append_content(blob_buffer(&scriptInit), blob_size(&scriptInit));
  blob_reset(&scriptInit);





























  @ function setall(value){
  @   for(var x in parentof){
  @     setone(x,value);
  @   }
  @ }
  @ setall("#ffffff");
  @ function setone(id, clr){
................................................................................
  @     var cid = clist[x];
  @     if( setone(cid,clr)==1 ){
  @       set_children(cid,clr);
  @     }
  @   }
  @ }
  @ </script>
  @ <hr>
  @ <form method="GET" action="%s(g.zBaseURL)/timeline">
  @ Start Date:
  @ <input type="text" size="30" value="%h(zStart)" name="d">
  @ Number Of Entries:  
  @ <input type="text" size="4" value="%d(nEntry)" name="n">
  @ <br><input type="submit" value="Submit">
  @ </form>
  @ <table><tr><td>
  @ <form method="GET" action="%s(g.zBaseURL)/timeline">
  @ <input type="hidden" value="%d(lastEvent)" name="e">
  @ <input type="hidden" value="%d(nEntry)" name="n">
  @ <input type="submit" value="Next %d(nEntry) Rows">
  @ </form></td><td>
  @ <form method="GET" action="%s(g.zBaseURL)/timeline">
  @ <input type="hidden" value="%d(firstEvent)" name="e">
  @ <input type="hidden" value="%d(nEntry)" name="n">
  @ <input type="hidden" value="1" name="a">
  @ <input type="submit" value="Previous %d(nEntry) Rows">
  @ </form></td></tr></table>
  style_footer();
}

/*
** The input query q selects various records.  Print a human-readable
** summary of those records.
**







|
<
<
<
<

<







 







<
<
<
<
<
<
<

<
<
<







 







|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<
<
<
<
<
<
<
<
<
<







 







|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>







 







>


>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
|
|
>
|
|
|
|
>
>
|
|
<
|
|
|
|
<
<
<
|
<
>
>
>
>
>
>
>
>
|
<
<
>
|
<
>
|
|
<
<
<
<
>
>
>
|
>
>
|
<
<
>
|
<
<
<
<
<
>
>
>
|
<
<

<
|
<
<

<
>
>
|
<
<
<
>
>
|
<
<
>
|
<
<
<
>
>
>
|
<
<
<
>
>
>
|
>
>
>


<
<
<
<
<
<
<
|
<

<
<
<
|



<
<
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







87
88
89
90
91
92
93
94




95

96
97
98
99
100
101
102
...
118
119
120
121
122
123
124







125



126
127
128
129
130
131
132
...
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
...
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
244
245
246
247
248
249
250
251
252
253
254
...
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318

319
320
321
322



323

324
325
326
327
328
329
330
331
332


333
334

335
336
337




338
339
340
341
342
343
344


345
346





347
348
349
350


351

352


353

354
355
356



357
358
359


360
361



362
363
364
365



366
367
368
369
370
371
372
373
374







375

376



377
378
379
380


381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
...
463
464
465
466
467
468
469




















470
471
472
473
474
475
476
**    5.  Number of non-merge children
**    6.  Number of parents
**    7.  True if is a leaf
**    8.  background color
**    9.  type ("ci", "w")
*/
void www_print_timeline(
  Stmt *pQuery




 ){

  int wikiFlags;
  int mxWikiLen;
  Blob comment;
  char zPrevDate[20];
  zPrevDate[0] = 0;

  mxWikiLen = db_get_int("timeline-max-comment", 0);
................................................................................
    int nPChild = db_column_int(pQuery, 5);
    int nParent = db_column_int(pQuery, 6);
    int isLeaf = db_column_int(pQuery, 7);
    const char *zBgClr = db_column_text(pQuery, 8);
    const char *zDate = db_column_text(pQuery, 2);
    const char *zType = db_column_text(pQuery, 9);
    const char *zUser = db_column_text(pQuery, 4);







    db_multi_exec("INSERT OR IGNORE INTO seen VALUES(%d)", rid);



    if( memcmp(zDate, zPrevDate, 10) ){
      sprintf(zPrevDate, "%.10s", zDate);
      @ <tr><td colspan=3>
      @   <div class="divider">%s(zPrevDate)</div>
      @ </td></tr>
    }
    @ <tr>
................................................................................
    blob_reset(&comment);
    @ (by %h(zUser))</td></tr>
  }
  @ </table>
}

/*
** Create a temporary table suitable for storing timeline data.
*/
static void timeline_temp_table(void){
  static const char zSql[] = 
    @ CREATE TEMP TABLE IF NOT EXISTS timeline(
    @   rid INTEGER PRIMARY KEY,
    @   uuid TEXT,
    @   timestamp TEXT,
    @   comment TEXT,
    @   user TEXT,
    @   nchild INTEGER,
    @   nparent INTEGER,
    @   isleaf BOOLEAN,
    @   bgcolor TEXT,
    @   etype TEXT
    @ )
  ;
  db_multi_exec(zSql);










}

/*
** Return a pointer to a constant string that forms the basis
** for a timeline query for the WWW interface.
*/
const char *timeline_query_for_www(void){
................................................................................
}

/*
** WEBPAGE: timeline
**
** Query parameters:
**
**    a=TIMESTAMP    after this date
**    b=TIMESTAMP    before this date.
**    n=COUNT        number of events in output
**    p=RID          artifact RID and up to COUNT parents and ancestors
**    d=RID          artifact RID and up to COUNT descendents
**    u=USER         only if belonging to this user
**    y=TYPE         'ci', 'w', 'tkt'
**
** p= and d= can appear individually or together.  If either p= or d=
** appear, then u=, y=, a=, and b= are ignored.
**
** If a= and b= appear, only a= is used.  If neither appear, the most
** recent events are choosen.
**
** If n= is missing, the default count is 20.
*/
void page_timeline(void){
  Stmt q;                            /* Query used to generate the timeline */
  Blob sql;                          /* text of SQL used to generate timeline */
  Blob desc;                         /* Description of the timeline */
  int nEntry = atoi(PD("n","20"));   /* Max number of entries on timeline */
  int p_rid = atoi(PD("p","0"));     /* artifact p and its parents */
  int d_rid = atoi(PD("d","0"));     /* artifact d and its descendents */
  const char *zUser = P("u");        /* All entries by this user if not NULL */
  const char *zType = P("y");        /* Type of events.  All if NULL */
  const char *zAfter = P("a");       /* Events after this time */
  const char *zBefore = P("b");      /* Events before this time */

  /* To view the timeline, must have permission to read project data.
  */
  login_check_credentials();
  if( !g.okRead ){ login_needed(); return; }

  style_header("Timeline");
................................................................................
  if( !g.okHistory &&
      db_exists("SELECT 1 FROM user"
                " WHERE login='anonymous'"
                "   AND cap LIKE '%%h%%'") ){
    @ <p><b>Note:</b> You will be able to access <u>much</u> more
    @ historical information if you <a href="%s(g.zTop)/login">login</a>.</p>
  }
  timeline_temp_table();
  blob_zero(&sql);
  blob_zero(&desc);
  blob_append(&sql, "INSERT OR IGNORE INTO timeline ", -1);
  blob_append(&sql, timeline_query_for_www(), -1);
  if( p_rid || d_rid ){
    /* If p= or d= is present, ignore all other parameters other than n= */
    char *zUuid;
    int np, nd;

    if( p_rid && d_rid && p_rid!=d_rid ) p_rid = d_rid;
    db_multi_exec(
       "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY)"
    );
    zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d",
                         p_rid ? p_rid : d_rid);
    blob_appendf(&sql, " AND event.objid IN ok");
    nd = 0;
    if( d_rid ){
      compute_descendents(d_rid, nEntry);
      nd = db_int(0, "SELECT count(*)-1 FROM ok");
      if( nd>0 ){
        db_multi_exec("%s", blob_str(&sql));
        blob_appendf(&desc, "%d descendents", nd);
      }
      db_multi_exec("DELETE FROM ok");
    }
    if( p_rid ){
      compute_ancestors(p_rid, nEntry);
      np = db_int(0, "SELECT count(*)-1 FROM ok");
      if( np>0 ){
        if( nd>0 ) blob_appendf(&desc, " and ");
        blob_appendf(&desc, "%d ancestors", np);
        db_multi_exec("%s", blob_str(&sql));
      }
    }
    blob_appendf(&desc, " of <a href='%s/info/%s'>[%.10s]</a>",
                 g.zBaseURL, zUuid, zUuid);
    db_prepare(&q, "SELECT * FROM timeline ORDER BY timestamp DESC");
  }else{
    int n;
    Blob url;
    const char *zEType = "event";
    const char *zDate;
    blob_zero(&url);
    blob_appendf(&url, "%s/timeline?n=%d", g.zBaseURL, nEntry);
    if( zType ){
      blob_appendf(&sql, " AND event.type=%Q", zType);
      blob_appendf(&url, "&y=%T", zType);
      if( zType[0]=='c' ){
        zEType = "checkin";
      }else if( zType[0]=='w' ){
        zEType = "wiki edit";
      }else if( zType[0]=='t' ){
        zEType = "ticket change";
      }
    }

    if( zUser ){
      blob_appendf(&sql, " AND event.user=%Q", zUser);
      blob_appendf(&url, "&u=%T", zUser);
    }



    if( zAfter ){

      while( isspace(zAfter[0]) ){ zAfter++; }
      if( zAfter[0] ){
        blob_appendf(&sql, 
           " AND event.mtime>=(SELECT julianday(%Q, 'utc'))"
           " ORDER BY event.mtime ASC", zAfter);
        zBefore = 0;
      }else{
        zAfter = 0;
      }


    }else if( zBefore ){
      while( isspace(zBefore[0]) ){ zBefore++; }

      if( zBefore[0] ){
        blob_appendf(&sql, 
           " AND event.mtime<=(SELECT julianday(%Q, 'utc'))"




           " ORDER BY event.mtime DESC", zBefore);
       }else{
        zBefore = 0;
      }
    }else{
      blob_appendf(&sql, " ORDER BY event.mtime DESC");
    }


    blob_appendf(&sql, " LIMIT %d", nEntry);
    db_multi_exec("%s", blob_str(&sql));






    n = db_int(0, "SELECT count(*) FROM timeline");
    if( zAfter==0 && zBefore==0 ){
      blob_appendf(&desc, "%d most recent %ss", n, zEType);


    }else{

      blob_appendf(&desc, "%d %ss", n, zEType);


    }

    if( zUser ){
      blob_appendf(&desc, " by user %h", zUser);
    }



    if( zAfter ){
      blob_appendf(&desc, " occurring on or after %h.<br>", zAfter);
    }else if( zBefore ){


      blob_appendf(&desc, " occurring on or before %h.<br>", zBefore);
    }



    if( zAfter || n==nEntry ){
      zDate = db_text(0, "SELECT min(timestamp) FROM timeline");
      blob_appendf(&desc, " <a href='%b&b=%s'>[older]</a>", &url, zDate);
    }



    if( zBefore || (zAfter && n==nEntry) ){
      zDate = db_text(0, "SELECT max(timestamp) FROM timeline");
      blob_appendf(&desc, " <a href='%b&a=%s'>[more recent]</a>", &url, zDate);
    }
  }
  blob_zero(&sql);
  db_prepare(&q, "SELECT * FROM timeline ORDER BY timestamp DESC");
  @ <h2>%b(&desc)</h2>
  blob_reset(&desc);







  www_print_timeline(&q);

  db_finalize(&q);




  @ <script>
  @ var parentof = new Object();
  @ var childof = new Object();


  db_prepare(&q, "SELECT rid FROM timeline");
  while( db_step(&q)==SQLITE_ROW ){
    int rid = db_column_int(&q, 0);
    Stmt q2;
    const char *zSep;
    Blob *pOut = cgi_output_blob();

    db_prepare(&q2, "SELECT pid FROM plink WHERE cid=%d", rid);
    zSep = "";
    blob_appendf(pOut, "parentof[\"m%d\"] = [", rid);
    while( db_step(&q2)==SQLITE_ROW ){
      int pid = db_column_int(&q2, 0);
      blob_appendf(pOut, "%s\"m%d\"", zSep, pid);
      zSep = ",";
    }
    db_finalize(&q2);
    blob_appendf(pOut, "];\n");
    db_prepare(&q2, "SELECT cid FROM plink WHERE pid=%d", rid);
    zSep = "";
    blob_appendf(pOut, "childof[\"m%d\"] = [", rid);
    while( db_step(&q2)==SQLITE_ROW ){
      int pid = db_column_int(&q2, 0);
      blob_appendf(pOut, "%s\"m%d\"", zSep, pid);
      zSep = ",";
    }
    db_finalize(&q2);
    blob_appendf(pOut, "];\n");
  }
  db_finalize(&q);
  @ function setall(value){
  @   for(var x in parentof){
  @     setone(x,value);
  @   }
  @ }
  @ setall("#ffffff");
  @ function setone(id, clr){
................................................................................
  @     var cid = clist[x];
  @     if( setone(cid,clr)==1 ){
  @       set_children(cid,clr);
  @     }
  @   }
  @ }
  @ </script>




















  style_footer();
}

/*
** The input query q selects various records.  Print a human-readable
** summary of those records.
**

Changes to src/wiki.c.

461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
  zSQL = mprintf("%s AND event.objid IN "
                 "  (SELECT rid FROM tagxref WHERE tagid="
                       "(SELECT tagid FROM tag WHERE tagname='wiki-%q'))"
                 "ORDER BY mtime DESC",
                 timeline_query_for_www(), zPageName);
  db_prepare(&q, zSQL);
  free(zSQL);
  www_print_timeline(&q, 0, 0, 0, 0);
  db_finalize(&q);
  style_footer();
}

/*
** WEBPAGE: wcontent
**







|







461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
  zSQL = mprintf("%s AND event.objid IN "
                 "  (SELECT rid FROM tagxref WHERE tagid="
                       "(SELECT tagid FROM tag WHERE tagname='wiki-%q'))"
                 "ORDER BY mtime DESC",
                 timeline_query_for_www(), zPageName);
  db_prepare(&q, zSQL);
  free(zSQL);
  www_print_timeline(&q);
  db_finalize(&q);
  style_footer();
}

/*
** WEBPAGE: wcontent
**