@@ -55,9 +55,9 @@
char zShortUuid[UUID_SIZE+1];
sprintf(zShortUuid, "%.10s", zUuid);
if( g.okHistory ){
@ <a onmouseover='%s(zIn)("m%d(id)")' onmouseout='%s(zOut)("m%d(id)")'
- @ href="%s(g.zBaseURL)/ci/%s(zUuid)">[%s(zShortUuid)]</a>
+ @ href="%s(g.zBaseURL)/vinfo/%s(zUuid)">[%s(zShortUuid)]</a>
}else{
@ <b onmouseover='%s(zIn)("m%d(id)")' onmouseout='%s(zOut)("m%d(id)")'>
@ [%s(zShortUuid)]</b>
}
@@ -72,8 +72,38 @@
@ <a href="%s(g.zBaseURL)/diff?v2=%s(zV1)">[diff]</a>
}else{
@ <a href="%s(g.zBaseURL)/diff?v1=%s(zV1)&v2=%s(zV2)">[diff]</a>
}
+ }
+}
+
+/*
+** Generate a hyperlink to a date & time.
+*/
+void hyperlink_to_date(const char *zDate, const char *zSuffix){
+ if( zSuffix==0 ) zSuffix = "";
+ if( g.okHistory ){
+ @ <a href="%s(g.zTop)/timeline?c=%T(zDate)">%s(zDate)</a>%s(zSuffix)
+ }else{
+ @ %s(zDate)%s(zSuffix)
+ }
+}
+
+/*
+** Generate a hyperlink to a user. This will link to a timeline showing
+** events by that user. If the date+time is specified, then the timeline
+** is centered on that date+time.
+*/
+void hyperlink_to_user(const char *zU, const char *zD, const char *zSuf){
+ if( zSuf==0 ) zSuf = "";
+ if( g.okHistory ){
+ if( zD && zD[0] ){
+ @ <a href="%s(g.zTop)/timeline?c=%T(zD)&u=%T(zU)">%h(zU)</a>%s(zSuf)
+ }else{
+ @ <a href="%s(g.zTop)/timeline?u=%T(zU)">%h(zU)</a>%s(zSuf)
+ }
+ }else{
+ @ %s(zU)
}
}
/*
@@ -103,8 +133,9 @@
*/
#if INTERFACE
#define TIMELINE_ARTID 0x0001 /* Show artifact IDs on non-check-in lines */
#define TIMELINE_LEAFONLY 0x0002 /* Show "Leaf", but not "Merge", "Fork" etc */
+#define TIMELINE_BRIEF 0x0004 /* Combine adjacent elements of same object */
#endif
/*
** Output a timeline in the web format given a query. The query
@@ -118,10 +149,12 @@
** 5. Number of non-merge children
** 6. Number of parents
** 7. True if is a leaf
** 8. background color
-** 9. type ("ci", "w")
+** 9. type ("ci", "w", "t")
** 10. list of symbolic tags.
+** 11. tagid for ticket or wiki
+** 12. Short comment to user for repeated tickets and wiki
*/
void www_print_timeline(
Stmt *pQuery, /* Query to implement the timeline */
int tmFlags, /* Flags controlling display behavior */
@@ -129,11 +162,13 @@
){
int wikiFlags;
int mxWikiLen;
Blob comment;
+ int prevTagid = 0;
+ int suppressCnt = 0;
char zPrevDate[20];
- zPrevDate[0] = 0;
-
+
+ zPrevDate[0] = 0;
mxWikiLen = db_get_int("timeline-max-comment", 0);
if( db_get_boolean("timeline-block-markup", 0) ){
wikiFlags = WIKI_INLINE;
}else{
@@ -156,8 +191,31 @@
const char *zDate = db_column_text(pQuery, 2);
const char *zType = db_column_text(pQuery, 9);
const char *zUser = db_column_text(pQuery, 4);
const char *zTagList = db_column_text(pQuery, 10);
+ int tagid = db_column_int(pQuery, 11);
+ int commentColumn = 3; /* Column containing comment text */
+ if( tagid ){
+ if( tagid==prevTagid ){
+ if( tmFlags & TIMELINE_BRIEF ){
+ suppressCnt++;
+ continue;
+ }else{
+ commentColumn = 12;
+ }
+ }
+ }
+ prevTagid = tagid;
+ if( suppressCnt ){
+ @ <tr><td><td><td>
+ @ <small><i>... %d(suppressCnt) similar
+ @ event%s(suppressCnt>1?"s":"") omitted.</i></small></tr>
+ suppressCnt = 0;
+ }
+ if( strcmp(zType,"div")==0 ){
+ @ <tr><td colspan=3><hr></td></tr>
+ continue;
+ }
db_multi_exec("INSERT OR IGNORE INTO seen VALUES(%d)", rid);
if( memcmp(zDate, zPrevDate, 10) ){
sprintf(zPrevDate, "%.10s", zDate);
@ <tr><td colspan=3>
@@ -206,9 +264,9 @@
}
}else if( (tmFlags & TIMELINE_ARTID)!=0 ){
hyperlink_to_uuid(zUuid);
}
- db_column_blob(pQuery, 3, &comment);
+ db_column_blob(pQuery, commentColumn, &comment);
if( mxWikiLen>0 && blob_size(&comment)>mxWikiLen ){
Blob truncated;
blob_zero(&truncated);
blob_append(&truncated, blob_buffer(&comment), mxWikiLen);
@@ -247,9 +305,11 @@
@ nparent INTEGER,
@ isleaf BOOLEAN,
@ bgcolor TEXT,
@ etype TEXT,
- @ taglist TEXT
+ @ taglist TEXT,
+ @ tagid INTEGER,
+ @ short TEXT
@ )
;
db_multi_exec(zSql);
}
@@ -278,9 +338,11 @@
@ bgcolor,
@ event.type,
@ (SELECT group_concat(substr(tagname,5), ', ') FROM tag, tagxref
@ WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid
- @ AND tagxref.rid=blob.rid AND tagxref.tagtype>0)
+ @ AND tagxref.rid=blob.rid AND tagxref.tagtype>0),
+ @ tagid,
+ @ brief
@ FROM event JOIN blob
@ WHERE blob.rid=event.objid
;
if( zBase==0 ){
@@ -302,15 +364,36 @@
style_submenu_element(zMenuName, zMenuName, "%s",
url_render(pUrl, zParam, zValue, zRemove, 0));
}
+
+/*
+** zDate is a localtime date. Insert records into the
+** "timeline" table to cause <hr> to be inserted before and after
+** entries of that date.
+*/
+static void timeline_add_dividers(const char *zDate){
+ db_multi_exec(
+ "INSERT INTO timeline(rid,timestamp,etype)"
+ "VALUES(-1,datetime(%Q,'-1 second') || '.9','div')",
+ zDate
+ );
+ db_multi_exec(
+ "INSERT INTO timeline(rid,timestamp,etype)"
+ "VALUES(-2,datetime(%Q) || '.1','div')",
+ zDate
+ );
+}
+
+
/*
** WEBPAGE: timeline
**
** Query parameters:
**
** a=TIMESTAMP after this date
** b=TIMESTAMP before this date.
+** c=TIMESTAMP "circa" 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 descendants
** t=TAGID show only check-ins with the given tagid
@@ -335,11 +418,13 @@
const char *zUser = P("u"); /* All entries by this user if not NULL */
const char *zType = PD("y","all"); /* Type of events. All if NULL */
const char *zAfter = P("a"); /* Events after this time */
const char *zBefore = P("b"); /* Events before this time */
+ const char *zCirca = P("c"); /* Events near this time */
const char *zTagName = P("t"); /* Show events with this tag */
HQuery url; /* URL for various branch links */
int tagid; /* Tag ID */
+ int tmFlags; /* Timeline flags */
/* To view the timeline, must have permission to read project data.
*/
login_check_credentials();
@@ -347,8 +432,13 @@
if( zTagName ){
tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", zTagName);
}else{
tagid = 0;
+ }
+ if( zType[0]=='a' ){
+ tmFlags = TIMELINE_BRIEF;
+ }else{
+ tmFlags = 0;
}
style_header("Timeline");
login_anonymous_available();
@@ -361,9 +451,12 @@
/* 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;
+ if( p_rid && d_rid ){
+ if( p_rid!=d_rid ) p_rid = d_rid;
+ if( P("n")==0 ) nEntry = 10;
+ }
db_multi_exec(
"CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY)"
);
zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d",
@@ -370,30 +463,40 @@
p_rid ? p_rid : d_rid);
blob_appendf(&sql, " AND event.objid IN ok");
nd = 0;
if( d_rid ){
- compute_descendants(d_rid, nEntry);
+ compute_descendants(d_rid, nEntry+1);
nd = db_int(0, "SELECT count(*)-1 FROM ok");
if( nd>0 ){
db_multi_exec("%s", blob_str(&sql));
blob_appendf(&desc, "%d descendants", nd);
}
+ timeline_add_dividers(
+ db_text("1","SELECT datetime(mtime,'localtime') FROM event"
+ " WHERE objid=%d", d_rid)
+ );
db_multi_exec("DELETE FROM ok");
}
if( p_rid ){
- compute_ancestors(p_rid, nEntry);
+ compute_ancestors(p_rid, nEntry+1);
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));
}
+ if( d_rid==0 ){
+ timeline_add_dividers(
+ db_text("1","SELECT datetime(mtime,'localtime') FROM event"
+ " WHERE objid=%d", p_rid)
+ );
+ }
}
if( g.okHistory ){
blob_appendf(&desc, " of <a href='%s/info/%s'>[%.10s]</a>",
g.zBaseURL, zUuid, zUuid);
}else{
- blob_appendf(&desc, " of [%.10s]", zUuid);
+ blob_appendf(&desc, " of check-in [%.10s]", zUuid);
}
}else{
int n;
const char *zEType = "event";
@@ -443,8 +546,30 @@
url_add_parameter(&url, "b", zBefore);
}else{
zBefore = 0;
}
+ }else if( zCirca ){
+ while( isspace(zCirca[0]) ){ zCirca++; }
+ if( zCirca[0] ){
+ double rCirca = db_double(0.0, "SELECT julianday(%Q, 'utc')", zCirca);
+ Blob sql2;
+ blob_init(&sql2, blob_str(&sql), -1);
+ blob_appendf(&sql2,
+ " AND event.mtime<=%f ORDER BY event.mtime DESC LIMIT %d",
+ rCirca, (nEntry+1)/2
+ );
+ db_multi_exec("%s", blob_str(&sql2));
+ blob_reset(&sql2);
+ blob_appendf(&sql,
+ " AND event.mtime>=%f ORDER BY event.mtime ASC",
+ rCirca
+ );
+ nEntry -= (nEntry+1)/2;
+ timeline_add_dividers(zCirca);
+ url_add_parameter(&url, "c", zCirca);
+ }else{
+ zCirca = 0;
+ }
}else{
blob_appendf(&sql, " ORDER BY event.mtime DESC");
}
blob_appendf(&sql, " LIMIT %d", nEntry);
@@ -453,9 +578,9 @@
n = db_int(0, "SELECT count(*) FROM timeline");
if( n<nEntry && zAfter ){
cgi_redirect(url_render(&url, "a", 0, "b", 0));
}
- if( zAfter==0 && zBefore==0 ){
+ if( zAfter==0 && zBefore==0 && zCirca==0 ){
blob_appendf(&desc, "%d most recent %ss", n, zEType);
}else{
blob_appendf(&desc, "%d %ss", n, zEType);
}
@@ -468,8 +593,10 @@
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);
+ }else if( zCirca ){
+ blob_appendf(&desc, " occurring around %h.<br>", zCirca);
}
if( g.okHistory ){
if( zAfter || n==nEntry ){
zDate = db_text(0, "SELECT min(timestamp) FROM timeline");
@@ -505,9 +632,9 @@
blob_zero(&sql);
db_prepare(&q, "SELECT * FROM timeline ORDER BY timestamp DESC");
@ <h2>%b(&desc)</h2>
blob_reset(&desc);
- www_print_timeline(&q, 0, 0);
+ www_print_timeline(&q, tmFlags, 0);
db_finalize(&q);
@ <script>
@ var parentof = new Object();