Index: src/default_css.txt
==================================================================
--- src/default_css.txt
+++ src/default_css.txt
@@ -195,10 +195,25 @@
width: 0px;
border-left-width: 2px;
border-left-style: dotted;
background: rgba(255,255,255,0);
}
+.tl-tooltip {
+ background-color: #fecd4b;
+ color: black;
+ text-align: center;
+ padding: 5px 1em;
+ border: 1px solid black;
+ border-radius: 6px;
+ position: absolute;
+ z-index: 100;
+}
+.tl-tooltip a {
+ background-color: #fecd4b;
+ color: black;
+}
+
span.tagDsp {
font-weight: bold;
}
span.wikiError {
font-weight: bold;
Index: src/export.c
==================================================================
--- src/export.c
+++ src/export.c
@@ -1061,10 +1061,11 @@
char *zMark; /* The Git-name of the check-in */
Blob sql; /* String of SQL for part of the query */
Blob comment; /* The comment text for the check-in */
int nErr = 0; /* Number of errors */
int bPhantomOk; /* True if phantom files should be ignored */
+ char buf[24];
pMan = manifest_get(rid, CFTYPE_MANIFEST, 0);
if( pMan==0 ){
/* Must be a phantom. Return without doing anything, and in particular
** without creating a mark for this check-in. */
@@ -1134,13 +1135,15 @@
fprintf(xCmd, "commit refs/heads/%s\n", zBranch);
fossil_free(zBranch);
zMark = gitmirror_find_mark(zUuid,0,1);
fprintf(xCmd, "mark %s\n", zMark);
fossil_free(zMark);
- fprintf(xCmd, "committer %s <%s@noemail.net> %lld +0000\n",
- pMan->zUser, pMan->zUser,
+ sqlite3_snprintf(sizeof(buf), buf, "%lld",
(sqlite3_int64)((pMan->rDate-2440587.5)*86400.0)
+ );
+ fprintf(xCmd, "committer %s <%s@noemail.net> %s +0000\n",
+ pMan->zUser, pMan->zUser, buf
);
blob_init(&comment, pMan->zComment, -1);
if( blob_size(&comment)==0 ){
blob_append(&comment, "(no comment)", -1);
}
Index: src/graph.js
==================================================================
--- src/graph.js
+++ src/graph.js
@@ -72,18 +72,26 @@
style.textContent = css;
document.querySelector("head").appendChild(style);
}
amendCssOnce = 0;
}
+var tooltipObj = document.createElement("span");
+tooltipObj.className = "tl-tooltip";
+tooltipObj.style.visibility = "hidden";
+document.getElementsByClassName("content")[0].appendChild(tooltipObj);
+/* Construct that graph corresponding to the timeline-data-N object */
function TimelineGraph(tx){
var topObj = document.getElementById("timelineTable"+tx.iTableId);
amendCss(tx.circleNodes, tx.showArrowheads);
+ topObj.onclick = clickOnGraph
+ topObj.ondblclick = dblclickOnGraph
var canvasDiv;
var railPitch;
var mergeOffset;
var node, arrow, arrowSmall, line, mArrow, mLine, wArrow, wLine;
+
function initGraph(){
var parent = topObj.rows[0].cells[1];
parent.style.verticalAlign = "top";
canvasDiv = document.createElement("div");
canvasDiv.className = "tl-canvas";
@@ -163,17 +171,22 @@
n.className = "tl-"+cls;
canvasDiv.appendChild(n);
return n;
}
function absoluteY(obj){
- var top = 0;
- if( obj.offsetParent ){
- do{
- top += obj.offsetTop;
- }while( obj = obj.offsetParent );
- }
- return top;
+ var y = 0;
+ do{
+ y += obj.offsetTop;
+ }while( obj = obj.offsetParent );
+ return y;
+ }
+ function absoluteX(obj){
+ var x = 0;
+ do{
+ x += obj.offsetLeft;
+ }while( obj = obj.offsetParent );
+ return x;
}
function miLineY(p){
return p.y + node.h - mLine.w - 1;
}
function drawLine(elem,color,x0,y0,x1,y1){
@@ -185,28 +198,34 @@
y1 = y0+elem.w;
cls += "h";
}
return drawBox(cls,color,x0,y0,x1,y1);
}
- function drawUpArrow(from,to,color){
+ function drawUpArrow(from,to,color,id){
var y = to.y + node.h;
var arrowSpace = from.y - y + (!from.id || from.r!=to.r ? node.h/2 : 0);
var arw = arrowSpace < arrow.h*1.5 ? arrowSmall : arrow;
var x = to.x + (node.w-line.w)/2;
var y0 = from.y + node.h/2;
var y1 = Math.ceil(to.y + node.h + arw.h/2);
- drawLine(line,color,x,y0,null,y1);
+ var n = drawLine(line,color,x,y0,null,y1);
+ addToolTip(n,id)
x = to.x + (node.w-arw.w)/2;
- var n = drawBox(arw.cls,null,x,y);
+ n = drawBox(arw.cls,null,x,y);
if(color) n.style.borderBottomColor = color;
+ addToolTip(n,id)
}
- function drawDotted(from,to,color){
+ function drawDotted(from,to,color,id){
var x = to.x + (node.w-line.w)/2;
var y0 = from.y + node.h/2;
var y1 = Math.ceil(to.y + node.h);
var n = drawLine(dotLine,null,x,y0,null,y1)
if( color ) n.style.borderColor = color
+ addToolTip(n,id)
+ }
+ function addToolTip(n,id){
+ if( id ) n.setAttribute("data-ix",id-tx.iTopRow)
}
/* Draw thin horizontal or vertical lines representing merges */
function drawMergeLine(x0,y0,x1,y1){
drawLine(mLine,null,x0,y0,x1,y1);
}
@@ -243,37 +262,39 @@
if(e) e.style.backgroundColor = p.bg;
e = document.getElementById("md"+p.id);
if(e) e.style.backgroundColor = p.bg;
}
if( p.r<0 ) return;
- if( p.u>0 ) drawUpArrow(p,tx.rowinfo[p.u-tx.iTopRow],p.fg);
- if( p.sb>0 ) drawDotted(p,tx.rowinfo[p.sb-tx.iTopRow],p.fg);
+ if( p.u>0 ) drawUpArrow(p,tx.rowinfo[p.u-tx.iTopRow],p.fg,p.id);
+ if( p.sb>0 ) drawDotted(p,tx.rowinfo[p.sb-tx.iTopRow],p.fg,p.id);
var cls = node.cls;
if( p.hasOwnProperty('mi') && p.mi.length ) cls += " merge";
if( p.f&1 ) cls += " leaf";
var n = drawBox(cls,p.bg,p.x,p.y);
n.id = "tln"+p.id;
+ addToolTip(n,p.id)
n.onclick = clickOnNode;
+ n.ondblclick = dblclickOnNode;
n.style.zIndex = 10;
if( !tx.omitDescenders ){
if( p.u==0 ){
if( p.hasOwnProperty('mo') && p.r==p.mo ){
var ix = p.hasOwnProperty('cu') ? p.cu : p.mu;
var top = tx.rowinfo[ix-tx.iTopRow]
- drawUpArrow(p,{x: p.x, y: top.y-node.h}, p.fg);
+ drawUpArrow(p,{x: p.x, y: top.y-node.h}, p.fg, p.id);
}else if( p.y>100 ){
- drawUpArrow(p,{x: p.x, y: p.y-50}, p.fg);
+ drawUpArrow(p,{x: p.x, y: p.y-50}, p.fg, p.id);
}else{
- drawUpArrow(p,{x: p.x, y: 0},p.fg);
+ drawUpArrow(p,{x: p.x, y: 0},p.fg, p.id);
}
}
if( p.hasOwnProperty('d') ){
if( p.y + 150 >= btm ){
- drawUpArrow({x: p.x, y: btm - node.h/2},p,p.fg);
+ drawUpArrow({x: p.x, y: btm - node.h/2},p,p.fg,p.id);
}else{
- drawUpArrow({x: p.x, y: p.y+50},p,p.fg);
- drawDotted({x: p.x, y: p.y+63},{x: p.x, y: p.y+50-node.h/2},p.fg);
+ drawUpArrow({x: p.x, y: p.y+50},p,p.fg,p.id);
+ drawDotted({x: p.x, y: p.y+63},{x: p.x, y: p.y+50-node.h/2},p.fg,p.id);
}
}
}
if( p.hasOwnProperty('mo') ){
var x0 = p.x + node.w/2;
@@ -326,11 +347,11 @@
}
var y0 = p.y + (node.h-line.w)/2;
var u = tx.rowinfo[p.au[i+1]-tx.iTopRow];
if( u.id
=0; i-- ){
drawNode(tx.rowinfo[i], btm);
}
}
var selRow;
- function clickOnNode(){
+ function clickOnNode(e){
+ tooltipObj.style.display = "none"
var p = tx.rowinfo[parseInt(this.id.match(/\d+$/)[0], 10)-tx.iTopRow];
if( !selRow ){
selRow = p;
this.className += " sel";
canvasDiv.className += " sel";
@@ -407,10 +429,72 @@
location.href=tx.baseUrl + "/fdiff?v1="+selRow.h+"&v2="+p.h
}else{
location.href=tx.baseUrl + "/vdiff?from="+selRow.h+"&to="+p.h
}
}
+ e.stopPropagation()
+ }
+ function dblclickOnNode(e){
+ var p = tx.rowinfo[parseInt(this.id.match(/\d+$/)[0], 10)-tx.iTopRow];
+ window.location.href = tx.baseUrl+"/info/"+p.h
+ e.stopPropagation()
+ }
+ function findTxIndex(e){
+ /* Look at all the graph elements. If any graph elements that is near
+ ** the click-point "e" and has a "data-ix" attribute, then return
+ ** the value of that attribute. Otherwise return -1 */
+ var x = e.x + window.pageXOffset - absoluteX(canvasDiv);
+ var y = e.y + window.pageYOffset - absoluteY(canvasDiv);
+ var aNode = canvasDiv.childNodes
+ var nNode = aNode.length;
+ var i;
+ for(i=0;in.offsetLeft+n.offsetWidth+5 ) continue;
+ if( yn.offsetTop+n.offsetHeight ) continue;
+ return n.getAttribute("data-ix")
+ }
+ return -1
+ }
+ /* Compute the hyperlink for the branch graph for tx.rowinfo[ix] */
+ function branchHyperlink(ix){
+ var br = tx.rowinfo[ix].br
+ var dest = tx.baseUrl + "/timeline?r=" + encodeURIComponent(br)
+ dest += tx.fileDiff ? "&m&cf=" : "&m&c="
+ dest += encodeURIComponent(tx.rowinfo[ix].h)
+ return dest
+ }
+ function clickOnGraph(e){
+ var ix = findTxIndex(e);
+ if( ix<0 ){
+ tooltipObj.style.display = "none"
+ }else{
+ var br = tx.rowinfo[ix].br
+ var dest = branchHyperlink(ix)
+ var hbr = br.replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/"/g, """)
+ .replace(/'/g, "'");
+ tooltipObj.innerHTML = ""+hbr+""
+ tooltipObj.style.display = "inline"
+ tooltipObj.style.position = "absolute"
+ var x = e.x + 4 + window.pageXOffset
+ tooltipObj.style.left = x+"px"
+ var y = e.y + window.pageYOffset - tooltipObj.clientHeight - 4
+ tooltipObj.style.top = y+"px"
+ tooltipObj.style.visibility = "visible"
+ }
+ }
+ function dblclickOnGraph(e){
+ var ix = findTxIndex(e);
+ var dest = branchHyperlink(ix)
+ tooltipObj.style.display = "none"
+ window.location.href = dest
}
function changeDisplay(selector,value){
var x = document.getElementsByClassName(selector);
var n = x.length;
for(var i=0; i=0 && precision\
@ {"delay":%d(nDelay),"mouseover":%d(bMouseover)}
}
@
style_graph_generator();
@@ -1475,10 +1477,11 @@
** Query parameters:
**
** a=TIMEORTAG After this event
** b=TIMEORTAG Before this event
** c=TIMEORTAG "Circa" this event
+** cf=FILEHASH "Circa" the first use of the file with FILEHASH
** m=TIMEORTAG Mark this event
** n=COUNT Maximum number of events. "all" for no limit
** p=CHECKIN Parents and ancestors of CHECKIN
** d=CHECKIN Children and descendants of CHECKIN
** dp=CHECKIN The same as d=CHECKIN&p=CHECKIN
@@ -1637,10 +1640,22 @@
cookie_write_parameter("y","y",zType);
}
cookie_render();
url_initialize(&url, "timeline");
cgi_query_parameters_to_url(&url);
+
+ /* Convert the cf=FILEHASH query parameter into a c=CHECKINHASH value */
+ if( P("cf")!=0 ){
+ zCirca = db_text(0,
+ "SELECT (SELECT uuid FROM blob WHERE rid=mlink.mid)"
+ " FROM mlink, event"
+ " WHERE mlink.fid=(SELECT rid FROM blob WHERE uuid LIKE '%q%%')"
+ " AND event.objid=mlink.mid"
+ " ORDER BY event.mtime LIMIT 1",
+ P("cf")
+ );
+ }
/* Convert r=TAG to t=TAG&rel. */
if( zBrName && !related ){
cgi_delete_query_parameter("r");
cgi_set_query_parameter("t", zBrName);
@@ -1994,12 +2009,13 @@
zYearMonth = timeline_expand_datetime(zYearMonth);
blob_append_sql(&cond, " AND %Q=strftime('%%Y-%%m',event.mtime) ",
zYearMonth);
}
else if( zYearWeek ){
+ char *z;
zYearWeek = timeline_expand_datetime(zYearWeek);
- char *z = db_text(0, "SELECT strftime('%%Y-%%W',%Q)", zYearWeek);
+ z = db_text(0, "SELECT strftime('%%Y-%%W',%Q)", zYearWeek);
if( z && z[0] ){
zYearWeekStart = db_text(0, "SELECT date(%Q,'-6 days','weekday 1')",
zYearWeek);
zYearWeek = z;
}else{
Index: src/user.c
==================================================================
--- src/user.c
+++ src/user.c
@@ -498,10 +498,11 @@
** (8) Check if the user can be extracted from the remote URL.
**
** The user name is stored in g.zLogin. The uid is in g.userUid.
*/
void user_select(void){
+ UrlData url;
if( g.userUid ) return;
if( g.zLogin ){
if( attempt_user(g.zLogin)==0 ){
fossil_fatal("no such user: %s", g.zLogin);
}else{
@@ -519,12 +520,13 @@
if( attempt_user(fossil_getenv("LOGNAME")) ) return;
if( attempt_user(fossil_getenv("USERNAME")) ) return;
- url_parse(0, 0);
- if( g.url.user && attempt_user(g.url.user) ) return;
+ memset(&url, 0, sizeof(url));
+ url_parse_local(0, 0, &url);
+ if( url.user && attempt_user(url.user) ) return;
fossil_print(
"Cannot figure out who you are! Consider using the --user\n"
"command line option, setting your USER environment variable,\n"
"or setting a default user with \"fossil user default USER\".\n"
Index: www/changes.wiki
==================================================================
--- www/changes.wiki
+++ www/changes.wiki
@@ -8,10 +8,12 @@
* Improved handling of relative hyperlinks on the
[/help?cmd=/artifact|/artifact] pages for wiki. For example,
hyperlinks and the lizard <img> now work correctly
for both [/artifact/2ff24ab0887cf522] and
[/doc/0d7ac90d575004c2415/www/index.wiki].
+ * Enhancements to the timeline graph layout, to show more information
+ with less clutter.
* Many documentation enhancements.
* For the "[/help?cmd=update|fossil update]" and
"[/help?cmd=checkout|fossil checkout]" commands, if a
managed file is removed because it is no longer part of the target
check-in and the directory containing the file is empty after the
@@ -27,26 +29,29 @@
* Added the [/help?cmd=/secureraw|/secureraw] page that requires the
complete SHA1 or SHA3 hash, not just a prefix, before it will deliver
content.
* Accept purely numeric ISO8601 date/time strings as long as they
do not conflict with a hash. Example: "20190510134217" instead of
- "2019-05-10 13:42:17". This is very useful for query parameters.
+ "2019-05-10 13:42:17". This helps keep URLs shorter and less
+ complicated
* Support both "1)" and "1." for numbered lists in markdown, as
commonmark does.
* The sync and clone HTTP requests omit the extra /xfer path element
from the end of the request URI. All servers since 2010 know that
the HTTP request is for a sync or clone from the mimetype so the
extra path element is not needed.
- * If an automatic sync gets a 301 or 302 redirect request, then update
+ * If an automatic sync gets a permanent redirect request, then update
the saved remote URL to the new address.
* Temporary filenames (for example used for external "diff" commands)
try to preserve the suffix of the original file.
* Added the [/help?cmd=/thisdayinhistory|/thisdayinhistory] web page.
* Enhanced parsing of [/help?cmd=/timeline|/timeline] query parameters
"ymd=", "ym=", and "yw=". All arguments are option (in which case they
default to the current time) and all accept ISO8601 date/times without
punctuation.
+ * Automatically disapprove pending moderation requests for a user when
+ that user is deleted. This helps in dealing with spam-bots.
* Improvements to the "Capability Summary" section in the
[/help?cmd=/secaudit0|Security Audit] web-page.
Changes for Version 2.8 (2019-02-20)