Fossil SCM

Merge all the recent tool-tip experiments into trunk.

drh 2019-06-03 13:30 trunk merge
Commit 6908832cf4cf8bd8a35b584f0e985718fc2b979e71bf7c260289f3fad43f82ef
--- src/configure.c
+++ src/configure.c
@@ -94,12 +94,17 @@
9494
{ "logo-mimetype", CONFIGSET_SKIN },
9595
{ "logo-image", CONFIGSET_SKIN },
9696
{ "background-mimetype", CONFIGSET_SKIN },
9797
{ "background-image", CONFIGSET_SKIN },
9898
{ "timeline-block-markup", CONFIGSET_SKIN },
99
+ { "timeline-date-format", CONFIGSET_SKIN },
100
+ { "timeline-dwelltime", CONFIGSET_SKIN },
101
+ { "timeline-closetime", CONFIGSET_SKIN },
99102
{ "timeline-max-comment", CONFIGSET_SKIN },
100103
{ "timeline-plaintext", CONFIGSET_SKIN },
104
+ { "timeline-truncate-at-blank", CONFIGSET_SKIN },
105
+ { "timeline-utc", CONFIGSET_SKIN },
101106
{ "adunit", CONFIGSET_SKIN },
102107
{ "adunit-omit-if-admin", CONFIGSET_SKIN },
103108
{ "adunit-omit-if-user", CONFIGSET_SKIN },
104109
{ "sitemap-docidx", CONFIGSET_SKIN },
105110
{ "sitemap-download", CONFIGSET_SKIN },
106111
107112
ADDED src/copybtn.js
--- src/configure.c
+++ src/configure.c
@@ -94,12 +94,17 @@
94 { "logo-mimetype", CONFIGSET_SKIN },
95 { "logo-image", CONFIGSET_SKIN },
96 { "background-mimetype", CONFIGSET_SKIN },
97 { "background-image", CONFIGSET_SKIN },
98 { "timeline-block-markup", CONFIGSET_SKIN },
 
 
 
99 { "timeline-max-comment", CONFIGSET_SKIN },
100 { "timeline-plaintext", CONFIGSET_SKIN },
 
 
101 { "adunit", CONFIGSET_SKIN },
102 { "adunit-omit-if-admin", CONFIGSET_SKIN },
103 { "adunit-omit-if-user", CONFIGSET_SKIN },
104 { "sitemap-docidx", CONFIGSET_SKIN },
105 { "sitemap-download", CONFIGSET_SKIN },
106
107 DDED src/copybtn.js
--- src/configure.c
+++ src/configure.c
@@ -94,12 +94,17 @@
94 { "logo-mimetype", CONFIGSET_SKIN },
95 { "logo-image", CONFIGSET_SKIN },
96 { "background-mimetype", CONFIGSET_SKIN },
97 { "background-image", CONFIGSET_SKIN },
98 { "timeline-block-markup", CONFIGSET_SKIN },
99 { "timeline-date-format", CONFIGSET_SKIN },
100 { "timeline-dwelltime", CONFIGSET_SKIN },
101 { "timeline-closetime", CONFIGSET_SKIN },
102 { "timeline-max-comment", CONFIGSET_SKIN },
103 { "timeline-plaintext", CONFIGSET_SKIN },
104 { "timeline-truncate-at-blank", CONFIGSET_SKIN },
105 { "timeline-utc", CONFIGSET_SKIN },
106 { "adunit", CONFIGSET_SKIN },
107 { "adunit-omit-if-admin", CONFIGSET_SKIN },
108 { "adunit-omit-if-user", CONFIGSET_SKIN },
109 { "sitemap-docidx", CONFIGSET_SKIN },
110 { "sitemap-download", CONFIGSET_SKIN },
111
112 DDED src/copybtn.js
--- a/src/copybtn.js
+++ b/src/copybtn.js
@@ -0,0 +1,19 @@
1
+/* Manage "Copy Buttons" linkex.style.position = 'absolute';
2
+ x.style.left = '-9999px';
3
+ x.value = text;
4
+x.select();
5
+ try{
6
+}catch(err){}
7
+}
8
+onContentLoadedtextAreatextArea.style.position = 'fixed';
9
+ textArea.style.top = 0;
10
+ textArea.style.left = 0;
11
+ textArea.style.width = le.padding = 0;
12
+ textArea.style.border = 'none';
13
+ textArea.style.outline = 'none';
14
+ textArea.style.boxShadow = 'none';
15
+ textArea.style.background = 'transparent';
16
+ textAreatextArea);
17
+ tidButton"
18
+** data-copytarget="idButton;
19
+ ById(id
--- a/src/copybtn.js
+++ b/src/copybtn.js
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/src/copybtn.js
+++ b/src/copybtn.js
@@ -0,0 +1,19 @@
1 /* Manage "Copy Buttons" linkex.style.position = 'absolute';
2 x.style.left = '-9999px';
3 x.value = text;
4 x.select();
5 try{
6 }catch(err){}
7 }
8 onContentLoadedtextAreatextArea.style.position = 'fixed';
9 textArea.style.top = 0;
10 textArea.style.left = 0;
11 textArea.style.width = le.padding = 0;
12 textArea.style.border = 'none';
13 textArea.style.outline = 'none';
14 textArea.style.boxShadow = 'none';
15 textArea.style.background = 'transparent';
16 textAreatextArea);
17 tidButton"
18 ** data-copytarget="idButton;
19 ById(id
--- src/default_css.txt
+++ src/default_css.txt
@@ -195,10 +195,20 @@
195195
width: 0px;
196196
border-left-width: 2px;
197197
border-left-style: dotted;
198198
background: rgba(255,255,255,0);
199199
}
200
+.tl-tooltip {
201
+ text-align: center;
202
+ padding: 5px 1em;
203
+ border: 1px solid black;
204
+ border-radius: 6px;
205
+ position: absolute;
206
+ z-index: 100;
207
+ box-shadow: 2px 2px 6px rgba(0, 0, 0, 0.75);
208
+}
209
+
200210
span.tagDsp {
201211
font-weight: bold;
202212
}
203213
span.wikiError {
204214
font-weight: bold;
@@ -757,5 +767,19 @@
757767
background-color: #ffb;
758768
}
759769
label {
760770
white-space: nowrap;
761771
}
772
+.copy-button {
773
+ display: inline-block;
774
+ width: 14px;
775
+ height: 14px;
776
+ margin: -2px 0 0 0;
777
+ padding: 0;
778
+ border: 0;
779
+ vertical-align: middle;
780
+//Note: the mkcss utility does not support line breaks in data URIs.
781
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath style='fill: black; opacity:0' d='M 14 14 H 0 V 0 h 14 v 14 z'/%3E%3Cpath style='fill:rgb(240,240,240)' d='M 1 0 h 6.6 l 2 2 h 1 l 3.4 3.4 v 8.6 h -10 v -2 h -3 z'/%3E%3Cpath style='fill:rgb(64,64,64)' d='M 2 1 h 5 l 3 3 v 7 h -8 z'/%3E%3Cpath style='fill:rgb(248,248,248)' d='M 3 2 h 3.6 l 2.4 2.4 v 5.6 h -6 z'/%3E%3Cpath style='fill:rgb(80,128,208)' d='M 4 5 h 4 v 1 h -4 z m 0 2 h 4 v 1 h -4 z'/%3E%3Cpath style='fill:rgb(64,64,64)' d='M 5 3 h 5 l 3 3 v 7 h -8 z'/%3E%3Cpath style='fill:rgb(248,248,248)' d='M 10 4.4 v 1.6 h 1.6 z m -4 -0.6 h 3 v 3 h -3 z m 0 3 h 6 v 5.4 h -6 z'/%3E%3Cpath style='fill:rgb(80,128,208)' d='M 7 8 h 4 v 1 h -4 z m 0 2 h 4 v 1 h -4 z'/%3E%3C/svg%3E");
782
+ background-repeat: no-repeat;
783
+ background-position: center;
784
+ cursor: pointer;
785
+}
762786
--- src/default_css.txt
+++ src/default_css.txt
@@ -195,10 +195,20 @@
195 width: 0px;
196 border-left-width: 2px;
197 border-left-style: dotted;
198 background: rgba(255,255,255,0);
199 }
 
 
 
 
 
 
 
 
 
 
200 span.tagDsp {
201 font-weight: bold;
202 }
203 span.wikiError {
204 font-weight: bold;
@@ -757,5 +767,19 @@
757 background-color: #ffb;
758 }
759 label {
760 white-space: nowrap;
761 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
762
--- src/default_css.txt
+++ src/default_css.txt
@@ -195,10 +195,20 @@
195 width: 0px;
196 border-left-width: 2px;
197 border-left-style: dotted;
198 background: rgba(255,255,255,0);
199 }
200 .tl-tooltip {
201 text-align: center;
202 padding: 5px 1em;
203 border: 1px solid black;
204 border-radius: 6px;
205 position: absolute;
206 z-index: 100;
207 box-shadow: 2px 2px 6px rgba(0, 0, 0, 0.75);
208 }
209
210 span.tagDsp {
211 font-weight: bold;
212 }
213 span.wikiError {
214 font-weight: bold;
@@ -757,5 +767,19 @@
767 background-color: #ffb;
768 }
769 label {
770 white-space: nowrap;
771 }
772 .copy-button {
773 display: inline-block;
774 width: 14px;
775 height: 14px;
776 margin: -2px 0 0 0;
777 padding: 0;
778 border: 0;
779 vertical-align: middle;
780 //Note: the mkcss utility does not support line breaks in data URIs.
781 background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath style='fill: black; opacity:0' d='M 14 14 H 0 V 0 h 14 v 14 z'/%3E%3Cpath style='fill:rgb(240,240,240)' d='M 1 0 h 6.6 l 2 2 h 1 l 3.4 3.4 v 8.6 h -10 v -2 h -3 z'/%3E%3Cpath style='fill:rgb(64,64,64)' d='M 2 1 h 5 l 3 3 v 7 h -8 z'/%3E%3Cpath style='fill:rgb(248,248,248)' d='M 3 2 h 3.6 l 2.4 2.4 v 5.6 h -6 z'/%3E%3Cpath style='fill:rgb(80,128,208)' d='M 4 5 h 4 v 1 h -4 z m 0 2 h 4 v 1 h -4 z'/%3E%3Cpath style='fill:rgb(64,64,64)' d='M 5 3 h 5 l 3 3 v 7 h -8 z'/%3E%3Cpath style='fill:rgb(248,248,248)' d='M 10 4.4 v 1.6 h 1.6 z m -4 -0.6 h 3 v 3 h -3 z m 0 3 h 6 v 5.4 h -6 z'/%3E%3Cpath style='fill:rgb(80,128,208)' d='M 7 8 h 4 v 1 h -4 z m 0 2 h 4 v 1 h -4 z'/%3E%3C/svg%3E");
782 background-repeat: no-repeat;
783 background-position: center;
784 cursor: pointer;
785 }
786
+266 -21
--- src/graph.js
+++ src/graph.js
@@ -1,6 +1,9 @@
11
/* This module contains javascript needed to render timeline graphs in Fossil.
2
+**
3
+** There can be multiple graphs on a single webpage, but this script is only
4
+** loaded once.
25
**
36
** Prior to sourcing this script, there should be a separate
47
** <script type='application/json' id='timeline-data-NN'> for each graph,
58
** each containing JSON like this:
69
**
@@ -14,10 +17,13 @@
1417
** "omitDescenders": BOOLEAN, // Omit ancestor lines off bottom of screen
1518
** "fileDiff": BOOLEAN, // True for file diff. False for check-in
1619
** "scrollToSelect": BOOLEAN, // Scroll to selection on first render
1720
** "nrail": INTEGER, // Number of vertical "rails"
1821
** "baseUrl": TEXT, // Top-level URL
22
+** "dwellTimeout": INTEGER, // Tooltip show delay in milliseconds
23
+** "closeTimeout": INTEGER, // Tooltip close delay in milliseconds
24
+** "hashDigits": INTEGER, // Limit of tooltip hashes ("hash-digits")
1925
** "rowinfo": ROWINFO-ARRAY }
2026
**
2127
** The rowinfo field is an array of structures, one per entry in the timeline,
2228
** where each structure has the following fields:
2329
**
@@ -55,10 +61,15 @@
5561
** merges.
5662
** ci: "cherrypick-in". Like "mi" except for cherrypick merges.
5763
** omitted if there are no cherrypick merges.
5864
** h: The artifact hash of the object being graphed
5965
*/
66
+
67
+/* The amendCss() function does a one-time change to the CSS to account
68
+** for the "circleNodes" and "showArrowheads" settings. Do this change
69
+** only once, even if there are multiple graphs being rendered.
70
+*/
6071
var amendCssOnce = 1; // Only change the CSS one time
6172
function amendCss(circleNodes,showArrowheads){
6273
if( !amendCssOnce ) return;
6374
var css = "";
6475
if( circleNodes ){
@@ -73,17 +84,134 @@
7384
document.querySelector("head").appendChild(style);
7485
}
7586
amendCssOnce = 0;
7687
}
7788
89
+/* The <span> object that holds the tooltip */
90
+var tooltipObj = document.createElement("span");
91
+tooltipObj.className = "tl-tooltip";
92
+tooltipObj.style.display = "none";
93
+document.getElementsByClassName("content")[0].appendChild(tooltipObj);
94
+tooltipObj.onmouseenter = function(){stopCloseTimer();}
95
+tooltipObj.onmouseleave = function(){
96
+ if (tooltipInfo.ixActive != -1) resumeCloseTimer();
97
+};
98
+
99
+/* State information for the tooltip popup and its timers */
100
+window.tooltipInfo = {
101
+ dwellTimeout: 250, /* The tooltip dwell timeout. */
102
+ closeTimeout: 3000, /* The tooltip close timeout. */
103
+ hashDigits: 16, /* Limit of tooltip hashes ("hash-digits"). */
104
+ idTimer: 0, /* The tooltip dwell timer id. */
105
+ idTimerClose: 0, /* The tooltip close timer id. */
106
+ ixHover: -1, /* The id of the element with the mouse. */
107
+ ixActive: -1, /* The id of the element with the tooltip. */
108
+ nodeHover: null, /* Graph node under mouse when ixHover==-2 */
109
+ posX: 0, posY: 0 /* The last mouse position. */
110
+};
111
+
112
+/* Functions used to control the tooltip popup and its timer */
113
+function hideGraphTooltip(){
114
+ stopCloseTimer();
115
+ tooltipObj.style.display = "none";
116
+ tooltipInfo.ixActive = -1;
117
+}
118
+document.body.onunload = hideGraphTooltip
119
+function stopDwellTimer(){
120
+ if (tooltipInfo.idTimer != 0) {
121
+ clearTimeout(tooltipInfo.idTimer);
122
+ tooltipInfo.idTimer = 0;
123
+ }
124
+}
125
+function resumeCloseTimer(){
126
+ /* This timer must be stopped explicitly to reset the elapsed timeout. */
127
+ if(tooltipInfo.idTimerClose == 0 && tooltipInfo.closeTimeout>0) {
128
+ tooltipInfo.idTimerClose = setTimeout(function(){
129
+ tooltipInfo.idTimerClose = 0;
130
+ hideGraphTooltip();
131
+ },tooltipInfo.closeTimeout);
132
+ }
133
+}
134
+function stopCloseTimer(){
135
+ if (tooltipInfo.idTimerClose != 0) {
136
+ clearTimeout(tooltipInfo.idTimerClose);
137
+ tooltipInfo.idTimerClose = 0;
138
+ }
139
+}
140
+
141
+/* Construct that graph corresponding to the timeline-data-N object that
142
+** is passed in by the tx parameter */
78143
function TimelineGraph(tx){
79144
var topObj = document.getElementById("timelineTable"+tx.iTableId);
80145
amendCss(tx.circleNodes, tx.showArrowheads);
146
+ tooltipInfo.dwellTimeout = tx.dwellTimeout
147
+ tooltipInfo.closeTimeout = tx.closeTimeout
148
+ tooltipInfo.hashDigits = tx.hashDigits
149
+ topObj.onclick = clickOnGraph
150
+ topObj.ondblclick = dblclickOnGraph
151
+ topObj.onmousemove = function(e) {
152
+ var ix = findTxIndex(e);
153
+ topObj.style.cursor = (ix<0) ? "" : "pointer"
154
+ /* Keep the already visible tooltip at a constant position, as long as the
155
+ ** mouse is over the same element. */
156
+ if(tooltipObj.style.display != "none"){
157
+ if(ix == tooltipInfo.ixHover) return;
158
+ }
159
+ /* The tooltip is either not visible, or the mouse is over a different
160
+ ** element, so clear the dwell timer, and record the new element id and
161
+ ** mouse position. */
162
+ stopDwellTimer();
163
+ if(ix >= 0){
164
+ tooltipInfo.ixHover = ix;
165
+ tooltipInfo.posX = e.clientX;
166
+ tooltipInfo.posY = e.clientY;
167
+ stopCloseTimer();
168
+ if(tooltipInfo.dwellTimeout>0){
169
+ tooltipInfo.idTimer = setTimeout(function() {
170
+ tooltipInfo.idTimer = 0;
171
+ stopCloseTimer();
172
+ showGraphTooltip();
173
+ },tooltipInfo.dwellTimeout);
174
+ }
175
+ }else{
176
+ /* The mouse is not over an element with a tooltip */
177
+ tooltipInfo.ixHover = -1;
178
+ resumeCloseTimer();
179
+ }
180
+ };
181
+ topObj.onmouseleave = function(e) {
182
+ /* Hide the tooltip if the mouse is outside the "timelineTableN" element,
183
+ ** and outside the tooltip. */
184
+ if(e.relatedTarget && e.relatedTarget != tooltipObj){
185
+ tooltipInfo.ixHover = -1;
186
+ hideGraphTooltip();
187
+ stopDwellTimer();
188
+ stopCloseTimer();
189
+ }
190
+ };
191
+ function nodeHover(e){
192
+ /* Invoked by mousemove events over a graph node */
193
+ e.stopPropagation()
194
+ if(tooltipInfo.ixHover==-2) return
195
+ tooltipInfo.ixHover = -2
196
+ tooltipInfo.posX = e.clientX
197
+ tooltipInfo.posY = e.clientY
198
+ tooltipInfo.nodeHover = this
199
+ stopCloseTimer();
200
+ if(tooltipInfo.dwellTimeout>0){
201
+ tooltipInfo.idTimer = setTimeout(function() {
202
+ tooltipInfo.idTimer = 0;
203
+ stopCloseTimer();
204
+ showGraphTooltip();
205
+ },tooltipInfo.dwellTimeout);
206
+ }
207
+ }
81208
var canvasDiv;
82209
var railPitch;
83210
var mergeOffset;
84211
var node, arrow, arrowSmall, line, mArrow, mLine, wArrow, wLine;
212
+
85213
function initGraph(){
86214
var parent = topObj.rows[0].cells[1];
87215
parent.style.verticalAlign = "top";
88216
canvasDiv = document.createElement("div");
89217
canvasDiv.className = "tl-canvas";
@@ -163,17 +291,22 @@
163291
n.className = "tl-"+cls;
164292
canvasDiv.appendChild(n);
165293
return n;
166294
}
167295
function absoluteY(obj){
168
- var top = 0;
169
- if( obj.offsetParent ){
170
- do{
171
- top += obj.offsetTop;
172
- }while( obj = obj.offsetParent );
173
- }
174
- return top;
296
+ var y = 0;
297
+ do{
298
+ y += obj.offsetTop;
299
+ }while( obj = obj.offsetParent );
300
+ return y;
301
+ }
302
+ function absoluteX(obj){
303
+ var x = 0;
304
+ do{
305
+ x += obj.offsetLeft;
306
+ }while( obj = obj.offsetParent );
307
+ return x;
175308
}
176309
function miLineY(p){
177310
return p.y + node.h - mLine.w - 1;
178311
}
179312
function drawLine(elem,color,x0,y0,x1,y1){
@@ -185,28 +318,34 @@
185318
y1 = y0+elem.w;
186319
cls += "h";
187320
}
188321
return drawBox(cls,color,x0,y0,x1,y1);
189322
}
190
- function drawUpArrow(from,to,color){
323
+ function drawUpArrow(from,to,color,id){
191324
var y = to.y + node.h;
192325
var arrowSpace = from.y - y + (!from.id || from.r!=to.r ? node.h/2 : 0);
193326
var arw = arrowSpace < arrow.h*1.5 ? arrowSmall : arrow;
194327
var x = to.x + (node.w-line.w)/2;
195328
var y0 = from.y + node.h/2;
196329
var y1 = Math.ceil(to.y + node.h + arw.h/2);
197
- drawLine(line,color,x,y0,null,y1);
330
+ var n = drawLine(line,color,x,y0,null,y1);
331
+ addToolTip(n,id)
198332
x = to.x + (node.w-arw.w)/2;
199
- var n = drawBox(arw.cls,null,x,y);
333
+ n = drawBox(arw.cls,null,x,y);
200334
if(color) n.style.borderBottomColor = color;
335
+ addToolTip(n,id)
201336
}
202
- function drawDotted(from,to,color){
337
+ function drawDotted(from,to,color,id){
203338
var x = to.x + (node.w-line.w)/2;
204339
var y0 = from.y + node.h/2;
205340
var y1 = Math.ceil(to.y + node.h);
206341
var n = drawLine(dotLine,null,x,y0,null,y1)
207342
if( color ) n.style.borderColor = color
343
+ addToolTip(n,id)
344
+ }
345
+ function addToolTip(n,id){
346
+ if( id ) n.setAttribute("data-ix",id-tx.iTopRow)
208347
}
209348
/* Draw thin horizontal or vertical lines representing merges */
210349
function drawMergeLine(x0,y0,x1,y1){
211350
drawLine(mLine,null,x0,y0,x1,y1);
212351
}
@@ -243,37 +382,39 @@
243382
if(e) e.style.backgroundColor = p.bg;
244383
e = document.getElementById("md"+p.id);
245384
if(e) e.style.backgroundColor = p.bg;
246385
}
247386
if( p.r<0 ) return;
248
- if( p.u>0 ) drawUpArrow(p,tx.rowinfo[p.u-tx.iTopRow],p.fg);
249
- if( p.sb>0 ) drawDotted(p,tx.rowinfo[p.sb-tx.iTopRow],p.fg);
387
+ if( p.u>0 ) drawUpArrow(p,tx.rowinfo[p.u-tx.iTopRow],p.fg,p.id);
388
+ if( p.sb>0 ) drawDotted(p,tx.rowinfo[p.sb-tx.iTopRow],p.fg,p.id);
250389
var cls = node.cls;
251390
if( p.hasOwnProperty('mi') && p.mi.length ) cls += " merge";
252391
if( p.f&1 ) cls += " leaf";
253392
var n = drawBox(cls,p.bg,p.x,p.y);
254393
n.id = "tln"+p.id;
255394
n.onclick = clickOnNode;
395
+ n.ondblclick = dblclickOnNode;
396
+ n.onmousemove = nodeHover;
256397
n.style.zIndex = 10;
257398
if( !tx.omitDescenders ){
258399
if( p.u==0 ){
259400
if( p.hasOwnProperty('mo') && p.r==p.mo ){
260401
var ix = p.hasOwnProperty('cu') ? p.cu : p.mu;
261402
var top = tx.rowinfo[ix-tx.iTopRow]
262
- drawUpArrow(p,{x: p.x, y: top.y-node.h}, p.fg);
403
+ drawUpArrow(p,{x: p.x, y: top.y-node.h}, p.fg, p.id);
263404
}else if( p.y>100 ){
264
- drawUpArrow(p,{x: p.x, y: p.y-50}, p.fg);
405
+ drawUpArrow(p,{x: p.x, y: p.y-50}, p.fg, p.id);
265406
}else{
266
- drawUpArrow(p,{x: p.x, y: 0},p.fg);
407
+ drawUpArrow(p,{x: p.x, y: 0},p.fg, p.id);
267408
}
268409
}
269410
if( p.hasOwnProperty('d') ){
270411
if( p.y + 150 >= btm ){
271
- drawUpArrow({x: p.x, y: btm - node.h/2},p,p.fg);
412
+ drawUpArrow({x: p.x, y: btm - node.h/2},p,p.fg,p.id);
272413
}else{
273
- drawUpArrow({x: p.x, y: p.y+50},p,p.fg);
274
- drawDotted({x: p.x, y: p.y+63},{x: p.x, y: p.y+50-node.h/2},p.fg);
414
+ drawUpArrow({x: p.x, y: p.y+50},p,p.fg,p.id);
415
+ drawDotted({x: p.x, y: p.y+63},{x: p.x, y: p.y+50-node.h/2},p.fg,p.id);
275416
}
276417
}
277418
}
278419
if( p.hasOwnProperty('mo') ){
279420
var x0 = p.x + node.w/2;
@@ -326,11 +467,11 @@
326467
}
327468
var y0 = p.y + (node.h-line.w)/2;
328469
var u = tx.rowinfo[p.au[i+1]-tx.iTopRow];
329470
if( u.id<p.id ){
330471
drawLine(line,u.fg,x0,y0,x1,null);
331
- drawUpArrow(p,u,u.fg);
472
+ drawUpArrow(p,u,u.fg,u.id);
332473
}else{
333474
var y1 = u.y + (node.h-line.w)/2;
334475
drawLine(wLine,u.fg,x0,y0,x1,null);
335476
drawLine(wLine,u.fg,x1-line.w,y0,null,y1+line.w);
336477
drawLine(wLine,u.fg,x1,y1,u.x-wArrow.w/2,null);
@@ -390,11 +531,12 @@
390531
for( var i=tx.rowinfo.length-1; i>=0; i-- ){
391532
drawNode(tx.rowinfo[i], btm);
392533
}
393534
}
394535
var selRow;
395
- function clickOnNode(){
536
+ function clickOnNode(e){
537
+ hideGraphTooltip()
396538
var p = tx.rowinfo[parseInt(this.id.match(/\d+$/)[0], 10)-tx.iTopRow];
397539
if( !selRow ){
398540
selRow = p;
399541
this.className += " sel";
400542
canvasDiv.className += " sel";
@@ -407,10 +549,113 @@
407549
location.href=tx.baseUrl + "/fdiff?v1="+selRow.h+"&v2="+p.h
408550
}else{
409551
location.href=tx.baseUrl + "/vdiff?from="+selRow.h+"&to="+p.h
410552
}
411553
}
554
+ e.stopPropagation()
555
+ }
556
+ function dblclickOnNode(e){
557
+ var p = tx.rowinfo[parseInt(this.id.match(/\d+$/)[0], 10)-tx.iTopRow];
558
+ window.location.href = tx.baseUrl+"/info/"+p.h
559
+ e.stopPropagation()
560
+ }
561
+ function findTxIndex(e){
562
+ /* Look at all the graph elements. If any graph elements that is near
563
+ ** the click-point "e" and has a "data-ix" attribute, then return
564
+ ** the value of that attribute. Otherwise return -1 */
565
+ var x = e.clientX + window.pageXOffset - absoluteX(canvasDiv);
566
+ var y = e.clientY + window.pageYOffset - absoluteY(canvasDiv);
567
+ var aNode = canvasDiv.childNodes
568
+ var nNode = aNode.length;
569
+ var i;
570
+ for(i=0;i<nNode;i++){
571
+ var n = aNode[i]
572
+ if( !n.hasAttribute("data-ix") ) continue;
573
+ if( x<n.offsetLeft-5 ) continue;
574
+ if( x>n.offsetLeft+n.offsetWidth+5 ) continue;
575
+ if( y<n.offsetTop-5 ) continue;
576
+ if( y>n.offsetTop+n.offsetHeight ) continue;
577
+ return n.getAttribute("data-ix")
578
+ }
579
+ return -1
580
+ }
581
+ /* Compute the hyperlink for the branch graph for tx.rowinfo[ix] */
582
+ function branchHyperlink(ix){
583
+ var br = tx.rowinfo[ix].br
584
+ var dest = tx.baseUrl + "/timeline?r=" + encodeURIComponent(br)
585
+ dest += tx.fileDiff ? "&m&cf=" : "&m&c="
586
+ dest += encodeURIComponent(tx.rowinfo[ix].h)
587
+ return dest
588
+ }
589
+ function clickOnGraph(e){
590
+ tooltipInfo.ixHover = findTxIndex(e);
591
+ tooltipInfo.posX = e.clientX;
592
+ tooltipInfo.posY = e.clientY;
593
+ showGraphTooltip();
594
+ }
595
+ function showGraphTooltip(){
596
+ var html = null
597
+ var ix = -1
598
+ if( tooltipInfo.ixHover==-2 ){
599
+ ix = parseInt(tooltipInfo.nodeHover.id.match(/\d+$/)[0],10)-tx.iTopRow
600
+ var h = tx.rowinfo[ix].h
601
+ var dest = tx.baseUrl + "/info/" + h
602
+ h = h.slice(0,tooltipInfo.hashDigits); // Assume single-byte characters.
603
+ if( tx.fileDiff ){
604
+ html = "artifact <a id=\"tooltip-link\" href=\""+dest+"\">"+h+"</a>"
605
+ }else{
606
+ html = "check-in <a id=\"tooltip-link\" href=\""+dest+"\">"+h+"</a>"
607
+ }
608
+ tooltipInfo.ixActive = -2;
609
+ }else if( tooltipInfo.ixHover>=0 ){
610
+ ix = tooltipInfo.ixHover
611
+ var br = tx.rowinfo[ix].br
612
+ var dest = branchHyperlink(ix)
613
+ var hbr = br.replace(/&/g, "&amp;")
614
+ .replace(/</g, "&lt;")
615
+ .replace(/>/g, "&gt;")
616
+ .replace(/"/g, "&quot;")
617
+ .replace(/'/g, "&#039;");
618
+ html = "branch <a id=\"tooltip-link\" href=\""+dest+"\">"+hbr+"</a>"
619
+ tooltipInfo.ixActive = ix;
620
+ }
621
+ if( html ){
622
+ /* Setup while hidden, to ensure proper dimensions. */
623
+ var s = getComputedStyle(document.body)
624
+ if( tx.rowinfo[ix].bg.length ){
625
+ tooltipObj.style.backgroundColor = tx.rowinfo[ix].bg
626
+ }else{
627
+ tooltipObj.style.backgroundColor = s.getPropertyValue('background-color')
628
+ }
629
+ tooltipObj.style.borderColor =
630
+ tooltipObj.style.color = s.getPropertyValue('color')
631
+ tooltipObj.style.visibility = "hidden"
632
+ tooltipObj.innerHTML = html
633
+ tooltipObj.appendChild(document.createTextNode(' '));
634
+ tooltipObj.appendChild(
635
+ makeCopyButton("tooltip-copybtn","tooltip-link",0));
636
+ tooltipObj.style.display = "inline"
637
+ tooltipObj.style.position = "absolute"
638
+ var x = tooltipInfo.posX + 4 + window.pageXOffset
639
+ - absoluteX(tooltipObj.offsetParent)
640
+ tooltipObj.style.left = x+"px"
641
+ var y = tooltipInfo.posY + window.pageYOffset
642
+ - tooltipObj.clientHeight - 4
643
+ - absoluteY(tooltipObj.offsetParent)
644
+ tooltipObj.style.top = y+"px"
645
+ tooltipObj.style.visibility = "visible"
646
+ }else{
647
+ hideGraphTooltip()
648
+ }
649
+ }
650
+ function dblclickOnGraph(e){
651
+ var ix = findTxIndex(e);
652
+ hideGraphTooltip()
653
+ if( ix>=0 ){
654
+ var dest = branchHyperlink(ix)
655
+ window.location.href = dest
656
+ }
412657
}
413658
function changeDisplay(selector,value){
414659
var x = document.getElementsByClassName(selector);
415660
var n = x.length;
416661
for(var i=0; i<n; i++) {x[i].style.display = value;}
417662
--- src/graph.js
+++ src/graph.js
@@ -1,6 +1,9 @@
1 /* This module contains javascript needed to render timeline graphs in Fossil.
 
 
 
2 **
3 ** Prior to sourcing this script, there should be a separate
4 ** <script type='application/json' id='timeline-data-NN'> for each graph,
5 ** each containing JSON like this:
6 **
@@ -14,10 +17,13 @@
14 ** "omitDescenders": BOOLEAN, // Omit ancestor lines off bottom of screen
15 ** "fileDiff": BOOLEAN, // True for file diff. False for check-in
16 ** "scrollToSelect": BOOLEAN, // Scroll to selection on first render
17 ** "nrail": INTEGER, // Number of vertical "rails"
18 ** "baseUrl": TEXT, // Top-level URL
 
 
 
19 ** "rowinfo": ROWINFO-ARRAY }
20 **
21 ** The rowinfo field is an array of structures, one per entry in the timeline,
22 ** where each structure has the following fields:
23 **
@@ -55,10 +61,15 @@
55 ** merges.
56 ** ci: "cherrypick-in". Like "mi" except for cherrypick merges.
57 ** omitted if there are no cherrypick merges.
58 ** h: The artifact hash of the object being graphed
59 */
 
 
 
 
 
60 var amendCssOnce = 1; // Only change the CSS one time
61 function amendCss(circleNodes,showArrowheads){
62 if( !amendCssOnce ) return;
63 var css = "";
64 if( circleNodes ){
@@ -73,17 +84,134 @@
73 document.querySelector("head").appendChild(style);
74 }
75 amendCssOnce = 0;
76 }
77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78 function TimelineGraph(tx){
79 var topObj = document.getElementById("timelineTable"+tx.iTableId);
80 amendCss(tx.circleNodes, tx.showArrowheads);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81 var canvasDiv;
82 var railPitch;
83 var mergeOffset;
84 var node, arrow, arrowSmall, line, mArrow, mLine, wArrow, wLine;
 
85 function initGraph(){
86 var parent = topObj.rows[0].cells[1];
87 parent.style.verticalAlign = "top";
88 canvasDiv = document.createElement("div");
89 canvasDiv.className = "tl-canvas";
@@ -163,17 +291,22 @@
163 n.className = "tl-"+cls;
164 canvasDiv.appendChild(n);
165 return n;
166 }
167 function absoluteY(obj){
168 var top = 0;
169 if( obj.offsetParent ){
170 do{
171 top += obj.offsetTop;
172 }while( obj = obj.offsetParent );
173 }
174 return top;
 
 
 
 
 
175 }
176 function miLineY(p){
177 return p.y + node.h - mLine.w - 1;
178 }
179 function drawLine(elem,color,x0,y0,x1,y1){
@@ -185,28 +318,34 @@
185 y1 = y0+elem.w;
186 cls += "h";
187 }
188 return drawBox(cls,color,x0,y0,x1,y1);
189 }
190 function drawUpArrow(from,to,color){
191 var y = to.y + node.h;
192 var arrowSpace = from.y - y + (!from.id || from.r!=to.r ? node.h/2 : 0);
193 var arw = arrowSpace < arrow.h*1.5 ? arrowSmall : arrow;
194 var x = to.x + (node.w-line.w)/2;
195 var y0 = from.y + node.h/2;
196 var y1 = Math.ceil(to.y + node.h + arw.h/2);
197 drawLine(line,color,x,y0,null,y1);
 
198 x = to.x + (node.w-arw.w)/2;
199 var n = drawBox(arw.cls,null,x,y);
200 if(color) n.style.borderBottomColor = color;
 
201 }
202 function drawDotted(from,to,color){
203 var x = to.x + (node.w-line.w)/2;
204 var y0 = from.y + node.h/2;
205 var y1 = Math.ceil(to.y + node.h);
206 var n = drawLine(dotLine,null,x,y0,null,y1)
207 if( color ) n.style.borderColor = color
 
 
 
 
208 }
209 /* Draw thin horizontal or vertical lines representing merges */
210 function drawMergeLine(x0,y0,x1,y1){
211 drawLine(mLine,null,x0,y0,x1,y1);
212 }
@@ -243,37 +382,39 @@
243 if(e) e.style.backgroundColor = p.bg;
244 e = document.getElementById("md"+p.id);
245 if(e) e.style.backgroundColor = p.bg;
246 }
247 if( p.r<0 ) return;
248 if( p.u>0 ) drawUpArrow(p,tx.rowinfo[p.u-tx.iTopRow],p.fg);
249 if( p.sb>0 ) drawDotted(p,tx.rowinfo[p.sb-tx.iTopRow],p.fg);
250 var cls = node.cls;
251 if( p.hasOwnProperty('mi') && p.mi.length ) cls += " merge";
252 if( p.f&1 ) cls += " leaf";
253 var n = drawBox(cls,p.bg,p.x,p.y);
254 n.id = "tln"+p.id;
255 n.onclick = clickOnNode;
 
 
256 n.style.zIndex = 10;
257 if( !tx.omitDescenders ){
258 if( p.u==0 ){
259 if( p.hasOwnProperty('mo') && p.r==p.mo ){
260 var ix = p.hasOwnProperty('cu') ? p.cu : p.mu;
261 var top = tx.rowinfo[ix-tx.iTopRow]
262 drawUpArrow(p,{x: p.x, y: top.y-node.h}, p.fg);
263 }else if( p.y>100 ){
264 drawUpArrow(p,{x: p.x, y: p.y-50}, p.fg);
265 }else{
266 drawUpArrow(p,{x: p.x, y: 0},p.fg);
267 }
268 }
269 if( p.hasOwnProperty('d') ){
270 if( p.y + 150 >= btm ){
271 drawUpArrow({x: p.x, y: btm - node.h/2},p,p.fg);
272 }else{
273 drawUpArrow({x: p.x, y: p.y+50},p,p.fg);
274 drawDotted({x: p.x, y: p.y+63},{x: p.x, y: p.y+50-node.h/2},p.fg);
275 }
276 }
277 }
278 if( p.hasOwnProperty('mo') ){
279 var x0 = p.x + node.w/2;
@@ -326,11 +467,11 @@
326 }
327 var y0 = p.y + (node.h-line.w)/2;
328 var u = tx.rowinfo[p.au[i+1]-tx.iTopRow];
329 if( u.id<p.id ){
330 drawLine(line,u.fg,x0,y0,x1,null);
331 drawUpArrow(p,u,u.fg);
332 }else{
333 var y1 = u.y + (node.h-line.w)/2;
334 drawLine(wLine,u.fg,x0,y0,x1,null);
335 drawLine(wLine,u.fg,x1-line.w,y0,null,y1+line.w);
336 drawLine(wLine,u.fg,x1,y1,u.x-wArrow.w/2,null);
@@ -390,11 +531,12 @@
390 for( var i=tx.rowinfo.length-1; i>=0; i-- ){
391 drawNode(tx.rowinfo[i], btm);
392 }
393 }
394 var selRow;
395 function clickOnNode(){
 
396 var p = tx.rowinfo[parseInt(this.id.match(/\d+$/)[0], 10)-tx.iTopRow];
397 if( !selRow ){
398 selRow = p;
399 this.className += " sel";
400 canvasDiv.className += " sel";
@@ -407,10 +549,113 @@
407 location.href=tx.baseUrl + "/fdiff?v1="+selRow.h+"&v2="+p.h
408 }else{
409 location.href=tx.baseUrl + "/vdiff?from="+selRow.h+"&to="+p.h
410 }
411 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
412 }
413 function changeDisplay(selector,value){
414 var x = document.getElementsByClassName(selector);
415 var n = x.length;
416 for(var i=0; i<n; i++) {x[i].style.display = value;}
417
--- src/graph.js
+++ src/graph.js
@@ -1,6 +1,9 @@
1 /* This module contains javascript needed to render timeline graphs in Fossil.
2 **
3 ** There can be multiple graphs on a single webpage, but this script is only
4 ** loaded once.
5 **
6 ** Prior to sourcing this script, there should be a separate
7 ** <script type='application/json' id='timeline-data-NN'> for each graph,
8 ** each containing JSON like this:
9 **
@@ -14,10 +17,13 @@
17 ** "omitDescenders": BOOLEAN, // Omit ancestor lines off bottom of screen
18 ** "fileDiff": BOOLEAN, // True for file diff. False for check-in
19 ** "scrollToSelect": BOOLEAN, // Scroll to selection on first render
20 ** "nrail": INTEGER, // Number of vertical "rails"
21 ** "baseUrl": TEXT, // Top-level URL
22 ** "dwellTimeout": INTEGER, // Tooltip show delay in milliseconds
23 ** "closeTimeout": INTEGER, // Tooltip close delay in milliseconds
24 ** "hashDigits": INTEGER, // Limit of tooltip hashes ("hash-digits")
25 ** "rowinfo": ROWINFO-ARRAY }
26 **
27 ** The rowinfo field is an array of structures, one per entry in the timeline,
28 ** where each structure has the following fields:
29 **
@@ -55,10 +61,15 @@
61 ** merges.
62 ** ci: "cherrypick-in". Like "mi" except for cherrypick merges.
63 ** omitted if there are no cherrypick merges.
64 ** h: The artifact hash of the object being graphed
65 */
66
67 /* The amendCss() function does a one-time change to the CSS to account
68 ** for the "circleNodes" and "showArrowheads" settings. Do this change
69 ** only once, even if there are multiple graphs being rendered.
70 */
71 var amendCssOnce = 1; // Only change the CSS one time
72 function amendCss(circleNodes,showArrowheads){
73 if( !amendCssOnce ) return;
74 var css = "";
75 if( circleNodes ){
@@ -73,17 +84,134 @@
84 document.querySelector("head").appendChild(style);
85 }
86 amendCssOnce = 0;
87 }
88
89 /* The <span> object that holds the tooltip */
90 var tooltipObj = document.createElement("span");
91 tooltipObj.className = "tl-tooltip";
92 tooltipObj.style.display = "none";
93 document.getElementsByClassName("content")[0].appendChild(tooltipObj);
94 tooltipObj.onmouseenter = function(){stopCloseTimer();}
95 tooltipObj.onmouseleave = function(){
96 if (tooltipInfo.ixActive != -1) resumeCloseTimer();
97 };
98
99 /* State information for the tooltip popup and its timers */
100 window.tooltipInfo = {
101 dwellTimeout: 250, /* The tooltip dwell timeout. */
102 closeTimeout: 3000, /* The tooltip close timeout. */
103 hashDigits: 16, /* Limit of tooltip hashes ("hash-digits"). */
104 idTimer: 0, /* The tooltip dwell timer id. */
105 idTimerClose: 0, /* The tooltip close timer id. */
106 ixHover: -1, /* The id of the element with the mouse. */
107 ixActive: -1, /* The id of the element with the tooltip. */
108 nodeHover: null, /* Graph node under mouse when ixHover==-2 */
109 posX: 0, posY: 0 /* The last mouse position. */
110 };
111
112 /* Functions used to control the tooltip popup and its timer */
113 function hideGraphTooltip(){
114 stopCloseTimer();
115 tooltipObj.style.display = "none";
116 tooltipInfo.ixActive = -1;
117 }
118 document.body.onunload = hideGraphTooltip
119 function stopDwellTimer(){
120 if (tooltipInfo.idTimer != 0) {
121 clearTimeout(tooltipInfo.idTimer);
122 tooltipInfo.idTimer = 0;
123 }
124 }
125 function resumeCloseTimer(){
126 /* This timer must be stopped explicitly to reset the elapsed timeout. */
127 if(tooltipInfo.idTimerClose == 0 && tooltipInfo.closeTimeout>0) {
128 tooltipInfo.idTimerClose = setTimeout(function(){
129 tooltipInfo.idTimerClose = 0;
130 hideGraphTooltip();
131 },tooltipInfo.closeTimeout);
132 }
133 }
134 function stopCloseTimer(){
135 if (tooltipInfo.idTimerClose != 0) {
136 clearTimeout(tooltipInfo.idTimerClose);
137 tooltipInfo.idTimerClose = 0;
138 }
139 }
140
141 /* Construct that graph corresponding to the timeline-data-N object that
142 ** is passed in by the tx parameter */
143 function TimelineGraph(tx){
144 var topObj = document.getElementById("timelineTable"+tx.iTableId);
145 amendCss(tx.circleNodes, tx.showArrowheads);
146 tooltipInfo.dwellTimeout = tx.dwellTimeout
147 tooltipInfo.closeTimeout = tx.closeTimeout
148 tooltipInfo.hashDigits = tx.hashDigits
149 topObj.onclick = clickOnGraph
150 topObj.ondblclick = dblclickOnGraph
151 topObj.onmousemove = function(e) {
152 var ix = findTxIndex(e);
153 topObj.style.cursor = (ix<0) ? "" : "pointer"
154 /* Keep the already visible tooltip at a constant position, as long as the
155 ** mouse is over the same element. */
156 if(tooltipObj.style.display != "none"){
157 if(ix == tooltipInfo.ixHover) return;
158 }
159 /* The tooltip is either not visible, or the mouse is over a different
160 ** element, so clear the dwell timer, and record the new element id and
161 ** mouse position. */
162 stopDwellTimer();
163 if(ix >= 0){
164 tooltipInfo.ixHover = ix;
165 tooltipInfo.posX = e.clientX;
166 tooltipInfo.posY = e.clientY;
167 stopCloseTimer();
168 if(tooltipInfo.dwellTimeout>0){
169 tooltipInfo.idTimer = setTimeout(function() {
170 tooltipInfo.idTimer = 0;
171 stopCloseTimer();
172 showGraphTooltip();
173 },tooltipInfo.dwellTimeout);
174 }
175 }else{
176 /* The mouse is not over an element with a tooltip */
177 tooltipInfo.ixHover = -1;
178 resumeCloseTimer();
179 }
180 };
181 topObj.onmouseleave = function(e) {
182 /* Hide the tooltip if the mouse is outside the "timelineTableN" element,
183 ** and outside the tooltip. */
184 if(e.relatedTarget && e.relatedTarget != tooltipObj){
185 tooltipInfo.ixHover = -1;
186 hideGraphTooltip();
187 stopDwellTimer();
188 stopCloseTimer();
189 }
190 };
191 function nodeHover(e){
192 /* Invoked by mousemove events over a graph node */
193 e.stopPropagation()
194 if(tooltipInfo.ixHover==-2) return
195 tooltipInfo.ixHover = -2
196 tooltipInfo.posX = e.clientX
197 tooltipInfo.posY = e.clientY
198 tooltipInfo.nodeHover = this
199 stopCloseTimer();
200 if(tooltipInfo.dwellTimeout>0){
201 tooltipInfo.idTimer = setTimeout(function() {
202 tooltipInfo.idTimer = 0;
203 stopCloseTimer();
204 showGraphTooltip();
205 },tooltipInfo.dwellTimeout);
206 }
207 }
208 var canvasDiv;
209 var railPitch;
210 var mergeOffset;
211 var node, arrow, arrowSmall, line, mArrow, mLine, wArrow, wLine;
212
213 function initGraph(){
214 var parent = topObj.rows[0].cells[1];
215 parent.style.verticalAlign = "top";
216 canvasDiv = document.createElement("div");
217 canvasDiv.className = "tl-canvas";
@@ -163,17 +291,22 @@
291 n.className = "tl-"+cls;
292 canvasDiv.appendChild(n);
293 return n;
294 }
295 function absoluteY(obj){
296 var y = 0;
297 do{
298 y += obj.offsetTop;
299 }while( obj = obj.offsetParent );
300 return y;
301 }
302 function absoluteX(obj){
303 var x = 0;
304 do{
305 x += obj.offsetLeft;
306 }while( obj = obj.offsetParent );
307 return x;
308 }
309 function miLineY(p){
310 return p.y + node.h - mLine.w - 1;
311 }
312 function drawLine(elem,color,x0,y0,x1,y1){
@@ -185,28 +318,34 @@
318 y1 = y0+elem.w;
319 cls += "h";
320 }
321 return drawBox(cls,color,x0,y0,x1,y1);
322 }
323 function drawUpArrow(from,to,color,id){
324 var y = to.y + node.h;
325 var arrowSpace = from.y - y + (!from.id || from.r!=to.r ? node.h/2 : 0);
326 var arw = arrowSpace < arrow.h*1.5 ? arrowSmall : arrow;
327 var x = to.x + (node.w-line.w)/2;
328 var y0 = from.y + node.h/2;
329 var y1 = Math.ceil(to.y + node.h + arw.h/2);
330 var n = drawLine(line,color,x,y0,null,y1);
331 addToolTip(n,id)
332 x = to.x + (node.w-arw.w)/2;
333 n = drawBox(arw.cls,null,x,y);
334 if(color) n.style.borderBottomColor = color;
335 addToolTip(n,id)
336 }
337 function drawDotted(from,to,color,id){
338 var x = to.x + (node.w-line.w)/2;
339 var y0 = from.y + node.h/2;
340 var y1 = Math.ceil(to.y + node.h);
341 var n = drawLine(dotLine,null,x,y0,null,y1)
342 if( color ) n.style.borderColor = color
343 addToolTip(n,id)
344 }
345 function addToolTip(n,id){
346 if( id ) n.setAttribute("data-ix",id-tx.iTopRow)
347 }
348 /* Draw thin horizontal or vertical lines representing merges */
349 function drawMergeLine(x0,y0,x1,y1){
350 drawLine(mLine,null,x0,y0,x1,y1);
351 }
@@ -243,37 +382,39 @@
382 if(e) e.style.backgroundColor = p.bg;
383 e = document.getElementById("md"+p.id);
384 if(e) e.style.backgroundColor = p.bg;
385 }
386 if( p.r<0 ) return;
387 if( p.u>0 ) drawUpArrow(p,tx.rowinfo[p.u-tx.iTopRow],p.fg,p.id);
388 if( p.sb>0 ) drawDotted(p,tx.rowinfo[p.sb-tx.iTopRow],p.fg,p.id);
389 var cls = node.cls;
390 if( p.hasOwnProperty('mi') && p.mi.length ) cls += " merge";
391 if( p.f&1 ) cls += " leaf";
392 var n = drawBox(cls,p.bg,p.x,p.y);
393 n.id = "tln"+p.id;
394 n.onclick = clickOnNode;
395 n.ondblclick = dblclickOnNode;
396 n.onmousemove = nodeHover;
397 n.style.zIndex = 10;
398 if( !tx.omitDescenders ){
399 if( p.u==0 ){
400 if( p.hasOwnProperty('mo') && p.r==p.mo ){
401 var ix = p.hasOwnProperty('cu') ? p.cu : p.mu;
402 var top = tx.rowinfo[ix-tx.iTopRow]
403 drawUpArrow(p,{x: p.x, y: top.y-node.h}, p.fg, p.id);
404 }else if( p.y>100 ){
405 drawUpArrow(p,{x: p.x, y: p.y-50}, p.fg, p.id);
406 }else{
407 drawUpArrow(p,{x: p.x, y: 0},p.fg, p.id);
408 }
409 }
410 if( p.hasOwnProperty('d') ){
411 if( p.y + 150 >= btm ){
412 drawUpArrow({x: p.x, y: btm - node.h/2},p,p.fg,p.id);
413 }else{
414 drawUpArrow({x: p.x, y: p.y+50},p,p.fg,p.id);
415 drawDotted({x: p.x, y: p.y+63},{x: p.x, y: p.y+50-node.h/2},p.fg,p.id);
416 }
417 }
418 }
419 if( p.hasOwnProperty('mo') ){
420 var x0 = p.x + node.w/2;
@@ -326,11 +467,11 @@
467 }
468 var y0 = p.y + (node.h-line.w)/2;
469 var u = tx.rowinfo[p.au[i+1]-tx.iTopRow];
470 if( u.id<p.id ){
471 drawLine(line,u.fg,x0,y0,x1,null);
472 drawUpArrow(p,u,u.fg,u.id);
473 }else{
474 var y1 = u.y + (node.h-line.w)/2;
475 drawLine(wLine,u.fg,x0,y0,x1,null);
476 drawLine(wLine,u.fg,x1-line.w,y0,null,y1+line.w);
477 drawLine(wLine,u.fg,x1,y1,u.x-wArrow.w/2,null);
@@ -390,11 +531,12 @@
531 for( var i=tx.rowinfo.length-1; i>=0; i-- ){
532 drawNode(tx.rowinfo[i], btm);
533 }
534 }
535 var selRow;
536 function clickOnNode(e){
537 hideGraphTooltip()
538 var p = tx.rowinfo[parseInt(this.id.match(/\d+$/)[0], 10)-tx.iTopRow];
539 if( !selRow ){
540 selRow = p;
541 this.className += " sel";
542 canvasDiv.className += " sel";
@@ -407,10 +549,113 @@
549 location.href=tx.baseUrl + "/fdiff?v1="+selRow.h+"&v2="+p.h
550 }else{
551 location.href=tx.baseUrl + "/vdiff?from="+selRow.h+"&to="+p.h
552 }
553 }
554 e.stopPropagation()
555 }
556 function dblclickOnNode(e){
557 var p = tx.rowinfo[parseInt(this.id.match(/\d+$/)[0], 10)-tx.iTopRow];
558 window.location.href = tx.baseUrl+"/info/"+p.h
559 e.stopPropagation()
560 }
561 function findTxIndex(e){
562 /* Look at all the graph elements. If any graph elements that is near
563 ** the click-point "e" and has a "data-ix" attribute, then return
564 ** the value of that attribute. Otherwise return -1 */
565 var x = e.clientX + window.pageXOffset - absoluteX(canvasDiv);
566 var y = e.clientY + window.pageYOffset - absoluteY(canvasDiv);
567 var aNode = canvasDiv.childNodes
568 var nNode = aNode.length;
569 var i;
570 for(i=0;i<nNode;i++){
571 var n = aNode[i]
572 if( !n.hasAttribute("data-ix") ) continue;
573 if( x<n.offsetLeft-5 ) continue;
574 if( x>n.offsetLeft+n.offsetWidth+5 ) continue;
575 if( y<n.offsetTop-5 ) continue;
576 if( y>n.offsetTop+n.offsetHeight ) continue;
577 return n.getAttribute("data-ix")
578 }
579 return -1
580 }
581 /* Compute the hyperlink for the branch graph for tx.rowinfo[ix] */
582 function branchHyperlink(ix){
583 var br = tx.rowinfo[ix].br
584 var dest = tx.baseUrl + "/timeline?r=" + encodeURIComponent(br)
585 dest += tx.fileDiff ? "&m&cf=" : "&m&c="
586 dest += encodeURIComponent(tx.rowinfo[ix].h)
587 return dest
588 }
589 function clickOnGraph(e){
590 tooltipInfo.ixHover = findTxIndex(e);
591 tooltipInfo.posX = e.clientX;
592 tooltipInfo.posY = e.clientY;
593 showGraphTooltip();
594 }
595 function showGraphTooltip(){
596 var html = null
597 var ix = -1
598 if( tooltipInfo.ixHover==-2 ){
599 ix = parseInt(tooltipInfo.nodeHover.id.match(/\d+$/)[0],10)-tx.iTopRow
600 var h = tx.rowinfo[ix].h
601 var dest = tx.baseUrl + "/info/" + h
602 h = h.slice(0,tooltipInfo.hashDigits); // Assume single-byte characters.
603 if( tx.fileDiff ){
604 html = "artifact <a id=\"tooltip-link\" href=\""+dest+"\">"+h+"</a>"
605 }else{
606 html = "check-in <a id=\"tooltip-link\" href=\""+dest+"\">"+h+"</a>"
607 }
608 tooltipInfo.ixActive = -2;
609 }else if( tooltipInfo.ixHover>=0 ){
610 ix = tooltipInfo.ixHover
611 var br = tx.rowinfo[ix].br
612 var dest = branchHyperlink(ix)
613 var hbr = br.replace(/&/g, "&amp;")
614 .replace(/</g, "&lt;")
615 .replace(/>/g, "&gt;")
616 .replace(/"/g, "&quot;")
617 .replace(/'/g, "&#039;");
618 html = "branch <a id=\"tooltip-link\" href=\""+dest+"\">"+hbr+"</a>"
619 tooltipInfo.ixActive = ix;
620 }
621 if( html ){
622 /* Setup while hidden, to ensure proper dimensions. */
623 var s = getComputedStyle(document.body)
624 if( tx.rowinfo[ix].bg.length ){
625 tooltipObj.style.backgroundColor = tx.rowinfo[ix].bg
626 }else{
627 tooltipObj.style.backgroundColor = s.getPropertyValue('background-color')
628 }
629 tooltipObj.style.borderColor =
630 tooltipObj.style.color = s.getPropertyValue('color')
631 tooltipObj.style.visibility = "hidden"
632 tooltipObj.innerHTML = html
633 tooltipObj.appendChild(document.createTextNode(' '));
634 tooltipObj.appendChild(
635 makeCopyButton("tooltip-copybtn","tooltip-link",0));
636 tooltipObj.style.display = "inline"
637 tooltipObj.style.position = "absolute"
638 var x = tooltipInfo.posX + 4 + window.pageXOffset
639 - absoluteX(tooltipObj.offsetParent)
640 tooltipObj.style.left = x+"px"
641 var y = tooltipInfo.posY + window.pageYOffset
642 - tooltipObj.clientHeight - 4
643 - absoluteY(tooltipObj.offsetParent)
644 tooltipObj.style.top = y+"px"
645 tooltipObj.style.visibility = "visible"
646 }else{
647 hideGraphTooltip()
648 }
649 }
650 function dblclickOnGraph(e){
651 var ix = findTxIndex(e);
652 hideGraphTooltip()
653 if( ix>=0 ){
654 var dest = branchHyperlink(ix)
655 window.location.href = dest
656 }
657 }
658 function changeDisplay(selector,value){
659 var x = document.getElementsByClassName(selector);
660 var n = x.length;
661 for(var i=0; i<n; i++) {x[i].style.display = value;}
662
+18 -5
--- src/info.c
+++ src/info.c
@@ -763,11 +763,15 @@
763763
" AND tag.tagid=tagxref.tagid "
764764
" AND +tag.tagname GLOB 'sym-*'", rid);
765765
while( db_step(&q2)==SQLITE_ROW ){
766766
const char *zTagName = db_column_text(&q2, 0);
767767
if( fossil_strcmp(zTagName,zBrName)==0 ){
768
- @ | %z(href("%R/timeline?r=%T&unhide",zTagName))%h(zTagName)</a>
768
+ @ | <span class="copy-button" id="copy-br0"
769
+ @ data-copytarget="br0" data-copylength="0">
770
+ @ </span>&nbsp;<span
771
+ @ id="br0">%z(href("%R/timeline?r=%T&unhide",zTagName))%h(zTagName)</a>
772
+ @ </span>
769773
if( wiki_tagid2("branch",zTagName)!=0 ){
770774
blob_appendf(&wiki_read_links, " | %z%h</a>",
771775
href("%R/wiki?name=branch/%h",zTagName), zTagName);
772776
}else if( g.perm.Write && g.perm.WrWiki ){
773777
blob_appendf(&wiki_add_links, " | %z%h</a>",
@@ -793,11 +797,14 @@
793797
@ | %z(href("%R/fileage?name=%!S",zUuid))file ages</a>
794798
@ | %z(href("%R/tree?nofiles&type=tree&ci=%!S",zUuid))folders</a>
795799
@ </td>
796800
@ </tr>
797801
798
- @ <tr><th>%s(hname_alg(nUuid)):</th><td>%.32s(zUuid)<wbr>%s(zUuid+32)
802
+ @ <tr><th>%s(hname_alg(nUuid)):</th><td>
803
+ @ <span class="copy-button" id="copy-fullhash"
804
+ @ data-copytarget="fullhash" data-copylength="%d(hash_digits(1))">
805
+ @ </span>&nbsp;<span id="fullhash">%.32s(zUuid)<wbr>%s(zUuid+32)</span>
799806
if( g.perm.Setup ){
800807
@ (Record ID: %d(rid))
801808
}
802809
@ </td></tr>
803810
@ <tr><th>User&nbsp;&amp;&nbsp;Date:</th><td>
@@ -2182,14 +2189,20 @@
21822189
}
21832190
zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid);
21842191
if( isFile ){
21852192
@ <h2>Latest version of file '%h(zName)':</h2>
21862193
style_submenu_element("Artifact", "%R/artifact/%S", zUuid);
2187
- }else if( g.perm.Setup ){
2188
- @ <h2>Artifact %s(zUuid) (%d(rid)):</h2>
21892194
}else{
2190
- @ <h2>Artifact %s(zUuid):</h2>
2195
+ style_copy_button();
2196
+ @ <h2>Artifact
2197
+ @ <span class="copy-button" id="copy-artifacthash"
2198
+ @ data-copytarget="artifacthash" data-copylength="%d(hash_digits(1))">
2199
+ if( g.perm.Setup ){
2200
+ @ </span>&nbsp;<span id="artifacthash">%s(zUuid)</span> (%d(rid)):</h2>
2201
+ }else{
2202
+ @ </span>&nbsp;<span id="artifacthash">%s(zUuid)</span>:</h2>
2203
+ }
21912204
}
21922205
blob_zero(&downloadName);
21932206
asText = P("txt")!=0;
21942207
if( asText ) objdescFlags &= ~OBJDESC_BASE;
21952208
objType = object_description(rid, objdescFlags, &downloadName);
21962209
--- src/info.c
+++ src/info.c
@@ -763,11 +763,15 @@
763 " AND tag.tagid=tagxref.tagid "
764 " AND +tag.tagname GLOB 'sym-*'", rid);
765 while( db_step(&q2)==SQLITE_ROW ){
766 const char *zTagName = db_column_text(&q2, 0);
767 if( fossil_strcmp(zTagName,zBrName)==0 ){
768 @ | %z(href("%R/timeline?r=%T&unhide",zTagName))%h(zTagName)</a>
 
 
 
 
769 if( wiki_tagid2("branch",zTagName)!=0 ){
770 blob_appendf(&wiki_read_links, " | %z%h</a>",
771 href("%R/wiki?name=branch/%h",zTagName), zTagName);
772 }else if( g.perm.Write && g.perm.WrWiki ){
773 blob_appendf(&wiki_add_links, " | %z%h</a>",
@@ -793,11 +797,14 @@
793 @ | %z(href("%R/fileage?name=%!S",zUuid))file ages</a>
794 @ | %z(href("%R/tree?nofiles&type=tree&ci=%!S",zUuid))folders</a>
795 @ </td>
796 @ </tr>
797
798 @ <tr><th>%s(hname_alg(nUuid)):</th><td>%.32s(zUuid)<wbr>%s(zUuid+32)
 
 
 
799 if( g.perm.Setup ){
800 @ (Record ID: %d(rid))
801 }
802 @ </td></tr>
803 @ <tr><th>User&nbsp;&amp;&nbsp;Date:</th><td>
@@ -2182,14 +2189,20 @@
2182 }
2183 zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid);
2184 if( isFile ){
2185 @ <h2>Latest version of file '%h(zName)':</h2>
2186 style_submenu_element("Artifact", "%R/artifact/%S", zUuid);
2187 }else if( g.perm.Setup ){
2188 @ <h2>Artifact %s(zUuid) (%d(rid)):</h2>
2189 }else{
2190 @ <h2>Artifact %s(zUuid):</h2>
 
 
 
 
 
 
 
 
2191 }
2192 blob_zero(&downloadName);
2193 asText = P("txt")!=0;
2194 if( asText ) objdescFlags &= ~OBJDESC_BASE;
2195 objType = object_description(rid, objdescFlags, &downloadName);
2196
--- src/info.c
+++ src/info.c
@@ -763,11 +763,15 @@
763 " AND tag.tagid=tagxref.tagid "
764 " AND +tag.tagname GLOB 'sym-*'", rid);
765 while( db_step(&q2)==SQLITE_ROW ){
766 const char *zTagName = db_column_text(&q2, 0);
767 if( fossil_strcmp(zTagName,zBrName)==0 ){
768 @ | <span class="copy-button" id="copy-br0"
769 @ data-copytarget="br0" data-copylength="0">
770 @ </span>&nbsp;<span
771 @ id="br0">%z(href("%R/timeline?r=%T&unhide",zTagName))%h(zTagName)</a>
772 @ </span>
773 if( wiki_tagid2("branch",zTagName)!=0 ){
774 blob_appendf(&wiki_read_links, " | %z%h</a>",
775 href("%R/wiki?name=branch/%h",zTagName), zTagName);
776 }else if( g.perm.Write && g.perm.WrWiki ){
777 blob_appendf(&wiki_add_links, " | %z%h</a>",
@@ -793,11 +797,14 @@
797 @ | %z(href("%R/fileage?name=%!S",zUuid))file ages</a>
798 @ | %z(href("%R/tree?nofiles&type=tree&ci=%!S",zUuid))folders</a>
799 @ </td>
800 @ </tr>
801
802 @ <tr><th>%s(hname_alg(nUuid)):</th><td>
803 @ <span class="copy-button" id="copy-fullhash"
804 @ data-copytarget="fullhash" data-copylength="%d(hash_digits(1))">
805 @ </span>&nbsp;<span id="fullhash">%.32s(zUuid)<wbr>%s(zUuid+32)</span>
806 if( g.perm.Setup ){
807 @ (Record ID: %d(rid))
808 }
809 @ </td></tr>
810 @ <tr><th>User&nbsp;&amp;&nbsp;Date:</th><td>
@@ -2182,14 +2189,20 @@
2189 }
2190 zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid);
2191 if( isFile ){
2192 @ <h2>Latest version of file '%h(zName)':</h2>
2193 style_submenu_element("Artifact", "%R/artifact/%S", zUuid);
 
 
2194 }else{
2195 style_copy_button();
2196 @ <h2>Artifact
2197 @ <span class="copy-button" id="copy-artifacthash"
2198 @ data-copytarget="artifacthash" data-copylength="%d(hash_digits(1))">
2199 if( g.perm.Setup ){
2200 @ </span>&nbsp;<span id="artifacthash">%s(zUuid)</span> (%d(rid)):</h2>
2201 }else{
2202 @ </span>&nbsp;<span id="artifacthash">%s(zUuid)</span>:</h2>
2203 }
2204 }
2205 blob_zero(&downloadName);
2206 asText = P("txt")!=0;
2207 if( asText ) objdescFlags &= ~OBJDESC_BASE;
2208 objType = object_description(rid, objdescFlags, &downloadName);
2209
+18 -5
--- src/info.c
+++ src/info.c
@@ -763,11 +763,15 @@
763763
" AND tag.tagid=tagxref.tagid "
764764
" AND +tag.tagname GLOB 'sym-*'", rid);
765765
while( db_step(&q2)==SQLITE_ROW ){
766766
const char *zTagName = db_column_text(&q2, 0);
767767
if( fossil_strcmp(zTagName,zBrName)==0 ){
768
- @ | %z(href("%R/timeline?r=%T&unhide",zTagName))%h(zTagName)</a>
768
+ @ | <span class="copy-button" id="copy-br0"
769
+ @ data-copytarget="br0" data-copylength="0">
770
+ @ </span>&nbsp;<span
771
+ @ id="br0">%z(href("%R/timeline?r=%T&unhide",zTagName))%h(zTagName)</a>
772
+ @ </span>
769773
if( wiki_tagid2("branch",zTagName)!=0 ){
770774
blob_appendf(&wiki_read_links, " | %z%h</a>",
771775
href("%R/wiki?name=branch/%h",zTagName), zTagName);
772776
}else if( g.perm.Write && g.perm.WrWiki ){
773777
blob_appendf(&wiki_add_links, " | %z%h</a>",
@@ -793,11 +797,14 @@
793797
@ | %z(href("%R/fileage?name=%!S",zUuid))file ages</a>
794798
@ | %z(href("%R/tree?nofiles&type=tree&ci=%!S",zUuid))folders</a>
795799
@ </td>
796800
@ </tr>
797801
798
- @ <tr><th>%s(hname_alg(nUuid)):</th><td>%.32s(zUuid)<wbr>%s(zUuid+32)
802
+ @ <tr><th>%s(hname_alg(nUuid)):</th><td>
803
+ @ <span class="copy-button" id="copy-fullhash"
804
+ @ data-copytarget="fullhash" data-copylength="%d(hash_digits(1))">
805
+ @ </span>&nbsp;<span id="fullhash">%.32s(zUuid)<wbr>%s(zUuid+32)</span>
799806
if( g.perm.Setup ){
800807
@ (Record ID: %d(rid))
801808
}
802809
@ </td></tr>
803810
@ <tr><th>User&nbsp;&amp;&nbsp;Date:</th><td>
@@ -2182,14 +2189,20 @@
21822189
}
21832190
zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid);
21842191
if( isFile ){
21852192
@ <h2>Latest version of file '%h(zName)':</h2>
21862193
style_submenu_element("Artifact", "%R/artifact/%S", zUuid);
2187
- }else if( g.perm.Setup ){
2188
- @ <h2>Artifact %s(zUuid) (%d(rid)):</h2>
21892194
}else{
2190
- @ <h2>Artifact %s(zUuid):</h2>
2195
+ style_copy_button();
2196
+ @ <h2>Artifact
2197
+ @ <span class="copy-button" id="copy-artifacthash"
2198
+ @ data-copytarget="artifacthash" data-copylength="%d(hash_digits(1))">
2199
+ if( g.perm.Setup ){
2200
+ @ </span>&nbsp;<span id="artifacthash">%s(zUuid)</span> (%d(rid)):</h2>
2201
+ }else{
2202
+ @ </span>&nbsp;<span id="artifacthash">%s(zUuid)</span>:</h2>
2203
+ }
21912204
}
21922205
blob_zero(&downloadName);
21932206
asText = P("txt")!=0;
21942207
if( asText ) objdescFlags &= ~OBJDESC_BASE;
21952208
objType = object_description(rid, objdescFlags, &downloadName);
21962209
--- src/info.c
+++ src/info.c
@@ -763,11 +763,15 @@
763 " AND tag.tagid=tagxref.tagid "
764 " AND +tag.tagname GLOB 'sym-*'", rid);
765 while( db_step(&q2)==SQLITE_ROW ){
766 const char *zTagName = db_column_text(&q2, 0);
767 if( fossil_strcmp(zTagName,zBrName)==0 ){
768 @ | %z(href("%R/timeline?r=%T&unhide",zTagName))%h(zTagName)</a>
 
 
 
 
769 if( wiki_tagid2("branch",zTagName)!=0 ){
770 blob_appendf(&wiki_read_links, " | %z%h</a>",
771 href("%R/wiki?name=branch/%h",zTagName), zTagName);
772 }else if( g.perm.Write && g.perm.WrWiki ){
773 blob_appendf(&wiki_add_links, " | %z%h</a>",
@@ -793,11 +797,14 @@
793 @ | %z(href("%R/fileage?name=%!S",zUuid))file ages</a>
794 @ | %z(href("%R/tree?nofiles&type=tree&ci=%!S",zUuid))folders</a>
795 @ </td>
796 @ </tr>
797
798 @ <tr><th>%s(hname_alg(nUuid)):</th><td>%.32s(zUuid)<wbr>%s(zUuid+32)
 
 
 
799 if( g.perm.Setup ){
800 @ (Record ID: %d(rid))
801 }
802 @ </td></tr>
803 @ <tr><th>User&nbsp;&amp;&nbsp;Date:</th><td>
@@ -2182,14 +2189,20 @@
2182 }
2183 zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid);
2184 if( isFile ){
2185 @ <h2>Latest version of file '%h(zName)':</h2>
2186 style_submenu_element("Artifact", "%R/artifact/%S", zUuid);
2187 }else if( g.perm.Setup ){
2188 @ <h2>Artifact %s(zUuid) (%d(rid)):</h2>
2189 }else{
2190 @ <h2>Artifact %s(zUuid):</h2>
 
 
 
 
 
 
 
 
2191 }
2192 blob_zero(&downloadName);
2193 asText = P("txt")!=0;
2194 if( asText ) objdescFlags &= ~OBJDESC_BASE;
2195 objType = object_description(rid, objdescFlags, &downloadName);
2196
--- src/info.c
+++ src/info.c
@@ -763,11 +763,15 @@
763 " AND tag.tagid=tagxref.tagid "
764 " AND +tag.tagname GLOB 'sym-*'", rid);
765 while( db_step(&q2)==SQLITE_ROW ){
766 const char *zTagName = db_column_text(&q2, 0);
767 if( fossil_strcmp(zTagName,zBrName)==0 ){
768 @ | <span class="copy-button" id="copy-br0"
769 @ data-copytarget="br0" data-copylength="0">
770 @ </span>&nbsp;<span
771 @ id="br0">%z(href("%R/timeline?r=%T&unhide",zTagName))%h(zTagName)</a>
772 @ </span>
773 if( wiki_tagid2("branch",zTagName)!=0 ){
774 blob_appendf(&wiki_read_links, " | %z%h</a>",
775 href("%R/wiki?name=branch/%h",zTagName), zTagName);
776 }else if( g.perm.Write && g.perm.WrWiki ){
777 blob_appendf(&wiki_add_links, " | %z%h</a>",
@@ -793,11 +797,14 @@
797 @ | %z(href("%R/fileage?name=%!S",zUuid))file ages</a>
798 @ | %z(href("%R/tree?nofiles&type=tree&ci=%!S",zUuid))folders</a>
799 @ </td>
800 @ </tr>
801
802 @ <tr><th>%s(hname_alg(nUuid)):</th><td>
803 @ <span class="copy-button" id="copy-fullhash"
804 @ data-copytarget="fullhash" data-copylength="%d(hash_digits(1))">
805 @ </span>&nbsp;<span id="fullhash">%.32s(zUuid)<wbr>%s(zUuid+32)</span>
806 if( g.perm.Setup ){
807 @ (Record ID: %d(rid))
808 }
809 @ </td></tr>
810 @ <tr><th>User&nbsp;&amp;&nbsp;Date:</th><td>
@@ -2182,14 +2189,20 @@
2189 }
2190 zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid);
2191 if( isFile ){
2192 @ <h2>Latest version of file '%h(zName)':</h2>
2193 style_submenu_element("Artifact", "%R/artifact/%S", zUuid);
 
 
2194 }else{
2195 style_copy_button();
2196 @ <h2>Artifact
2197 @ <span class="copy-button" id="copy-artifacthash"
2198 @ data-copytarget="artifacthash" data-copylength="%d(hash_digits(1))">
2199 if( g.perm.Setup ){
2200 @ </span>&nbsp;<span id="artifacthash">%s(zUuid)</span> (%d(rid)):</h2>
2201 }else{
2202 @ </span>&nbsp;<span id="artifacthash">%s(zUuid)</span>:</h2>
2203 }
2204 }
2205 blob_zero(&downloadName);
2206 asText = P("txt")!=0;
2207 if( asText ) objdescFlags &= ~OBJDESC_BASE;
2208 objType = object_description(rid, objdescFlags, &downloadName);
2209
--- src/main.mk
+++ src/main.mk
@@ -210,10 +210,11 @@
210210
$(SRCDIR)/../skins/xekri/css.txt \
211211
$(SRCDIR)/../skins/xekri/details.txt \
212212
$(SRCDIR)/../skins/xekri/footer.txt \
213213
$(SRCDIR)/../skins/xekri/header.txt \
214214
$(SRCDIR)/ci_edit.js \
215
+ $(SRCDIR)/copybtn.js \
215216
$(SRCDIR)/diff.tcl \
216217
$(SRCDIR)/forum.js \
217218
$(SRCDIR)/graph.js \
218219
$(SRCDIR)/href.js \
219220
$(SRCDIR)/login.js \
220221
--- src/main.mk
+++ src/main.mk
@@ -210,10 +210,11 @@
210 $(SRCDIR)/../skins/xekri/css.txt \
211 $(SRCDIR)/../skins/xekri/details.txt \
212 $(SRCDIR)/../skins/xekri/footer.txt \
213 $(SRCDIR)/../skins/xekri/header.txt \
214 $(SRCDIR)/ci_edit.js \
 
215 $(SRCDIR)/diff.tcl \
216 $(SRCDIR)/forum.js \
217 $(SRCDIR)/graph.js \
218 $(SRCDIR)/href.js \
219 $(SRCDIR)/login.js \
220
--- src/main.mk
+++ src/main.mk
@@ -210,10 +210,11 @@
210 $(SRCDIR)/../skins/xekri/css.txt \
211 $(SRCDIR)/../skins/xekri/details.txt \
212 $(SRCDIR)/../skins/xekri/footer.txt \
213 $(SRCDIR)/../skins/xekri/header.txt \
214 $(SRCDIR)/ci_edit.js \
215 $(SRCDIR)/copybtn.js \
216 $(SRCDIR)/diff.tcl \
217 $(SRCDIR)/forum.js \
218 $(SRCDIR)/graph.js \
219 $(SRCDIR)/href.js \
220 $(SRCDIR)/login.js \
221
+3 -3
--- src/printf.c
+++ src/printf.c
@@ -48,11 +48,11 @@
4848
/*
4949
** Return the number of artifact hash digits to display. The number is for
5050
** human output if the bForUrl is false and is destined for a URL if
5151
** bForUrl is false.
5252
*/
53
-static int hashDigits(int bForUrl){
53
+int hash_digits(int bForUrl){
5454
static int nDigitHuman = 0;
5555
static int nDigitUrl = 0;
5656
if( nDigitHuman==0 ){
5757
nDigitHuman = db_get_int("hash-digits", FOSSIL_HASH_DIGITS);
5858
if( nDigitHuman < 6 ) nDigitHuman = 6;
@@ -66,11 +66,11 @@
6666
6767
/*
6868
** Return the number of characters in a %S output.
6969
*/
7070
int length_of_S_display(void){
71
- return hashDigits(0);
71
+ return hash_digits(0);
7272
}
7373
7474
/*
7575
** Conversion types fall into various categories as defined by the
7676
** following enumeration.
@@ -679,11 +679,11 @@
679679
if( bufpt==0 ){
680680
bufpt = "";
681681
}else if( xtype==etDYNSTRING ){
682682
zExtra = bufpt;
683683
}else if( xtype==etSTRINGID ){
684
- precision = hashDigits(flag_altform2);
684
+ precision = hash_digits(flag_altform2);
685685
}
686686
length = StrNLen32(bufpt, limit);
687687
if( precision>=0 && precision<length ) length = precision;
688688
break;
689689
}
690690
--- src/printf.c
+++ src/printf.c
@@ -48,11 +48,11 @@
48 /*
49 ** Return the number of artifact hash digits to display. The number is for
50 ** human output if the bForUrl is false and is destined for a URL if
51 ** bForUrl is false.
52 */
53 static int hashDigits(int bForUrl){
54 static int nDigitHuman = 0;
55 static int nDigitUrl = 0;
56 if( nDigitHuman==0 ){
57 nDigitHuman = db_get_int("hash-digits", FOSSIL_HASH_DIGITS);
58 if( nDigitHuman < 6 ) nDigitHuman = 6;
@@ -66,11 +66,11 @@
66
67 /*
68 ** Return the number of characters in a %S output.
69 */
70 int length_of_S_display(void){
71 return hashDigits(0);
72 }
73
74 /*
75 ** Conversion types fall into various categories as defined by the
76 ** following enumeration.
@@ -679,11 +679,11 @@
679 if( bufpt==0 ){
680 bufpt = "";
681 }else if( xtype==etDYNSTRING ){
682 zExtra = bufpt;
683 }else if( xtype==etSTRINGID ){
684 precision = hashDigits(flag_altform2);
685 }
686 length = StrNLen32(bufpt, limit);
687 if( precision>=0 && precision<length ) length = precision;
688 break;
689 }
690
--- src/printf.c
+++ src/printf.c
@@ -48,11 +48,11 @@
48 /*
49 ** Return the number of artifact hash digits to display. The number is for
50 ** human output if the bForUrl is false and is destined for a URL if
51 ** bForUrl is false.
52 */
53 int hash_digits(int bForUrl){
54 static int nDigitHuman = 0;
55 static int nDigitUrl = 0;
56 if( nDigitHuman==0 ){
57 nDigitHuman = db_get_int("hash-digits", FOSSIL_HASH_DIGITS);
58 if( nDigitHuman < 6 ) nDigitHuman = 6;
@@ -66,11 +66,11 @@
66
67 /*
68 ** Return the number of characters in a %S output.
69 */
70 int length_of_S_display(void){
71 return hash_digits(0);
72 }
73
74 /*
75 ** Conversion types fall into various categories as defined by the
76 ** following enumeration.
@@ -679,11 +679,11 @@
679 if( bufpt==0 ){
680 bufpt = "";
681 }else if( xtype==etDYNSTRING ){
682 zExtra = bufpt;
683 }else if( xtype==etSTRINGID ){
684 precision = hash_digits(flag_altform2);
685 }
686 length = StrNLen32(bufpt, limit);
687 if( precision>=0 && precision<length ) length = precision;
688 break;
689 }
690
+16
--- src/setup.c
+++ src/setup.c
@@ -749,10 +749,26 @@
749749
"timeline-max-comment", "tmc", "0", 0);
750750
@ <p>The maximum length of a comment to be displayed in a timeline.
751751
@ "0" there is no length limit.
752752
@ (Property: "timeline-max-comment")</p>
753753
754
+ @ <hr />
755
+ entry_attribute("Tooltip dwell time (milliseconds)", 6,
756
+ "timeline-dwelltime", "tdt", "100", 0);
757
+ @ <br>
758
+ entry_attribute("Tooltip close time (milliseconds)", 6,
759
+ "timeline-closetime", "tct", "250", 0);
760
+ @ <p>The <strong>dwell time</strong> defines how long the mouse pointer
761
+ @ should be stationary above an object of the graph before a tooltip
762
+ @ appears.<br>
763
+ @ The <strong>close time</strong> defines how long the mouse pointer
764
+ @ can be away from an object before a tooltip is closed.</p>
765
+ @ <p>Set <strong>dwell time</strong> to "0" to disable tooltips.<br>
766
+ @ Set <strong>close time</strong> to "0" to keep tooltips visible until
767
+ @ the mouse is clicked elsewhere.<p>
768
+ @ <p>(Properties: "timeline-dwelltime", "timeline-closetime")</p>
769
+
754770
@ <hr />
755771
@ <p><input type="submit" name="submit" value="Apply Changes" /></p>
756772
@ </div></form>
757773
db_end_transaction(0);
758774
style_footer();
759775
--- src/setup.c
+++ src/setup.c
@@ -749,10 +749,26 @@
749 "timeline-max-comment", "tmc", "0", 0);
750 @ <p>The maximum length of a comment to be displayed in a timeline.
751 @ "0" there is no length limit.
752 @ (Property: "timeline-max-comment")</p>
753
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
754 @ <hr />
755 @ <p><input type="submit" name="submit" value="Apply Changes" /></p>
756 @ </div></form>
757 db_end_transaction(0);
758 style_footer();
759
--- src/setup.c
+++ src/setup.c
@@ -749,10 +749,26 @@
749 "timeline-max-comment", "tmc", "0", 0);
750 @ <p>The maximum length of a comment to be displayed in a timeline.
751 @ "0" there is no length limit.
752 @ (Property: "timeline-max-comment")</p>
753
754 @ <hr />
755 entry_attribute("Tooltip dwell time (milliseconds)", 6,
756 "timeline-dwelltime", "tdt", "100", 0);
757 @ <br>
758 entry_attribute("Tooltip close time (milliseconds)", 6,
759 "timeline-closetime", "tct", "250", 0);
760 @ <p>The <strong>dwell time</strong> defines how long the mouse pointer
761 @ should be stationary above an object of the graph before a tooltip
762 @ appears.<br>
763 @ The <strong>close time</strong> defines how long the mouse pointer
764 @ can be away from an object before a tooltip is closed.</p>
765 @ <p>Set <strong>dwell time</strong> to "0" to disable tooltips.<br>
766 @ Set <strong>close time</strong> to "0" to keep tooltips visible until
767 @ the mouse is clicked elsewhere.<p>
768 @ <p>(Properties: "timeline-dwelltime", "timeline-closetime")</p>
769
770 @ <hr />
771 @ <p><input type="submit" name="submit" value="Apply Changes" /></p>
772 @ </div></form>
773 db_end_transaction(0);
774 style_footer();
775
+16
--- src/setup.c
+++ src/setup.c
@@ -749,10 +749,26 @@
749749
"timeline-max-comment", "tmc", "0", 0);
750750
@ <p>The maximum length of a comment to be displayed in a timeline.
751751
@ "0" there is no length limit.
752752
@ (Property: "timeline-max-comment")</p>
753753
754
+ @ <hr />
755
+ entry_attribute("Tooltip dwell time (milliseconds)", 6,
756
+ "timeline-dwelltime", "tdt", "100", 0);
757
+ @ <br>
758
+ entry_attribute("Tooltip close time (milliseconds)", 6,
759
+ "timeline-closetime", "tct", "250", 0);
760
+ @ <p>The <strong>dwell time</strong> defines how long the mouse pointer
761
+ @ should be stationary above an object of the graph before a tooltip
762
+ @ appears.<br>
763
+ @ The <strong>close time</strong> defines how long the mouse pointer
764
+ @ can be away from an object before a tooltip is closed.</p>
765
+ @ <p>Set <strong>dwell time</strong> to "0" to disable tooltips.<br>
766
+ @ Set <strong>close time</strong> to "0" to keep tooltips visible until
767
+ @ the mouse is clicked elsewhere.<p>
768
+ @ <p>(Properties: "timeline-dwelltime", "timeline-closetime")</p>
769
+
754770
@ <hr />
755771
@ <p><input type="submit" name="submit" value="Apply Changes" /></p>
756772
@ </div></form>
757773
db_end_transaction(0);
758774
style_footer();
759775
--- src/setup.c
+++ src/setup.c
@@ -749,10 +749,26 @@
749 "timeline-max-comment", "tmc", "0", 0);
750 @ <p>The maximum length of a comment to be displayed in a timeline.
751 @ "0" there is no length limit.
752 @ (Property: "timeline-max-comment")</p>
753
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
754 @ <hr />
755 @ <p><input type="submit" name="submit" value="Apply Changes" /></p>
756 @ </div></form>
757 db_end_transaction(0);
758 style_footer();
759
--- src/setup.c
+++ src/setup.c
@@ -749,10 +749,26 @@
749 "timeline-max-comment", "tmc", "0", 0);
750 @ <p>The maximum length of a comment to be displayed in a timeline.
751 @ "0" there is no length limit.
752 @ (Property: "timeline-max-comment")</p>
753
754 @ <hr />
755 entry_attribute("Tooltip dwell time (milliseconds)", 6,
756 "timeline-dwelltime", "tdt", "100", 0);
757 @ <br>
758 entry_attribute("Tooltip close time (milliseconds)", 6,
759 "timeline-closetime", "tct", "250", 0);
760 @ <p>The <strong>dwell time</strong> defines how long the mouse pointer
761 @ should be stationary above an object of the graph before a tooltip
762 @ appears.<br>
763 @ The <strong>close time</strong> defines how long the mouse pointer
764 @ can be away from an object before a tooltip is closed.</p>
765 @ <p>Set <strong>dwell time</strong> to "0" to disable tooltips.<br>
766 @ Set <strong>close time</strong> to "0" to keep tooltips visible until
767 @ the mouse is clicked elsewhere.<p>
768 @ <p>(Properties: "timeline-dwelltime", "timeline-closetime")</p>
769
770 @ <hr />
771 @ <p><input type="submit" name="submit" value="Apply Changes" /></p>
772 @ </div></form>
773 db_end_transaction(0);
774 style_footer();
775
+20 -5
--- src/style.c
+++ src/style.c
@@ -82,13 +82,14 @@
8282
static unsigned adUnitFlags = 0;
8383
8484
/*
8585
** Flags for various javascript files needed prior to </body>
8686
*/
87
-static int needHrefJs = 0; /* href.js */
88
-static int needSortJs = 0; /* sorttable.js */
89
-static int needGraphJs = 0; /* graph.js */
87
+static int needHrefJs = 0; /* href.js */
88
+static int needSortJs = 0; /* sorttable.js */
89
+static int needGraphJs = 0; /* graph.js */
90
+static int needCopyBtnJs = 0; /* copybtn.js */
9091
9192
/*
9293
** Extra JS added to the end of the file.
9394
*/
9495
static Blob blobOnLoad = BLOB_INITIALIZER;
@@ -533,15 +534,22 @@
533534
void style_table_sorter(void){
534535
needSortJs = 1;
535536
}
536537
537538
/*
538
-** Indicate that the table-sorting javascript is needed.
539
+** Indicate that the timeline graph javascript is needed.
539540
*/
540541
void style_graph_generator(void){
541542
needGraphJs = 1;
542543
}
544
+
545
+/*
546
+** Indicate that the copy button javascript is needed.
547
+*/
548
+void style_copy_button(void){
549
+ needCopyBtnJs = 1;
550
+}
543551
544552
/*
545553
** Generate code to load a single javascript file
546554
*/
547555
void style_load_one_js_file(const char *zFile){
@@ -578,19 +586,26 @@
578586
int bMouseover = db_get_boolean("auto-hyperlink-mouseover",0);
579587
@ <script id='href-data' type='application/json'>\
580588
@ {"delay":%d(nDelay),"mouseover":%d(bMouseover)}</script>
581589
}
582590
@ <script nonce="%h(style_nonce())">
591
+ @ function debugMsg(msg){
592
+ @ var n = document.getElementById("debugMsg");
593
+ @ if(n){n.textContent=msg;}
594
+ @ }
583595
if( needHrefJs ){
584596
cgi_append_content(builtin_text("href.js"),-1);
585597
}
586598
if( needSortJs ){
587599
cgi_append_content(builtin_text("sorttable.js"),-1);
588600
}
589601
if( needGraphJs ){
590602
cgi_append_content(builtin_text("graph.js"),-1);
591603
}
604
+ if( needCopyBtnJs ){
605
+ cgi_append_content(builtin_text("copybtn.js"),-1);
606
+ }
592607
for(i=0; i<nJsToLoad; i++){
593608
cgi_append_content(builtin_text(azJsToLoad[i]),-1);
594609
}
595610
if( blob_size(&blobOnLoad)>0 ){
596611
@ window.onload = function(){
@@ -743,11 +758,11 @@
743758
if( zAd ){
744759
@ <div class="adunit_banner">
745760
cgi_append_content(zAd, -1);
746761
@ </div>
747762
}
748
- @ <div class="content">
763
+ @ <div class="content"><span id="debugMsg"></span>
749764
}
750765
cgi_destination(CGI_BODY);
751766
752767
if( sideboxUsed ){
753768
/* Put the footer at the bottom of the page.
754769
--- src/style.c
+++ src/style.c
@@ -82,13 +82,14 @@
82 static unsigned adUnitFlags = 0;
83
84 /*
85 ** Flags for various javascript files needed prior to </body>
86 */
87 static int needHrefJs = 0; /* href.js */
88 static int needSortJs = 0; /* sorttable.js */
89 static int needGraphJs = 0; /* graph.js */
 
90
91 /*
92 ** Extra JS added to the end of the file.
93 */
94 static Blob blobOnLoad = BLOB_INITIALIZER;
@@ -533,15 +534,22 @@
533 void style_table_sorter(void){
534 needSortJs = 1;
535 }
536
537 /*
538 ** Indicate that the table-sorting javascript is needed.
539 */
540 void style_graph_generator(void){
541 needGraphJs = 1;
542 }
 
 
 
 
 
 
 
543
544 /*
545 ** Generate code to load a single javascript file
546 */
547 void style_load_one_js_file(const char *zFile){
@@ -578,19 +586,26 @@
578 int bMouseover = db_get_boolean("auto-hyperlink-mouseover",0);
579 @ <script id='href-data' type='application/json'>\
580 @ {"delay":%d(nDelay),"mouseover":%d(bMouseover)}</script>
581 }
582 @ <script nonce="%h(style_nonce())">
 
 
 
 
583 if( needHrefJs ){
584 cgi_append_content(builtin_text("href.js"),-1);
585 }
586 if( needSortJs ){
587 cgi_append_content(builtin_text("sorttable.js"),-1);
588 }
589 if( needGraphJs ){
590 cgi_append_content(builtin_text("graph.js"),-1);
591 }
 
 
 
592 for(i=0; i<nJsToLoad; i++){
593 cgi_append_content(builtin_text(azJsToLoad[i]),-1);
594 }
595 if( blob_size(&blobOnLoad)>0 ){
596 @ window.onload = function(){
@@ -743,11 +758,11 @@
743 if( zAd ){
744 @ <div class="adunit_banner">
745 cgi_append_content(zAd, -1);
746 @ </div>
747 }
748 @ <div class="content">
749 }
750 cgi_destination(CGI_BODY);
751
752 if( sideboxUsed ){
753 /* Put the footer at the bottom of the page.
754
--- src/style.c
+++ src/style.c
@@ -82,13 +82,14 @@
82 static unsigned adUnitFlags = 0;
83
84 /*
85 ** Flags for various javascript files needed prior to </body>
86 */
87 static int needHrefJs = 0; /* href.js */
88 static int needSortJs = 0; /* sorttable.js */
89 static int needGraphJs = 0; /* graph.js */
90 static int needCopyBtnJs = 0; /* copybtn.js */
91
92 /*
93 ** Extra JS added to the end of the file.
94 */
95 static Blob blobOnLoad = BLOB_INITIALIZER;
@@ -533,15 +534,22 @@
534 void style_table_sorter(void){
535 needSortJs = 1;
536 }
537
538 /*
539 ** Indicate that the timeline graph javascript is needed.
540 */
541 void style_graph_generator(void){
542 needGraphJs = 1;
543 }
544
545 /*
546 ** Indicate that the copy button javascript is needed.
547 */
548 void style_copy_button(void){
549 needCopyBtnJs = 1;
550 }
551
552 /*
553 ** Generate code to load a single javascript file
554 */
555 void style_load_one_js_file(const char *zFile){
@@ -578,19 +586,26 @@
586 int bMouseover = db_get_boolean("auto-hyperlink-mouseover",0);
587 @ <script id='href-data' type='application/json'>\
588 @ {"delay":%d(nDelay),"mouseover":%d(bMouseover)}</script>
589 }
590 @ <script nonce="%h(style_nonce())">
591 @ function debugMsg(msg){
592 @ var n = document.getElementById("debugMsg");
593 @ if(n){n.textContent=msg;}
594 @ }
595 if( needHrefJs ){
596 cgi_append_content(builtin_text("href.js"),-1);
597 }
598 if( needSortJs ){
599 cgi_append_content(builtin_text("sorttable.js"),-1);
600 }
601 if( needGraphJs ){
602 cgi_append_content(builtin_text("graph.js"),-1);
603 }
604 if( needCopyBtnJs ){
605 cgi_append_content(builtin_text("copybtn.js"),-1);
606 }
607 for(i=0; i<nJsToLoad; i++){
608 cgi_append_content(builtin_text(azJsToLoad[i]),-1);
609 }
610 if( blob_size(&blobOnLoad)>0 ){
611 @ window.onload = function(){
@@ -743,11 +758,11 @@
758 if( zAd ){
759 @ <div class="adunit_banner">
760 cgi_append_content(zAd, -1);
761 @ </div>
762 }
763 @ <div class="content"><span id="debugMsg"></span>
764 }
765 cgi_destination(CGI_BODY);
766
767 if( sideboxUsed ){
768 /* Put the footer at the bottom of the page.
769
+61 -1
--- src/th_main.c
+++ src/th_main.c
@@ -267,11 +267,11 @@
267267
static int enableOutput = 1;
268268
269269
/*
270270
** TH1 command: enable_output BOOLEAN
271271
**
272
-** Enable or disable the puts and wiki commands.
272
+** Enable or disable the puts, wiki, combobox and copybtn commands.
273273
*/
274274
static int enableOutputCmd(
275275
Th_Interp *interp,
276276
void *p,
277277
int argc,
@@ -990,10 +990,69 @@
990990
sendText("</select>", -1, 0);
991991
Th_Free(interp, azElem);
992992
}
993993
return TH_OK;
994994
}
995
+
996
+/*
997
+** TH1 command: copybtn TARGETID TEXT ?COPYLENGTH?
998
+**
999
+** Output TEXT with a click-to-copy button next to it. Loads the copybtn.js
1000
+** Javascript module, and generates HTML elements with the following IDs:
1001
+**
1002
+** TARGETID: The <span> wrapper around TEXT.
1003
+** copy-TARGETID: The <span> for the copy button.
1004
+**
1005
+** The optional COPYLENGTH argument defines the length of the substring of TEXT
1006
+** copied to clipboard:
1007
+**
1008
+** <= 0: No limit (default if the argument is omitted).
1009
+** >= 3: Truncate TEXT after COPYLENGTH (single-byte) characters.
1010
+** 1: Use the "hash-digits" setting as the limit.
1011
+** 2: Use the length appropriate for URLs as the limit (defined at
1012
+** compile-time by FOSSIL_HASH_DIGITS_URL, defaults to 16).
1013
+*/
1014
+static int copybtnCmd(
1015
+ Th_Interp *interp,
1016
+ void *p,
1017
+ int argc,
1018
+ const char **argv,
1019
+ int *argl
1020
+){
1021
+ if( argc!=3 && argc!=4 ){
1022
+ return Th_WrongNumArgs(interp, "copybtn TARGETID TEXT COPYLENGTH");
1023
+ }
1024
+ if( enableOutput ){
1025
+ int copylength = 0;
1026
+ char *zTargetId, *zText, *zResult;
1027
+ if( argc==4 && Th_ToInt(interp, argv[3], argl[3], &copylength) ){
1028
+ return TH_ERROR;
1029
+ }
1030
+ if( copylength==1 ) copylength = hash_digits(0);
1031
+ else if( copylength==2 ) copylength = hash_digits(1);
1032
+ zTargetId = htmlize((char*)argv[1], argl[1]);
1033
+ zText = htmlize((char*)argv[2], argl[2]);
1034
+ zResult = mprintf(
1035
+ "<span "
1036
+ "class=\"copy-button\" "
1037
+ "id=\"copy-%s\" "
1038
+ "data-copytarget=\"%s\" "
1039
+ "data-copylength=\"%d\">"
1040
+ "</span>"
1041
+ "&nbsp;"
1042
+ "<span id=\"%s\">"
1043
+ "%s"
1044
+ "</span>",
1045
+ zTargetId, zTargetId, copylength, zTargetId, zText);
1046
+ free(zTargetId);
1047
+ free(zText);
1048
+ style_copy_button();
1049
+ sendText(zResult, -1, 0);
1050
+ free(zResult);
1051
+ }
1052
+ return TH_OK;
1053
+}
9951054
9961055
/*
9971056
** TH1 command: linecount STRING MAX MIN
9981057
**
9991058
** Return one more than the number of \n characters in STRING. But
@@ -2024,10 +2083,11 @@
20242083
{"anycap", anycapCmd, 0},
20252084
{"artifact", artifactCmd, 0},
20262085
{"cgiHeaderLine", cgiHeaderLineCmd, 0},
20272086
{"checkout", checkoutCmd, 0},
20282087
{"combobox", comboboxCmd, 0},
2088
+ {"copybtn", copybtnCmd, 0},
20292089
{"date", dateCmd, 0},
20302090
{"decorate", wikiCmd, (void*)&aFlags[2]},
20312091
{"dir", dirCmd, 0},
20322092
{"enable_output", enableOutputCmd, 0},
20332093
{"encode64", encode64Cmd, 0},
20342094
--- src/th_main.c
+++ src/th_main.c
@@ -267,11 +267,11 @@
267 static int enableOutput = 1;
268
269 /*
270 ** TH1 command: enable_output BOOLEAN
271 **
272 ** Enable or disable the puts and wiki commands.
273 */
274 static int enableOutputCmd(
275 Th_Interp *interp,
276 void *p,
277 int argc,
@@ -990,10 +990,69 @@
990 sendText("</select>", -1, 0);
991 Th_Free(interp, azElem);
992 }
993 return TH_OK;
994 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
995
996 /*
997 ** TH1 command: linecount STRING MAX MIN
998 **
999 ** Return one more than the number of \n characters in STRING. But
@@ -2024,10 +2083,11 @@
2024 {"anycap", anycapCmd, 0},
2025 {"artifact", artifactCmd, 0},
2026 {"cgiHeaderLine", cgiHeaderLineCmd, 0},
2027 {"checkout", checkoutCmd, 0},
2028 {"combobox", comboboxCmd, 0},
 
2029 {"date", dateCmd, 0},
2030 {"decorate", wikiCmd, (void*)&aFlags[2]},
2031 {"dir", dirCmd, 0},
2032 {"enable_output", enableOutputCmd, 0},
2033 {"encode64", encode64Cmd, 0},
2034
--- src/th_main.c
+++ src/th_main.c
@@ -267,11 +267,11 @@
267 static int enableOutput = 1;
268
269 /*
270 ** TH1 command: enable_output BOOLEAN
271 **
272 ** Enable or disable the puts, wiki, combobox and copybtn commands.
273 */
274 static int enableOutputCmd(
275 Th_Interp *interp,
276 void *p,
277 int argc,
@@ -990,10 +990,69 @@
990 sendText("</select>", -1, 0);
991 Th_Free(interp, azElem);
992 }
993 return TH_OK;
994 }
995
996 /*
997 ** TH1 command: copybtn TARGETID TEXT ?COPYLENGTH?
998 **
999 ** Output TEXT with a click-to-copy button next to it. Loads the copybtn.js
1000 ** Javascript module, and generates HTML elements with the following IDs:
1001 **
1002 ** TARGETID: The <span> wrapper around TEXT.
1003 ** copy-TARGETID: The <span> for the copy button.
1004 **
1005 ** The optional COPYLENGTH argument defines the length of the substring of TEXT
1006 ** copied to clipboard:
1007 **
1008 ** <= 0: No limit (default if the argument is omitted).
1009 ** >= 3: Truncate TEXT after COPYLENGTH (single-byte) characters.
1010 ** 1: Use the "hash-digits" setting as the limit.
1011 ** 2: Use the length appropriate for URLs as the limit (defined at
1012 ** compile-time by FOSSIL_HASH_DIGITS_URL, defaults to 16).
1013 */
1014 static int copybtnCmd(
1015 Th_Interp *interp,
1016 void *p,
1017 int argc,
1018 const char **argv,
1019 int *argl
1020 ){
1021 if( argc!=3 && argc!=4 ){
1022 return Th_WrongNumArgs(interp, "copybtn TARGETID TEXT COPYLENGTH");
1023 }
1024 if( enableOutput ){
1025 int copylength = 0;
1026 char *zTargetId, *zText, *zResult;
1027 if( argc==4 && Th_ToInt(interp, argv[3], argl[3], &copylength) ){
1028 return TH_ERROR;
1029 }
1030 if( copylength==1 ) copylength = hash_digits(0);
1031 else if( copylength==2 ) copylength = hash_digits(1);
1032 zTargetId = htmlize((char*)argv[1], argl[1]);
1033 zText = htmlize((char*)argv[2], argl[2]);
1034 zResult = mprintf(
1035 "<span "
1036 "class=\"copy-button\" "
1037 "id=\"copy-%s\" "
1038 "data-copytarget=\"%s\" "
1039 "data-copylength=\"%d\">"
1040 "</span>"
1041 "&nbsp;"
1042 "<span id=\"%s\">"
1043 "%s"
1044 "</span>",
1045 zTargetId, zTargetId, copylength, zTargetId, zText);
1046 free(zTargetId);
1047 free(zText);
1048 style_copy_button();
1049 sendText(zResult, -1, 0);
1050 free(zResult);
1051 }
1052 return TH_OK;
1053 }
1054
1055 /*
1056 ** TH1 command: linecount STRING MAX MIN
1057 **
1058 ** Return one more than the number of \n characters in STRING. But
@@ -2024,10 +2083,11 @@
2083 {"anycap", anycapCmd, 0},
2084 {"artifact", artifactCmd, 0},
2085 {"cgiHeaderLine", cgiHeaderLineCmd, 0},
2086 {"checkout", checkoutCmd, 0},
2087 {"combobox", comboboxCmd, 0},
2088 {"copybtn", copybtnCmd, 0},
2089 {"date", dateCmd, 0},
2090 {"decorate", wikiCmd, (void*)&aFlags[2]},
2091 {"dir", dirCmd, 0},
2092 {"enable_output", enableOutputCmd, 0},
2093 {"encode64", encode64Cmd, 0},
2094
+24 -4
--- src/timeline.c
+++ src/timeline.c
@@ -830,10 +830,12 @@
830830
int colorGraph; /* Use colors for graph lines */
831831
int iTopRow; /* Index of the top row of the graph */
832832
int fileDiff; /* True for file diff. False for check-in diff */
833833
int omitDescenders; /* True to omit descenders */
834834
int scrollToSelect; /* True to scroll to the selection */
835
+ int dwellTimeout; /* Milliseconds to wait for tooltips to show */
836
+ int closeTimeout; /* Milliseconds to wait for tooltips to close */
835837
u8 *aiMap; /* The rail map */
836838
837839
iRailPitch = atoi(PD("railpitch","0"));
838840
showArrowheads = skin_detail_boolean("timeline-arrowheads");
839841
circleNodes = skin_detail_boolean("timeline-circle-nodes");
@@ -840,10 +842,12 @@
840842
colorGraph = skin_detail_boolean("timeline-color-graph-lines");
841843
iTopRow = pGraph->pFirst ? pGraph->pFirst->idx : 0;
842844
omitDescenders = (tmFlags & TIMELINE_DISJOINT)!=0;
843845
fileDiff = (tmFlags & TIMELINE_FILEDIFF)!=0;
844846
scrollToSelect = (tmFlags & TIMELINE_NOSCROLL)==0;
847
+ dwellTimeout = atoi(db_get("timeline-dwelltime","100"));
848
+ closeTimeout = atoi(db_get("timeline-closetime","250"));
845849
@ <script id='timeline-data-%d(iTableId)' type='application/json'>{
846850
@ "iTableId": %d(iTableId),
847851
@ "circleNodes": %d(circleNodes),
848852
@ "showArrowheads": %d(showArrowheads),
849853
@ "iRailPitch": %d(iRailPitch),
@@ -853,10 +857,13 @@
853857
@ "omitDescenders": %d(omitDescenders),
854858
@ "fileDiff": %d(fileDiff),
855859
@ "scrollToSelect": %d(scrollToSelect),
856860
@ "nrail": %d(pGraph->mxRail+1),
857861
@ "baseUrl": "%R",
862
+ @ "dwellTimeout": %d(dwellTimeout),
863
+ @ "closeTimeout": %d(closeTimeout),
864
+ @ "hashDigit": %d(hash_digits(1)),
858865
@ "bottomRowId": "btm-%d(iTableId)",
859866
if( pGraph->nRow==0 ){
860867
@ "rowinfo": null
861868
}else{
862869
@ "rowinfo": [
@@ -904,10 +911,11 @@
904911
** the screen. This array is omitted if there are no inbound
905912
** merges.
906913
** ci: "cherrypick-in". Like "mi" except for cherrypick merges.
907914
** omitted if there are no cherrypick merges.
908915
** h: The artifact hash of the object being graphed
916
+ * br: The branch to which the artifact belongs
909917
*/
910918
aiMap = pGraph->aiRailMap;
911919
for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){
912920
int k = 0;
913921
cgi_printf("{\"id\":%d,", pRow->idx);
@@ -978,15 +986,17 @@
978986
cgi_printf("%c%d", cSep, mi);
979987
cSep = ',';
980988
}
981989
}
982990
if( k ) cgi_printf("],");
991
+ cgi_printf("\"br\":\"%j\",", pRow->zBranch ? pRow->zBranch : "");
983992
cgi_printf("\"h\":\"%!S\"}%s",
984993
pRow->zUuid, pRow->pNext ? ",\n" : "]\n");
985994
}
986995
@ }</script>
987996
style_graph_generator();
997
+ style_copy_button(); /* Dependency: graph.js requires copybtn.js. */
988998
graph_free(pGraph);
989999
}
9901000
}
9911001
9921002
/*
@@ -1478,10 +1488,11 @@
14781488
** Query parameters:
14791489
**
14801490
** a=TIMEORTAG After this event
14811491
** b=TIMEORTAG Before this event
14821492
** c=TIMEORTAG "Circa" this event
1493
+** cf=FILEHASH "Circa" the first use of the file with FILEHASH
14831494
** m=TIMEORTAG Mark this event
14841495
** n=COUNT Maximum number of events. "all" for no limit
14851496
** p=CHECKIN Parents and ancestors of CHECKIN
14861497
** d=CHECKIN Children and descendants of CHECKIN
14871498
** dp=CHECKIN The same as d=CHECKIN&p=CHECKIN
@@ -1641,15 +1652,24 @@
16411652
}
16421653
cookie_render();
16431654
url_initialize(&url, "timeline");
16441655
cgi_query_parameters_to_url(&url);
16451656
1646
- /* Convert r=TAG to t=TAG&rel. */
1657
+ /* Convert the cf=FILEHASH query parameter into a c=CHECKINHASH value */
1658
+ if( P("cf")!=0 ){
1659
+ zCirca = db_text(0,
1660
+ "SELECT (SELECT uuid FROM blob WHERE rid=mlink.mid)"
1661
+ " FROM mlink, event"
1662
+ " WHERE mlink.fid=(SELECT rid FROM blob WHERE uuid LIKE '%q%%')"
1663
+ " AND event.objid=mlink.mid"
1664
+ " ORDER BY event.mtime LIMIT 1",
1665
+ P("cf")
1666
+ );
1667
+ }
1668
+
1669
+ /* r=TAG works like a combination of t=TAG & rel */
16471670
if( zBrName && !related ){
1648
- cgi_delete_query_parameter("r");
1649
- cgi_set_query_parameter("t", zBrName);
1650
- cgi_set_query_parameter("rel", "1");
16511671
zTagName = zBrName;
16521672
related = 1;
16531673
zType = "ci";
16541674
}
16551675
16561676
--- src/timeline.c
+++ src/timeline.c
@@ -830,10 +830,12 @@
830 int colorGraph; /* Use colors for graph lines */
831 int iTopRow; /* Index of the top row of the graph */
832 int fileDiff; /* True for file diff. False for check-in diff */
833 int omitDescenders; /* True to omit descenders */
834 int scrollToSelect; /* True to scroll to the selection */
 
 
835 u8 *aiMap; /* The rail map */
836
837 iRailPitch = atoi(PD("railpitch","0"));
838 showArrowheads = skin_detail_boolean("timeline-arrowheads");
839 circleNodes = skin_detail_boolean("timeline-circle-nodes");
@@ -840,10 +842,12 @@
840 colorGraph = skin_detail_boolean("timeline-color-graph-lines");
841 iTopRow = pGraph->pFirst ? pGraph->pFirst->idx : 0;
842 omitDescenders = (tmFlags & TIMELINE_DISJOINT)!=0;
843 fileDiff = (tmFlags & TIMELINE_FILEDIFF)!=0;
844 scrollToSelect = (tmFlags & TIMELINE_NOSCROLL)==0;
 
 
845 @ <script id='timeline-data-%d(iTableId)' type='application/json'>{
846 @ "iTableId": %d(iTableId),
847 @ "circleNodes": %d(circleNodes),
848 @ "showArrowheads": %d(showArrowheads),
849 @ "iRailPitch": %d(iRailPitch),
@@ -853,10 +857,13 @@
853 @ "omitDescenders": %d(omitDescenders),
854 @ "fileDiff": %d(fileDiff),
855 @ "scrollToSelect": %d(scrollToSelect),
856 @ "nrail": %d(pGraph->mxRail+1),
857 @ "baseUrl": "%R",
 
 
 
858 @ "bottomRowId": "btm-%d(iTableId)",
859 if( pGraph->nRow==0 ){
860 @ "rowinfo": null
861 }else{
862 @ "rowinfo": [
@@ -904,10 +911,11 @@
904 ** the screen. This array is omitted if there are no inbound
905 ** merges.
906 ** ci: "cherrypick-in". Like "mi" except for cherrypick merges.
907 ** omitted if there are no cherrypick merges.
908 ** h: The artifact hash of the object being graphed
 
909 */
910 aiMap = pGraph->aiRailMap;
911 for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){
912 int k = 0;
913 cgi_printf("{\"id\":%d,", pRow->idx);
@@ -978,15 +986,17 @@
978 cgi_printf("%c%d", cSep, mi);
979 cSep = ',';
980 }
981 }
982 if( k ) cgi_printf("],");
 
983 cgi_printf("\"h\":\"%!S\"}%s",
984 pRow->zUuid, pRow->pNext ? ",\n" : "]\n");
985 }
986 @ }</script>
987 style_graph_generator();
 
988 graph_free(pGraph);
989 }
990 }
991
992 /*
@@ -1478,10 +1488,11 @@
1478 ** Query parameters:
1479 **
1480 ** a=TIMEORTAG After this event
1481 ** b=TIMEORTAG Before this event
1482 ** c=TIMEORTAG "Circa" this event
 
1483 ** m=TIMEORTAG Mark this event
1484 ** n=COUNT Maximum number of events. "all" for no limit
1485 ** p=CHECKIN Parents and ancestors of CHECKIN
1486 ** d=CHECKIN Children and descendants of CHECKIN
1487 ** dp=CHECKIN The same as d=CHECKIN&p=CHECKIN
@@ -1641,15 +1652,24 @@
1641 }
1642 cookie_render();
1643 url_initialize(&url, "timeline");
1644 cgi_query_parameters_to_url(&url);
1645
1646 /* Convert r=TAG to t=TAG&rel. */
 
 
 
 
 
 
 
 
 
 
 
 
1647 if( zBrName && !related ){
1648 cgi_delete_query_parameter("r");
1649 cgi_set_query_parameter("t", zBrName);
1650 cgi_set_query_parameter("rel", "1");
1651 zTagName = zBrName;
1652 related = 1;
1653 zType = "ci";
1654 }
1655
1656
--- src/timeline.c
+++ src/timeline.c
@@ -830,10 +830,12 @@
830 int colorGraph; /* Use colors for graph lines */
831 int iTopRow; /* Index of the top row of the graph */
832 int fileDiff; /* True for file diff. False for check-in diff */
833 int omitDescenders; /* True to omit descenders */
834 int scrollToSelect; /* True to scroll to the selection */
835 int dwellTimeout; /* Milliseconds to wait for tooltips to show */
836 int closeTimeout; /* Milliseconds to wait for tooltips to close */
837 u8 *aiMap; /* The rail map */
838
839 iRailPitch = atoi(PD("railpitch","0"));
840 showArrowheads = skin_detail_boolean("timeline-arrowheads");
841 circleNodes = skin_detail_boolean("timeline-circle-nodes");
@@ -840,10 +842,12 @@
842 colorGraph = skin_detail_boolean("timeline-color-graph-lines");
843 iTopRow = pGraph->pFirst ? pGraph->pFirst->idx : 0;
844 omitDescenders = (tmFlags & TIMELINE_DISJOINT)!=0;
845 fileDiff = (tmFlags & TIMELINE_FILEDIFF)!=0;
846 scrollToSelect = (tmFlags & TIMELINE_NOSCROLL)==0;
847 dwellTimeout = atoi(db_get("timeline-dwelltime","100"));
848 closeTimeout = atoi(db_get("timeline-closetime","250"));
849 @ <script id='timeline-data-%d(iTableId)' type='application/json'>{
850 @ "iTableId": %d(iTableId),
851 @ "circleNodes": %d(circleNodes),
852 @ "showArrowheads": %d(showArrowheads),
853 @ "iRailPitch": %d(iRailPitch),
@@ -853,10 +857,13 @@
857 @ "omitDescenders": %d(omitDescenders),
858 @ "fileDiff": %d(fileDiff),
859 @ "scrollToSelect": %d(scrollToSelect),
860 @ "nrail": %d(pGraph->mxRail+1),
861 @ "baseUrl": "%R",
862 @ "dwellTimeout": %d(dwellTimeout),
863 @ "closeTimeout": %d(closeTimeout),
864 @ "hashDigit": %d(hash_digits(1)),
865 @ "bottomRowId": "btm-%d(iTableId)",
866 if( pGraph->nRow==0 ){
867 @ "rowinfo": null
868 }else{
869 @ "rowinfo": [
@@ -904,10 +911,11 @@
911 ** the screen. This array is omitted if there are no inbound
912 ** merges.
913 ** ci: "cherrypick-in". Like "mi" except for cherrypick merges.
914 ** omitted if there are no cherrypick merges.
915 ** h: The artifact hash of the object being graphed
916 * br: The branch to which the artifact belongs
917 */
918 aiMap = pGraph->aiRailMap;
919 for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){
920 int k = 0;
921 cgi_printf("{\"id\":%d,", pRow->idx);
@@ -978,15 +986,17 @@
986 cgi_printf("%c%d", cSep, mi);
987 cSep = ',';
988 }
989 }
990 if( k ) cgi_printf("],");
991 cgi_printf("\"br\":\"%j\",", pRow->zBranch ? pRow->zBranch : "");
992 cgi_printf("\"h\":\"%!S\"}%s",
993 pRow->zUuid, pRow->pNext ? ",\n" : "]\n");
994 }
995 @ }</script>
996 style_graph_generator();
997 style_copy_button(); /* Dependency: graph.js requires copybtn.js. */
998 graph_free(pGraph);
999 }
1000 }
1001
1002 /*
@@ -1478,10 +1488,11 @@
1488 ** Query parameters:
1489 **
1490 ** a=TIMEORTAG After this event
1491 ** b=TIMEORTAG Before this event
1492 ** c=TIMEORTAG "Circa" this event
1493 ** cf=FILEHASH "Circa" the first use of the file with FILEHASH
1494 ** m=TIMEORTAG Mark this event
1495 ** n=COUNT Maximum number of events. "all" for no limit
1496 ** p=CHECKIN Parents and ancestors of CHECKIN
1497 ** d=CHECKIN Children and descendants of CHECKIN
1498 ** dp=CHECKIN The same as d=CHECKIN&p=CHECKIN
@@ -1641,15 +1652,24 @@
1652 }
1653 cookie_render();
1654 url_initialize(&url, "timeline");
1655 cgi_query_parameters_to_url(&url);
1656
1657 /* Convert the cf=FILEHASH query parameter into a c=CHECKINHASH value */
1658 if( P("cf")!=0 ){
1659 zCirca = db_text(0,
1660 "SELECT (SELECT uuid FROM blob WHERE rid=mlink.mid)"
1661 " FROM mlink, event"
1662 " WHERE mlink.fid=(SELECT rid FROM blob WHERE uuid LIKE '%q%%')"
1663 " AND event.objid=mlink.mid"
1664 " ORDER BY event.mtime LIMIT 1",
1665 P("cf")
1666 );
1667 }
1668
1669 /* r=TAG works like a combination of t=TAG & rel */
1670 if( zBrName && !related ){
 
 
 
1671 zTagName = zBrName;
1672 related = 1;
1673 zType = "ci";
1674 }
1675
1676
+4 -4
--- src/tktsetup.c
+++ src/tktsetup.c
@@ -446,16 +446,16 @@
446446
static const char zDefaultView[] =
447447
@ <table cellpadding="5">
448448
@ <tr><td class="tktDspLabel">Ticket&nbsp;UUID:</td>
449449
@ <th1>
450450
@ if {[info exists tkt_uuid]} {
451
+@ html "<td class='tktDspValue' colspan='3'>"
452
+@ copybtn tkt_uuid $tkt_uuid 1
451453
@ if {[hascap s]} {
452
-@ html "<td class='tktDspValue' colspan='3'>$tkt_uuid "
453
-@ html "($tkt_id)</td></tr>\n"
454
-@ } else {
455
-@ html "<td class='tktDspValue' colspan='3'>$tkt_uuid</td></tr>\n"
454
+@ html " ($tkt_id)"
456455
@ }
456
+@ html "</td></tr>\n"
457457
@ } else {
458458
@ if {[hascap s]} {
459459
@ html "<td class='tktDspValue' colspan='3'>Deleted "
460460
@ html "(0)</td></tr>\n"
461461
@ } else {
462462
--- src/tktsetup.c
+++ src/tktsetup.c
@@ -446,16 +446,16 @@
446 static const char zDefaultView[] =
447 @ <table cellpadding="5">
448 @ <tr><td class="tktDspLabel">Ticket&nbsp;UUID:</td>
449 @ <th1>
450 @ if {[info exists tkt_uuid]} {
 
 
451 @ if {[hascap s]} {
452 @ html "<td class='tktDspValue' colspan='3'>$tkt_uuid "
453 @ html "($tkt_id)</td></tr>\n"
454 @ } else {
455 @ html "<td class='tktDspValue' colspan='3'>$tkt_uuid</td></tr>\n"
456 @ }
 
457 @ } else {
458 @ if {[hascap s]} {
459 @ html "<td class='tktDspValue' colspan='3'>Deleted "
460 @ html "(0)</td></tr>\n"
461 @ } else {
462
--- src/tktsetup.c
+++ src/tktsetup.c
@@ -446,16 +446,16 @@
446 static const char zDefaultView[] =
447 @ <table cellpadding="5">
448 @ <tr><td class="tktDspLabel">Ticket&nbsp;UUID:</td>
449 @ <th1>
450 @ if {[info exists tkt_uuid]} {
451 @ html "<td class='tktDspValue' colspan='3'>"
452 @ copybtn tkt_uuid $tkt_uuid 1
453 @ if {[hascap s]} {
454 @ html " ($tkt_id)"
 
 
 
455 @ }
456 @ html "</td></tr>\n"
457 @ } else {
458 @ if {[hascap s]} {
459 @ html "<td class='tktDspValue' colspan='3'>Deleted "
460 @ html "(0)</td></tr>\n"
461 @ } else {
462
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -632,10 +632,11 @@
632632
$(SRCDIR)/../skins/xekri/css.txt \
633633
$(SRCDIR)/../skins/xekri/details.txt \
634634
$(SRCDIR)/../skins/xekri/footer.txt \
635635
$(SRCDIR)/../skins/xekri/header.txt \
636636
$(SRCDIR)/ci_edit.js \
637
+ $(SRCDIR)/copybtn.js \
637638
$(SRCDIR)/diff.tcl \
638639
$(SRCDIR)/forum.js \
639640
$(SRCDIR)/graph.js \
640641
$(SRCDIR)/href.js \
641642
$(SRCDIR)/login.js \
642643
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -632,10 +632,11 @@
632 $(SRCDIR)/../skins/xekri/css.txt \
633 $(SRCDIR)/../skins/xekri/details.txt \
634 $(SRCDIR)/../skins/xekri/footer.txt \
635 $(SRCDIR)/../skins/xekri/header.txt \
636 $(SRCDIR)/ci_edit.js \
 
637 $(SRCDIR)/diff.tcl \
638 $(SRCDIR)/forum.js \
639 $(SRCDIR)/graph.js \
640 $(SRCDIR)/href.js \
641 $(SRCDIR)/login.js \
642
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -632,10 +632,11 @@
632 $(SRCDIR)/../skins/xekri/css.txt \
633 $(SRCDIR)/../skins/xekri/details.txt \
634 $(SRCDIR)/../skins/xekri/footer.txt \
635 $(SRCDIR)/../skins/xekri/header.txt \
636 $(SRCDIR)/ci_edit.js \
637 $(SRCDIR)/copybtn.js \
638 $(SRCDIR)/diff.tcl \
639 $(SRCDIR)/forum.js \
640 $(SRCDIR)/graph.js \
641 $(SRCDIR)/href.js \
642 $(SRCDIR)/login.js \
643
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -535,10 +535,11 @@
535535
$(SRCDIR)\..\skins\xekri\css.txt \
536536
$(SRCDIR)\..\skins\xekri\details.txt \
537537
$(SRCDIR)\..\skins\xekri\footer.txt \
538538
$(SRCDIR)\..\skins\xekri\header.txt \
539539
$(SRCDIR)\ci_edit.js \
540
+ $(SRCDIR)\copybtn.js \
540541
$(SRCDIR)\diff.tcl \
541542
$(SRCDIR)\forum.js \
542543
$(SRCDIR)\graph.js \
543544
$(SRCDIR)\href.js \
544545
$(SRCDIR)\login.js \
545546
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -535,10 +535,11 @@
535 $(SRCDIR)\..\skins\xekri\css.txt \
536 $(SRCDIR)\..\skins\xekri\details.txt \
537 $(SRCDIR)\..\skins\xekri\footer.txt \
538 $(SRCDIR)\..\skins\xekri\header.txt \
539 $(SRCDIR)\ci_edit.js \
 
540 $(SRCDIR)\diff.tcl \
541 $(SRCDIR)\forum.js \
542 $(SRCDIR)\graph.js \
543 $(SRCDIR)\href.js \
544 $(SRCDIR)\login.js \
545
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -535,10 +535,11 @@
535 $(SRCDIR)\..\skins\xekri\css.txt \
536 $(SRCDIR)\..\skins\xekri\details.txt \
537 $(SRCDIR)\..\skins\xekri\footer.txt \
538 $(SRCDIR)\..\skins\xekri\header.txt \
539 $(SRCDIR)\ci_edit.js \
540 $(SRCDIR)\copybtn.js \
541 $(SRCDIR)\diff.tcl \
542 $(SRCDIR)\forum.js \
543 $(SRCDIR)\graph.js \
544 $(SRCDIR)\href.js \
545 $(SRCDIR)\login.js \
546
+22 -1
--- www/th1.md
+++ www/th1.md
@@ -170,10 +170,11 @@
170170
* anycap
171171
* artifact
172172
* cgiHeaderLine
173173
* checkout
174174
* combobox
175
+ * copybtn
175176
* date
176177
* decorate
177178
* dir
178179
* enable\_output
179180
* encode64
@@ -277,10 +278,30 @@
277278
CGI parameter and the name of a variable that contains the currently
278279
selected value. TEXT-LIST is a list of possible values for the
279280
combobox. NUMLINES is 1 for a true combobox. If NUMLINES is greater
280281
than one then the display is a listbox with the number of lines given.
281282
283
+<a name="copybtn"></a>TH1 copybtn Command
284
+-----------------------------------------
285
+
286
+ * copybtn TARGETID TEXT ?COPYLENGTH?
287
+
288
+Output TEXT with a click-to-copy button next to it. Loads the copybtn.js
289
+Javascript module, and generates HTML elements with the following IDs:
290
+
291
+ * TARGETID: The `<span>` wrapper around TEXT.
292
+ * copy-TARGETID: The `<span>` for the copy button.
293
+
294
+The optional COPYLENGTH argument defines the length of the substring of TEXT
295
+copied to clipboard:
296
+
297
+ * <= 0: No limit (default if the argument is omitted).
298
+ * >= 3: Truncate TEXT after COPYLENGTH (single-byte) characters.
299
+ * 1: Use the "hash-digits" setting as the limit.
300
+ * 2: Use the length appropriate for URLs as the limit (defined at
301
+ compile-time by `FOSSIL_HASH_DIGITS_URL`, defaults to 16).
302
+
282303
<a name="date"></a>TH1 date Command
283304
-----------------------------------
284305
285306
* date ?-local?
286307
@@ -310,11 +331,11 @@
310331
<a name="enable_output"></a>TH1 enable\_output Command
311332
------------------------------------------------------
312333
313334
* enable\_output BOOLEAN
314335
315
-Enable or disable sending output when the combobox, puts, or wiki
336
+Enable or disable sending output when the combobox, copybtn, puts, or wiki
316337
commands are used.
317338
318339
<a name="encode64"></a>TH1 encode64 Command
319340
-------------------------------------------
320341
321342
--- www/th1.md
+++ www/th1.md
@@ -170,10 +170,11 @@
170 * anycap
171 * artifact
172 * cgiHeaderLine
173 * checkout
174 * combobox
 
175 * date
176 * decorate
177 * dir
178 * enable\_output
179 * encode64
@@ -277,10 +278,30 @@
277 CGI parameter and the name of a variable that contains the currently
278 selected value. TEXT-LIST is a list of possible values for the
279 combobox. NUMLINES is 1 for a true combobox. If NUMLINES is greater
280 than one then the display is a listbox with the number of lines given.
281
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
282 <a name="date"></a>TH1 date Command
283 -----------------------------------
284
285 * date ?-local?
286
@@ -310,11 +331,11 @@
310 <a name="enable_output"></a>TH1 enable\_output Command
311 ------------------------------------------------------
312
313 * enable\_output BOOLEAN
314
315 Enable or disable sending output when the combobox, puts, or wiki
316 commands are used.
317
318 <a name="encode64"></a>TH1 encode64 Command
319 -------------------------------------------
320
321
--- www/th1.md
+++ www/th1.md
@@ -170,10 +170,11 @@
170 * anycap
171 * artifact
172 * cgiHeaderLine
173 * checkout
174 * combobox
175 * copybtn
176 * date
177 * decorate
178 * dir
179 * enable\_output
180 * encode64
@@ -277,10 +278,30 @@
278 CGI parameter and the name of a variable that contains the currently
279 selected value. TEXT-LIST is a list of possible values for the
280 combobox. NUMLINES is 1 for a true combobox. If NUMLINES is greater
281 than one then the display is a listbox with the number of lines given.
282
283 <a name="copybtn"></a>TH1 copybtn Command
284 -----------------------------------------
285
286 * copybtn TARGETID TEXT ?COPYLENGTH?
287
288 Output TEXT with a click-to-copy button next to it. Loads the copybtn.js
289 Javascript module, and generates HTML elements with the following IDs:
290
291 * TARGETID: The `<span>` wrapper around TEXT.
292 * copy-TARGETID: The `<span>` for the copy button.
293
294 The optional COPYLENGTH argument defines the length of the substring of TEXT
295 copied to clipboard:
296
297 * <= 0: No limit (default if the argument is omitted).
298 * >= 3: Truncate TEXT after COPYLENGTH (single-byte) characters.
299 * 1: Use the "hash-digits" setting as the limit.
300 * 2: Use the length appropriate for URLs as the limit (defined at
301 compile-time by `FOSSIL_HASH_DIGITS_URL`, defaults to 16).
302
303 <a name="date"></a>TH1 date Command
304 -----------------------------------
305
306 * date ?-local?
307
@@ -310,11 +331,11 @@
331 <a name="enable_output"></a>TH1 enable\_output Command
332 ------------------------------------------------------
333
334 * enable\_output BOOLEAN
335
336 Enable or disable sending output when the combobox, copybtn, puts, or wiki
337 commands are used.
338
339 <a name="encode64"></a>TH1 encode64 Command
340 -------------------------------------------
341
342

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button