Fossil

Check-in [6908832c]
Login

Check-in [6908832c]

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

Overview
Comment:Merge all the recent tool-tip experiments into trunk.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 6908832cf4cf8bd8a35b584f0e985718fc2b979e71bf7c260289f3fad43f82ef
User & Date: drh 2019-06-03 13:30:43
Context
2019-06-05
19:45
Update the built-in SQLite to the latest 3.29.0 alpha. ... (check-in: 9c128d2e user: drh tags: trunk)
07:17
More consistent naming scheme for IDs of copy buttons and their target elements. ... (check-in: b19a9826 user: florian tags: copybtn.js-tweaks)
2019-06-03
13:30
Merge all the recent tool-tip experiments into trunk. ... (check-in: 6908832c user: drh tags: trunk)
09:10
Cherry-pick [787650c36a]: Minimize impact of the SVG icon on line height. ... (Closed-Leaf check-in: 0a9f12ce user: florian tags: copybtn.js-demonstration)
2019-05-29
13:27
Activate the load limiter for the /vdiff page. ... (check-in: 76f4e009 user: drh tags: trunk)
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/configure.c.

92
93
94
95
96
97
98



99
100


101
102
103
104
105
106
107
  { "footer",                 CONFIGSET_SKIN },
  { "details",                CONFIGSET_SKIN },
  { "logo-mimetype",          CONFIGSET_SKIN },
  { "logo-image",             CONFIGSET_SKIN },
  { "background-mimetype",    CONFIGSET_SKIN },
  { "background-image",       CONFIGSET_SKIN },
  { "timeline-block-markup",  CONFIGSET_SKIN },



  { "timeline-max-comment",   CONFIGSET_SKIN },
  { "timeline-plaintext",     CONFIGSET_SKIN },


  { "adunit",                 CONFIGSET_SKIN },
  { "adunit-omit-if-admin",   CONFIGSET_SKIN },
  { "adunit-omit-if-user",    CONFIGSET_SKIN },
  { "sitemap-docidx",         CONFIGSET_SKIN },
  { "sitemap-download",       CONFIGSET_SKIN },
  { "sitemap-license",        CONFIGSET_SKIN },
  { "sitemap-contact",        CONFIGSET_SKIN },







>
>
>


>
>







92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
  { "footer",                 CONFIGSET_SKIN },
  { "details",                CONFIGSET_SKIN },
  { "logo-mimetype",          CONFIGSET_SKIN },
  { "logo-image",             CONFIGSET_SKIN },
  { "background-mimetype",    CONFIGSET_SKIN },
  { "background-image",       CONFIGSET_SKIN },
  { "timeline-block-markup",  CONFIGSET_SKIN },
  { "timeline-date-format",   CONFIGSET_SKIN },
  { "timeline-dwelltime",     CONFIGSET_SKIN },
  { "timeline-closetime",     CONFIGSET_SKIN },
  { "timeline-max-comment",   CONFIGSET_SKIN },
  { "timeline-plaintext",     CONFIGSET_SKIN },
  { "timeline-truncate-at-blank", CONFIGSET_SKIN },
  { "timeline-utc",           CONFIGSET_SKIN },
  { "adunit",                 CONFIGSET_SKIN },
  { "adunit-omit-if-admin",   CONFIGSET_SKIN },
  { "adunit-omit-if-user",    CONFIGSET_SKIN },
  { "sitemap-docidx",         CONFIGSET_SKIN },
  { "sitemap-download",       CONFIGSET_SKIN },
  { "sitemap-license",        CONFIGSET_SKIN },
  { "sitemap-contact",        CONFIGSET_SKIN },

Added src/copybtn.js.















































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/* Manage "Copy Buttons" linked to target elements, to copy the text (or, parts
** thereof) of the target elements to the clipboard.
**
** Newly created buttons are <span> elements with an SVG background icon,
** defined by the "copy-button" class in the default CSS style sheet.
**
** To simplify customization, the only properties modified for HTML-defined
** buttons are the "onclick" handler, and the "transition" and "opacity" styles
** (used for animation).
**
** For HTML-defined buttons, either initCopyButtonById(), or initCopyButton(),
** needs to be called to attach the "onclick" handler (done automatically from
** a handler attached to the "DOMContentLoaded" event).
**
** The initialization functions do not overwrite the "data-copytarget" and
** "data-copylength" attributes with empty or null values for <idTarget> and
** <cchLength>, respectively. Set <cchLength> to "-1" to explicitly remove the
** previous copy length limit.
**
** HTML snippet for statically created buttons:
**
**    <span class="copy-button" id="idButton"
**      data-copytarget="idTarget" data-copylength="cchLength"></span>
*/
function makeCopyButton(idButton,idTarget,cchLength){
  var elButton = document.createElement("span");
  elButton.className = "copy-button";
  elButton.id = idButton;
  initCopyButton(elButton,idTarget,cchLength);
  return elButton;
}
function initCopyButtonById(idButton,idTarget,cchLength){
  var elButton = document.getElementById(idButton);
  if( elButton ) initCopyButton(elButton,idTarget,cchLength);
  return elButton;
}
function initCopyButton(elButton,idTarget,cchLength){
  elButton.style.transition = "";
  elButton.style.opacity = 1;
  if( idTarget ) elButton.setAttribute("data-copytarget",idTarget);
  if( cchLength ) elButton.setAttribute("data-copylength",cchLength);
  elButton.onclick = clickCopyButton;
  return elButton;
}
onContentLoaded(function(){
  var aButtons = document.getElementsByClassName("copy-button");
  for ( var i=0; i<aButtons.length; i++ ){
    initCopyButton(aButtons[i],0,0);
  }
});
/* The onclick handler for the "Copy Button". */
var lockCopyText = false;
function clickCopyButton(e){
  e.preventDefault();   /* Mandatory for <a> and <button>. */
  e.stopPropagation();
  if( lockCopyText ) return;
  lockCopyText = true;
  this.style.transition = "opacity 400ms ease-in-out";
  this.style.opacity = 0;
  var idTarget = this.getAttribute("data-copytarget");
  var elTarget = document.getElementById(idTarget);
  if( elTarget ){
    var text = elTarget.innerText.replace(/^\s+|\s+$/g,'');
    var cchLength = parseInt(this.getAttribute("data-copylength"));
    if( !isNaN(cchLength) && cchLength>0 ){
      text = text.slice(0,cchLength);   // Assume single-byte chars.
    }
    copyTextToClipboard(text);
  }
  setTimeout(function(id){
    var elButton = document.getElementById(id);
    if( elButton ){
      elButton.style.transition = "";
      elButton.style.opacity = 1;
    }
    lockCopyText = false;
  }.bind(null,this.id),400);
}
/* Create a temporary <textarea> element and copy the contents to clipboard. */
function copyTextToClipboard(text){
  var textArea = document.createElement("textarea");
  textArea.style.position = 'fixed';
  textArea.style.top = 0;
  textArea.style.left = 0;
  textArea.style.width = '2em';
  textArea.style.height = '2em';
  textArea.style.padding = 0;
  textArea.style.border = 'none';
  textArea.style.outline = 'none';
  textArea.style.boxShadow = 'none';
  textArea.style.background = 'transparent';
  textArea.value = text;
  document.body.appendChild(textArea);
  textArea.focus();
  textArea.select();
  try{
    document.execCommand('copy');
  }catch(err){
  }
  document.body.removeChild(textArea);
}
/* Execute a function as soon as the HTML document has been completely loaded.
** The idea for this code is based on the contentLoaded() function presented
** here:
**
**    Cross-browser wrapper for DOMContentLoaded
**    http://javascript.nwbox.com/ContentLoaded/
*/
function onContentLoaded(fnready) {
  var fninit = function() {
    if (document.addEventListener ||
        event.type === 'load' ||
        document.readyState === 'complete') {
      if (document.addEventListener) {
        document.removeEventListener('DOMContentLoaded',fninit,false);
        window.removeEventListener('load',fninit,false);
      }
      else {
        document.detachEvent('onreadystatechange',fninit);
        window.detachEvent('onload',fninit);
      }
    }
    fnready.call(window);
  };
  if (document.readyState === 'complete')
    fnready.call(window);
  else if (document.addEventListener) {
    document.addEventListener('DOMContentLoaded',fninit,false);
    window.addEventListener('load',fninit,false);
  }
  else {
    document.attachEvent('onreadystatechange',fninit);
    window.attachEvent('onload',fninit);
  }
}

Changes to src/default_css.txt.

193
194
195
196
197
198
199










200
201
202
203
204
205
206
}
.tl-line.dotted.v {
  width: 0px;
  border-left-width: 2px;
  border-left-style: dotted;
  background: rgba(255,255,255,0);
}










span.tagDsp {
  font-weight: bold;
}
span.wikiError {
  font-weight: bold;
  color: red;
}







>
>
>
>
>
>
>
>
>
>







193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
}
.tl-line.dotted.v {
  width: 0px;
  border-left-width: 2px;
  border-left-style: dotted;
  background: rgba(255,255,255,0);
}
.tl-tooltip {
  text-align: center;
  padding: 5px 1em;
  border: 1px solid black;
  border-radius: 6px;
  position: absolute;
  z-index: 100;
  box-shadow: 2px 2px 6px rgba(0, 0, 0, 0.75);
}

span.tagDsp {
  font-weight: bold;
}
span.wikiError {
  font-weight: bold;
  color: red;
}
755
756
757
758
759
760
761














}
.capsumWrite {
  background-color: #ffb;
}
label {
  white-space: nowrap;
}





















>
>
>
>
>
>
>
>
>
>
>
>
>
>
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
}
.capsumWrite {
  background-color: #ffb;
}
label {
  white-space: nowrap;
}
.copy-button {
  display: inline-block;
  width: 14px;
  height: 14px;
  margin: -2px 0 0 0;
  padding: 0;
  border: 0;
  vertical-align: middle;
//Note: the mkcss utility does not support line breaks in data URIs.
  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");
  background-repeat: no-repeat;
  background-position: center;
  cursor: pointer;
}

Changes to src/graph.js.

1



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18



19
20
21
22
23
24
25
/* This module contains javascript needed to render timeline graphs in Fossil.



**
** Prior to sourcing this script, there should be a separate
** <script type='application/json' id='timeline-data-NN'> for each graph,
** each containing JSON like this:
**
**   { "iTableId": INTEGER,         // Table sequence number (NN)
**     "circleNodes": BOOLEAN,      // True for circle nodes. False for squares
**     "showArrowheads": BOOLEAN,   // True for arrowheads. False to omit
**     "iRailPitch": INTEGER,       // Spacing between vertical lines (px)
**     "colorGraph": BOOLEAN,       // True to put color on graph lines
**     "nomo": BOOLEAN,             // True to join merge lines with rails
**     "iTopRow": INTEGER,          // Index of top-most row in the graph
**     "omitDescenders": BOOLEAN,   // Omit ancestor lines off bottom of screen
**     "fileDiff": BOOLEAN,         // True for file diff. False for check-in
**     "scrollToSelect": BOOLEAN,   // Scroll to selection on first render
**     "nrail": INTEGER,            // Number of vertical "rails"
**     "baseUrl": TEXT,             // Top-level URL



**     "rowinfo": ROWINFO-ARRAY }
**
** The rowinfo field is an array of structures, one per entry in the timeline,
** where each structure has the following fields:
**
**   id:  The id of the <div> element for the row. This is an integer.
**        to get an actual id, prepend "m" to the integer.  The top node

>
>
>

















>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* This module contains javascript needed to render timeline graphs in Fossil.
**
** There can be multiple graphs on a single webpage, but this script is only
** loaded once.  
**
** Prior to sourcing this script, there should be a separate
** <script type='application/json' id='timeline-data-NN'> for each graph,
** each containing JSON like this:
**
**   { "iTableId": INTEGER,         // Table sequence number (NN)
**     "circleNodes": BOOLEAN,      // True for circle nodes. False for squares
**     "showArrowheads": BOOLEAN,   // True for arrowheads. False to omit
**     "iRailPitch": INTEGER,       // Spacing between vertical lines (px)
**     "colorGraph": BOOLEAN,       // True to put color on graph lines
**     "nomo": BOOLEAN,             // True to join merge lines with rails
**     "iTopRow": INTEGER,          // Index of top-most row in the graph
**     "omitDescenders": BOOLEAN,   // Omit ancestor lines off bottom of screen
**     "fileDiff": BOOLEAN,         // True for file diff. False for check-in
**     "scrollToSelect": BOOLEAN,   // Scroll to selection on first render
**     "nrail": INTEGER,            // Number of vertical "rails"
**     "baseUrl": TEXT,             // Top-level URL
**     "dwellTimeout": INTEGER,     // Tooltip show delay in milliseconds
**     "closeTimeout": INTEGER,     // Tooltip close delay in milliseconds
**     "hashDigits": INTEGER,       // Limit of tooltip hashes ("hash-digits")
**     "rowinfo": ROWINFO-ARRAY }
**
** The rowinfo field is an array of structures, one per entry in the timeline,
** where each structure has the following fields:
**
**   id:  The id of the <div> element for the row. This is an integer.
**        to get an actual id, prepend "m" to the integer.  The top node
53
54
55
56
57
58
59





60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77






















































78
79
80






























































81
82
83
84

85
86
87
88
89
90
91
**        and a thin merge-arrow descender is drawn to the bottom of
**        the screen. This array is omitted if there are no inbound
**        merges.
**   ci:  "cherrypick-in". Like "mi" except for cherrypick merges.
**        omitted if there are no cherrypick merges.
**    h:  The artifact hash of the object being graphed
*/





var amendCssOnce = 1; // Only change the CSS one time
function amendCss(circleNodes,showArrowheads){
  if( !amendCssOnce ) return;
  var css = "";
  if( circleNodes ){
    css += ".tl-node, .tl-node:after { border-radius: 50%; }";
  }
  if( !showArrowheads ){
    css += ".tl-arrow.u { display: none; }";
  }
  if( css!=="" ){
    var style = document.createElement("style");
    style.textContent = css;
    document.querySelector("head").appendChild(style);
  }
  amendCssOnce = 0;
}























































function TimelineGraph(tx){
  var topObj = document.getElementById("timelineTable"+tx.iTableId);
  amendCss(tx.circleNodes, tx.showArrowheads);






























































  var canvasDiv;
  var railPitch;
  var mergeOffset;
  var node, arrow, arrowSmall, line, mArrow, mLine, wArrow, wLine;

  function initGraph(){
    var parent = topObj.rows[0].cells[1];
    parent.style.verticalAlign = "top";
    canvasDiv = document.createElement("div");
    canvasDiv.className = "tl-canvas";
    canvasDiv.style.position = "absolute";
    parent.appendChild(canvasDiv);







>
>
>
>
>


















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



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




>







59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
**        and a thin merge-arrow descender is drawn to the bottom of
**        the screen. This array is omitted if there are no inbound
**        merges.
**   ci:  "cherrypick-in". Like "mi" except for cherrypick merges.
**        omitted if there are no cherrypick merges.
**    h:  The artifact hash of the object being graphed
*/

/* The amendCss() function does a one-time change to the CSS to account
** for the "circleNodes" and "showArrowheads" settings.  Do this change
** only once, even if there are multiple graphs being rendered.
*/
var amendCssOnce = 1; // Only change the CSS one time
function amendCss(circleNodes,showArrowheads){
  if( !amendCssOnce ) return;
  var css = "";
  if( circleNodes ){
    css += ".tl-node, .tl-node:after { border-radius: 50%; }";
  }
  if( !showArrowheads ){
    css += ".tl-arrow.u { display: none; }";
  }
  if( css!=="" ){
    var style = document.createElement("style");
    style.textContent = css;
    document.querySelector("head").appendChild(style);
  }
  amendCssOnce = 0;
}

/* The <span> object that holds the tooltip */
var tooltipObj = document.createElement("span");
tooltipObj.className = "tl-tooltip";
tooltipObj.style.display = "none";
document.getElementsByClassName("content")[0].appendChild(tooltipObj);
tooltipObj.onmouseenter = function(){stopCloseTimer();}
tooltipObj.onmouseleave = function(){
  if (tooltipInfo.ixActive != -1) resumeCloseTimer();
};

/* State information for the tooltip popup and its timers */
window.tooltipInfo = {
  dwellTimeout: 250,  /* The tooltip dwell timeout. */
  closeTimeout: 3000, /* The tooltip close timeout. */
  hashDigits: 16,     /* Limit of tooltip hashes ("hash-digits"). */
  idTimer: 0,         /* The tooltip dwell timer id. */
  idTimerClose: 0,    /* The tooltip close timer id. */
  ixHover: -1,        /* The id of the element with the mouse. */
  ixActive: -1,       /* The id of the element with the tooltip. */
  nodeHover: null,    /* Graph node under mouse when ixHover==-2 */
  posX: 0, posY: 0    /* The last mouse position. */
};

/* Functions used to control the tooltip popup and its timer */
function hideGraphTooltip(){
  stopCloseTimer();
  tooltipObj.style.display = "none";
  tooltipInfo.ixActive = -1;
}
document.body.onunload = hideGraphTooltip
function stopDwellTimer(){
  if (tooltipInfo.idTimer != 0) {
    clearTimeout(tooltipInfo.idTimer);
    tooltipInfo.idTimer = 0;
  }
}
function resumeCloseTimer(){
  /* This timer must be stopped explicitly to reset the elapsed timeout. */
  if(tooltipInfo.idTimerClose == 0 && tooltipInfo.closeTimeout>0) {
    tooltipInfo.idTimerClose = setTimeout(function(){
      tooltipInfo.idTimerClose = 0;
      hideGraphTooltip();
    },tooltipInfo.closeTimeout);
  }
}
function stopCloseTimer(){
  if (tooltipInfo.idTimerClose != 0) {
    clearTimeout(tooltipInfo.idTimerClose);
    tooltipInfo.idTimerClose = 0;
  }
}

/* Construct that graph corresponding to the timeline-data-N object that
** is passed in by the tx parameter */
function TimelineGraph(tx){
  var topObj = document.getElementById("timelineTable"+tx.iTableId);
  amendCss(tx.circleNodes, tx.showArrowheads);
  tooltipInfo.dwellTimeout = tx.dwellTimeout
  tooltipInfo.closeTimeout = tx.closeTimeout
  tooltipInfo.hashDigits = tx.hashDigits
  topObj.onclick = clickOnGraph
  topObj.ondblclick = dblclickOnGraph
  topObj.onmousemove = function(e) {
    var ix = findTxIndex(e);
    topObj.style.cursor = (ix<0) ? "" : "pointer"
    /* Keep the already visible tooltip at a constant position, as long as the
    ** mouse is over the same element. */
    if(tooltipObj.style.display != "none"){
      if(ix == tooltipInfo.ixHover) return;
    }
    /* The tooltip is either not visible, or the mouse is over a different
    ** element, so clear the dwell timer, and record the new element id and
    ** mouse position. */
    stopDwellTimer();
    if(ix >= 0){
      tooltipInfo.ixHover = ix;
      tooltipInfo.posX = e.clientX;
      tooltipInfo.posY = e.clientY;
      stopCloseTimer();
      if(tooltipInfo.dwellTimeout>0){
        tooltipInfo.idTimer = setTimeout(function() {
          tooltipInfo.idTimer = 0;
          stopCloseTimer();
          showGraphTooltip();
        },tooltipInfo.dwellTimeout);
      }
    }else{
      /* The mouse is not over an element with a tooltip */
      tooltipInfo.ixHover = -1;
      resumeCloseTimer();
    }
  };
  topObj.onmouseleave = function(e) {
    /* Hide the tooltip if the mouse is outside the "timelineTableN" element,
    ** and outside the tooltip. */
    if(e.relatedTarget && e.relatedTarget != tooltipObj){
      tooltipInfo.ixHover = -1;
      hideGraphTooltip();
      stopDwellTimer();
      stopCloseTimer();
    }
  };
  function nodeHover(e){
    /* Invoked by mousemove events over a graph node */
    e.stopPropagation()
    if(tooltipInfo.ixHover==-2) return
    tooltipInfo.ixHover = -2
    tooltipInfo.posX = e.clientX
    tooltipInfo.posY = e.clientY
    tooltipInfo.nodeHover = this
    stopCloseTimer();
    if(tooltipInfo.dwellTimeout>0){
      tooltipInfo.idTimer = setTimeout(function() {
        tooltipInfo.idTimer = 0;
        stopCloseTimer();
        showGraphTooltip();
      },tooltipInfo.dwellTimeout);
    }
  }
  var canvasDiv;
  var railPitch;
  var mergeOffset;
  var node, arrow, arrowSmall, line, mArrow, mLine, wArrow, wLine;

  function initGraph(){
    var parent = topObj.rows[0].cells[1];
    parent.style.verticalAlign = "top";
    canvasDiv = document.createElement("div");
    canvasDiv.className = "tl-canvas";
    canvasDiv.style.position = "absolute";
    parent.appendChild(canvasDiv);
161
162
163
164
165
166
167
168
169
170
171
172

173





174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197

198
199
200

201
202
203
204
205
206
207




208
209
210
211
212
213
214
    if( h ) n.style.height = h+"px";
    if( color ) n.style.backgroundColor = color;
    n.className = "tl-"+cls;
    canvasDiv.appendChild(n);
    return n;
  }
  function absoluteY(obj){
    var top = 0;
    if( obj.offsetParent ){
      do{
        top += obj.offsetTop;
      }while( obj = obj.offsetParent );

    }





    return top;
  }
  function miLineY(p){
    return p.y + node.h - mLine.w - 1;
  }
  function drawLine(elem,color,x0,y0,x1,y1){
    var cls = elem.cls + " ";
    if( x1===null ){
      x1 = x0+elem.w;
      cls += "v";
    }else{
      y1 = y0+elem.w;
      cls += "h";
    }
    return drawBox(cls,color,x0,y0,x1,y1);
  }
  function drawUpArrow(from,to,color){
    var y = to.y + node.h;
    var arrowSpace = from.y - y + (!from.id || from.r!=to.r ? node.h/2 : 0);
    var arw = arrowSpace < arrow.h*1.5 ? arrowSmall : arrow;
    var x = to.x + (node.w-line.w)/2;
    var y0 = from.y + node.h/2;
    var y1 = Math.ceil(to.y + node.h + arw.h/2);
    drawLine(line,color,x,y0,null,y1);

    x = to.x + (node.w-arw.w)/2;
    var n = drawBox(arw.cls,null,x,y);
    if(color) n.style.borderBottomColor = color;

  }
  function drawDotted(from,to,color){
    var x = to.x + (node.w-line.w)/2;
    var y0 = from.y + node.h/2;
    var y1 = Math.ceil(to.y + node.h);
    var n = drawLine(dotLine,null,x,y0,null,y1)
    if( color ) n.style.borderColor = color




  }
  /* Draw thin horizontal or vertical lines representing merges */
  function drawMergeLine(x0,y0,x1,y1){
    drawLine(mLine,null,x0,y0,x1,y1);
  }
  function drawCherrypickLine(x0,y0,x1,y1){
    drawLine(cpLine,null,x0,y0,x1,y1);







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















|






|
>

|

>

|





>
>
>
>







289
290
291
292
293
294
295
296

297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
    if( h ) n.style.height = h+"px";
    if( color ) n.style.backgroundColor = color;
    n.className = "tl-"+cls;
    canvasDiv.appendChild(n);
    return n;
  }
  function absoluteY(obj){
    var y = 0;

    do{
      y += obj.offsetTop;
    }while( obj = obj.offsetParent );
    return y;
  }
  function absoluteX(obj){
    var x = 0;
    do{
      x += obj.offsetLeft;
    }while( obj = obj.offsetParent );
    return x;
  }
  function miLineY(p){
    return p.y + node.h - mLine.w - 1;
  }
  function drawLine(elem,color,x0,y0,x1,y1){
    var cls = elem.cls + " ";
    if( x1===null ){
      x1 = x0+elem.w;
      cls += "v";
    }else{
      y1 = y0+elem.w;
      cls += "h";
    }
    return drawBox(cls,color,x0,y0,x1,y1);
  }
  function drawUpArrow(from,to,color,id){
    var y = to.y + node.h;
    var arrowSpace = from.y - y + (!from.id || from.r!=to.r ? node.h/2 : 0);
    var arw = arrowSpace < arrow.h*1.5 ? arrowSmall : arrow;
    var x = to.x + (node.w-line.w)/2;
    var y0 = from.y + node.h/2;
    var y1 = Math.ceil(to.y + node.h + arw.h/2);
    var n = drawLine(line,color,x,y0,null,y1);
    addToolTip(n,id)
    x = to.x + (node.w-arw.w)/2;
    n = drawBox(arw.cls,null,x,y);
    if(color) n.style.borderBottomColor = color;
    addToolTip(n,id)
  }
  function drawDotted(from,to,color,id){
    var x = to.x + (node.w-line.w)/2;
    var y0 = from.y + node.h/2;
    var y1 = Math.ceil(to.y + node.h);
    var n = drawLine(dotLine,null,x,y0,null,y1)
    if( color ) n.style.borderColor = color
    addToolTip(n,id)
  }
  function addToolTip(n,id){
    if( id ) n.setAttribute("data-ix",id-tx.iTopRow)
  }
  /* Draw thin horizontal or vertical lines representing merges */
  function drawMergeLine(x0,y0,x1,y1){
    drawLine(mLine,null,x0,y0,x1,y1);
  }
  function drawCherrypickLine(x0,y0,x1,y1){
    drawLine(cpLine,null,x0,y0,x1,y1);
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255


256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
    if( p.bg ){
      var e = document.getElementById("mc"+p.id);
      if(e) e.style.backgroundColor = p.bg;
      e = document.getElementById("md"+p.id);
      if(e) e.style.backgroundColor = p.bg;
    }
    if( p.r<0 ) return;
    if( p.u>0 ) drawUpArrow(p,tx.rowinfo[p.u-tx.iTopRow],p.fg);
    if( p.sb>0 ) drawDotted(p,tx.rowinfo[p.sb-tx.iTopRow],p.fg);
    var cls = node.cls;
    if( p.hasOwnProperty('mi') && p.mi.length ) cls += " merge";
    if( p.f&1 ) cls += " leaf";
    var n = drawBox(cls,p.bg,p.x,p.y);
    n.id = "tln"+p.id;
    n.onclick = clickOnNode;


    n.style.zIndex = 10;
    if( !tx.omitDescenders ){
      if( p.u==0 ){
        if( p.hasOwnProperty('mo') && p.r==p.mo ){
          var ix = p.hasOwnProperty('cu') ? p.cu : p.mu;
          var top = tx.rowinfo[ix-tx.iTopRow]
          drawUpArrow(p,{x: p.x, y: top.y-node.h}, p.fg);
        }else if( p.y>100 ){
          drawUpArrow(p,{x: p.x, y: p.y-50}, p.fg);
        }else{
          drawUpArrow(p,{x: p.x, y: 0},p.fg);
        }
      }
      if( p.hasOwnProperty('d') ){
        if( p.y + 150 >= btm ){
          drawUpArrow({x: p.x, y: btm - node.h/2},p,p.fg);
        }else{
          drawUpArrow({x: p.x, y: p.y+50},p,p.fg);
          drawDotted({x: p.x, y: p.y+63},{x: p.x, y: p.y+50-node.h/2},p.fg);
        }
      }
    }
    if( p.hasOwnProperty('mo') ){
      var x0 = p.x + node.w/2;
      var x1 = p.mo*railPitch + node.w/2;
      var u = tx.rowinfo[p.mu-tx.iTopRow];







|
|






>
>






|

|

|




|

|
|







380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
    if( p.bg ){
      var e = document.getElementById("mc"+p.id);
      if(e) e.style.backgroundColor = p.bg;
      e = document.getElementById("md"+p.id);
      if(e) e.style.backgroundColor = p.bg;
    }
    if( p.r<0 ) return;
    if( p.u>0 ) drawUpArrow(p,tx.rowinfo[p.u-tx.iTopRow],p.fg,p.id);
    if( p.sb>0 ) drawDotted(p,tx.rowinfo[p.sb-tx.iTopRow],p.fg,p.id);
    var cls = node.cls;
    if( p.hasOwnProperty('mi') && p.mi.length ) cls += " merge";
    if( p.f&1 ) cls += " leaf";
    var n = drawBox(cls,p.bg,p.x,p.y);
    n.id = "tln"+p.id;
    n.onclick = clickOnNode;
    n.ondblclick = dblclickOnNode;
    n.onmousemove = nodeHover;
    n.style.zIndex = 10;
    if( !tx.omitDescenders ){
      if( p.u==0 ){
        if( p.hasOwnProperty('mo') && p.r==p.mo ){
          var ix = p.hasOwnProperty('cu') ? p.cu : p.mu;
          var top = tx.rowinfo[ix-tx.iTopRow]
          drawUpArrow(p,{x: p.x, y: top.y-node.h}, p.fg, p.id);
        }else if( p.y>100 ){
          drawUpArrow(p,{x: p.x, y: p.y-50}, p.fg, p.id);
        }else{
          drawUpArrow(p,{x: p.x, y: 0},p.fg, p.id);
        }
      }
      if( p.hasOwnProperty('d') ){
        if( p.y + 150 >= btm ){
          drawUpArrow({x: p.x, y: btm - node.h/2},p,p.fg,p.id);
        }else{
          drawUpArrow({x: p.x, y: p.y+50},p,p.fg,p.id);
          drawDotted({x: p.x, y: p.y+63},{x: p.x, y: p.y+50-node.h/2},p.fg,p.id);
        }
      }
    }
    if( p.hasOwnProperty('mo') ){
      var x0 = p.x + node.w/2;
      var x1 = p.mo*railPitch + node.w/2;
      var u = tx.rowinfo[p.mu-tx.iTopRow];
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
          x0 = Math.ceil(x0);
          x1 += line.w;
        }
        var y0 = p.y + (node.h-line.w)/2;
        var u = tx.rowinfo[p.au[i+1]-tx.iTopRow];
        if( u.id<p.id ){
          drawLine(line,u.fg,x0,y0,x1,null);
          drawUpArrow(p,u,u.fg);
        }else{
          var y1 = u.y + (node.h-line.w)/2;
          drawLine(wLine,u.fg,x0,y0,x1,null);
          drawLine(wLine,u.fg,x1-line.w,y0,null,y1+line.w);
          drawLine(wLine,u.fg,x1,y1,u.x-wArrow.w/2,null);
          var x = u.x-wArrow.w;
          var y = u.y+(node.h-wArrow.h)/2;







|







465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
          x0 = Math.ceil(x0);
          x1 += line.w;
        }
        var y0 = p.y + (node.h-line.w)/2;
        var u = tx.rowinfo[p.au[i+1]-tx.iTopRow];
        if( u.id<p.id ){
          drawLine(line,u.fg,x0,y0,x1,null);
          drawUpArrow(p,u,u.fg,u.id);
        }else{
          var y1 = u.y + (node.h-line.w)/2;
          drawLine(wLine,u.fg,x0,y0,x1,null);
          drawLine(wLine,u.fg,x1-line.w,y0,null,y1+line.w);
          drawLine(wLine,u.fg,x1,y1,u.x-wArrow.w/2,null);
          var x = u.x-wArrow.w;
          var y = u.y+(node.h-wArrow.h)/2;
388
389
390
391
392
393
394
395

396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411







































































































412
413
414
415
416
417
418
    var btm = absoluteY(tlBtm) - canvasY + tlBtm.offsetHeight;
    for( var i=0; i<tx.nrail; i++) mergeBtm[i] = btm;
    for( var i=tx.rowinfo.length-1; i>=0; i-- ){
      drawNode(tx.rowinfo[i], btm);
    }
  }
  var selRow;
  function clickOnNode(){

    var p = tx.rowinfo[parseInt(this.id.match(/\d+$/)[0], 10)-tx.iTopRow];
    if( !selRow ){
      selRow = p;
      this.className += " sel";
      canvasDiv.className += " sel";
    }else if( selRow==p ){
      selRow = null;
      this.className = this.className.replace(" sel", "");
      canvasDiv.className = canvasDiv.className.replace(" sel", "");
    }else{
      if( tx.fileDiff ){
        location.href=tx.baseUrl + "/fdiff?v1="+selRow.h+"&v2="+p.h
      }else{
        location.href=tx.baseUrl + "/vdiff?from="+selRow.h+"&to="+p.h
      }
    }







































































































  }
  function changeDisplay(selector,value){
    var x = document.getElementsByClassName(selector);
    var n = x.length;
    for(var i=0; i<n; i++) {x[i].style.display = value;}
  }
  function changeDisplayById(id,value){







|
>
















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







529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
    var btm = absoluteY(tlBtm) - canvasY + tlBtm.offsetHeight;
    for( var i=0; i<tx.nrail; i++) mergeBtm[i] = btm;
    for( var i=tx.rowinfo.length-1; i>=0; i-- ){
      drawNode(tx.rowinfo[i], btm);
    }
  }
  var selRow;
  function clickOnNode(e){
    hideGraphTooltip()
    var p = tx.rowinfo[parseInt(this.id.match(/\d+$/)[0], 10)-tx.iTopRow];
    if( !selRow ){
      selRow = p;
      this.className += " sel";
      canvasDiv.className += " sel";
    }else if( selRow==p ){
      selRow = null;
      this.className = this.className.replace(" sel", "");
      canvasDiv.className = canvasDiv.className.replace(" sel", "");
    }else{
      if( tx.fileDiff ){
        location.href=tx.baseUrl + "/fdiff?v1="+selRow.h+"&v2="+p.h
      }else{
        location.href=tx.baseUrl + "/vdiff?from="+selRow.h+"&to="+p.h
      }
    }
    e.stopPropagation()
  }
  function dblclickOnNode(e){
    var p = tx.rowinfo[parseInt(this.id.match(/\d+$/)[0], 10)-tx.iTopRow];
    window.location.href = tx.baseUrl+"/info/"+p.h
    e.stopPropagation()
  }
  function findTxIndex(e){
    /* Look at all the graph elements.  If any graph elements that is near
    ** the click-point "e" and has a "data-ix" attribute, then return
    ** the value of that attribute.  Otherwise return -1 */
    var x = e.clientX + window.pageXOffset - absoluteX(canvasDiv);
    var y = e.clientY + window.pageYOffset - absoluteY(canvasDiv);
    var aNode = canvasDiv.childNodes
    var nNode = aNode.length;
    var i;
    for(i=0;i<nNode;i++){
      var n = aNode[i]
      if( !n.hasAttribute("data-ix") ) continue;
      if( x<n.offsetLeft-5 ) continue;
      if( x>n.offsetLeft+n.offsetWidth+5 ) continue;
      if( y<n.offsetTop-5 ) continue;
      if( y>n.offsetTop+n.offsetHeight ) continue;
      return n.getAttribute("data-ix")
    }
    return -1
  }
  /* Compute the hyperlink for the branch graph for tx.rowinfo[ix] */
  function branchHyperlink(ix){
    var br = tx.rowinfo[ix].br
    var dest = tx.baseUrl + "/timeline?r=" + encodeURIComponent(br)
    dest += tx.fileDiff ? "&m&cf=" : "&m&c="
    dest += encodeURIComponent(tx.rowinfo[ix].h)
    return dest
  }
  function clickOnGraph(e){
    tooltipInfo.ixHover = findTxIndex(e);
    tooltipInfo.posX = e.clientX;
    tooltipInfo.posY = e.clientY;
    showGraphTooltip();
  }
  function showGraphTooltip(){
    var html = null
    var ix = -1
    if( tooltipInfo.ixHover==-2 ){
      ix = parseInt(tooltipInfo.nodeHover.id.match(/\d+$/)[0],10)-tx.iTopRow
      var h = tx.rowinfo[ix].h
      var dest = tx.baseUrl + "/info/" + h
      h = h.slice(0,tooltipInfo.hashDigits); // Assume single-byte characters.
      if( tx.fileDiff ){
        html = "artifact <a id=\"tooltip-link\" href=\""+dest+"\">"+h+"</a>"
      }else{
        html = "check-in <a id=\"tooltip-link\" href=\""+dest+"\">"+h+"</a>"
      }
      tooltipInfo.ixActive = -2;
    }else if( tooltipInfo.ixHover>=0 ){
      ix = tooltipInfo.ixHover
      var br = tx.rowinfo[ix].br
      var dest = branchHyperlink(ix)
      var hbr = br.replace(/&/g, "&amp;")
         .replace(/</g, "&lt;")
         .replace(/>/g, "&gt;")
         .replace(/"/g, "&quot;")
         .replace(/'/g, "&#039;");
      html = "branch <a id=\"tooltip-link\" href=\""+dest+"\">"+hbr+"</a>"
      tooltipInfo.ixActive = ix;
    }
    if( html ){
      /* Setup while hidden, to ensure proper dimensions. */
      var s = getComputedStyle(document.body)
      if( tx.rowinfo[ix].bg.length ){
        tooltipObj.style.backgroundColor = tx.rowinfo[ix].bg
      }else{
        tooltipObj.style.backgroundColor = s.getPropertyValue('background-color')
      }
      tooltipObj.style.borderColor =
         tooltipObj.style.color = s.getPropertyValue('color')
      tooltipObj.style.visibility = "hidden"
      tooltipObj.innerHTML = html
      tooltipObj.appendChild(document.createTextNode(' '));
      tooltipObj.appendChild(
        makeCopyButton("tooltip-copybtn","tooltip-link",0));
      tooltipObj.style.display = "inline"
      tooltipObj.style.position = "absolute"
      var x = tooltipInfo.posX + 4 + window.pageXOffset
                   - absoluteX(tooltipObj.offsetParent)
      tooltipObj.style.left = x+"px"
      var y = tooltipInfo.posY + window.pageYOffset
                   - tooltipObj.clientHeight - 4
                   - absoluteY(tooltipObj.offsetParent)
      tooltipObj.style.top = y+"px"
      tooltipObj.style.visibility = "visible"
    }else{
      hideGraphTooltip()
    }
  }
  function dblclickOnGraph(e){
    var ix = findTxIndex(e);
    hideGraphTooltip()
    if( ix>=0 ){
      var dest = branchHyperlink(ix)
      window.location.href = dest
    }
  }
  function changeDisplay(selector,value){
    var x = document.getElementsByClassName(selector);
    var n = x.length;
    for(var i=0; i<n; i++) {x[i].style.display = value;}
  }
  function changeDisplayById(id,value){

Changes to src/info.c.

761
762
763
764
765
766
767



768

769
770
771
772
773
774
775
    db_prepare(&q2,"SELECT substr(tag.tagname,5) FROM tagxref, tag "
                   " WHERE rid=%d AND tagtype>0 "
                   "   AND tag.tagid=tagxref.tagid "
                   "   AND +tag.tagname GLOB 'sym-*'", rid);
    while( db_step(&q2)==SQLITE_ROW ){
      const char *zTagName = db_column_text(&q2, 0);
      if( fossil_strcmp(zTagName,zBrName)==0 ){



        @  | %z(href("%R/timeline?r=%T&unhide",zTagName))%h(zTagName)</a>

        if( wiki_tagid2("branch",zTagName)!=0 ){
          blob_appendf(&wiki_read_links, " | %z%h</a>",
              href("%R/wiki?name=branch/%h",zTagName), zTagName);
        }else if( g.perm.Write && g.perm.WrWiki ){
          blob_appendf(&wiki_add_links, " | %z%h</a>",
              href("%R/wikiedit?name=branch/%h",zTagName), zTagName);
        }







>
>
>
|
>







761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
    db_prepare(&q2,"SELECT substr(tag.tagname,5) FROM tagxref, tag "
                   " WHERE rid=%d AND tagtype>0 "
                   "   AND tag.tagid=tagxref.tagid "
                   "   AND +tag.tagname GLOB 'sym-*'", rid);
    while( db_step(&q2)==SQLITE_ROW ){
      const char *zTagName = db_column_text(&q2, 0);
      if( fossil_strcmp(zTagName,zBrName)==0 ){
        @  | <span class="copy-button" id="copy-br0"
        @      data-copytarget="br0" data-copylength="0">
        @  </span>&nbsp;<span
        @  id="br0">%z(href("%R/timeline?r=%T&unhide",zTagName))%h(zTagName)</a>
        @  </span>
        if( wiki_tagid2("branch",zTagName)!=0 ){
          blob_appendf(&wiki_read_links, " | %z%h</a>",
              href("%R/wiki?name=branch/%h",zTagName), zTagName);
        }else if( g.perm.Write && g.perm.WrWiki ){
          blob_appendf(&wiki_add_links, " | %z%h</a>",
              href("%R/wikiedit?name=branch/%h",zTagName), zTagName);
        }
791
792
793
794
795
796
797
798



799
800
801
802
803
804
805
    @   <td>
    @     %z(href("%R/tree?ci=%!S",zUuid))files</a>
    @   | %z(href("%R/fileage?name=%!S",zUuid))file ages</a>
    @   | %z(href("%R/tree?nofiles&type=tree&ci=%!S",zUuid))folders</a>
    @   </td>
    @ </tr>

    @ <tr><th>%s(hname_alg(nUuid)):</th><td>%.32s(zUuid)<wbr>%s(zUuid+32)



    if( g.perm.Setup ){
      @ (Record ID: %d(rid))
    }
    @ </td></tr>
    @ <tr><th>User&nbsp;&amp;&nbsp;Date:</th><td>
    hyperlink_to_user(zUser,zDate," on ");
    hyperlink_to_date(zDate, "</td></tr>");







|
>
>
>







795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
    @   <td>
    @     %z(href("%R/tree?ci=%!S",zUuid))files</a>
    @   | %z(href("%R/fileage?name=%!S",zUuid))file ages</a>
    @   | %z(href("%R/tree?nofiles&type=tree&ci=%!S",zUuid))folders</a>
    @   </td>
    @ </tr>

    @ <tr><th>%s(hname_alg(nUuid)):</th><td>
    @ <span class="copy-button" id="copy-fullhash"
    @   data-copytarget="fullhash" data-copylength="%d(hash_digits(1))">
    @ </span>&nbsp;<span id="fullhash">%.32s(zUuid)<wbr>%s(zUuid+32)</span>
    if( g.perm.Setup ){
      @ (Record ID: %d(rid))
    }
    @ </td></tr>
    @ <tr><th>User&nbsp;&amp;&nbsp;Date:</th><td>
    hyperlink_to_user(zUser,zDate," on ");
    hyperlink_to_date(zDate, "</td></tr>");
2180
2181
2182
2183
2184
2185
2186
2187

2188




2189
2190

2191
2192
2193
2194
2195
2196
2197
    url_add_parameter(&url, "verbose", "1");
    objdescFlags |= OBJDESC_DETAIL;
  }
  zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid);
  if( isFile ){
    @ <h2>Latest version of file '%h(zName)':</h2>
    style_submenu_element("Artifact", "%R/artifact/%S", zUuid);
  }else if( g.perm.Setup ){

    @ <h2>Artifact %s(zUuid) (%d(rid)):</h2>




  }else{
    @ <h2>Artifact %s(zUuid):</h2>

  }
  blob_zero(&downloadName);
  asText = P("txt")!=0;
  if( asText ) objdescFlags &= ~OBJDESC_BASE;
  objType = object_description(rid, objdescFlags, &downloadName);
  if( !descOnly && P("download")!=0 ){
    cgi_redirectf("%R/raw/%T?name=%s", blob_str(&downloadName),







|
>
|
>
>
>
>
|
|
>







2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
    url_add_parameter(&url, "verbose", "1");
    objdescFlags |= OBJDESC_DETAIL;
  }
  zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid);
  if( isFile ){
    @ <h2>Latest version of file '%h(zName)':</h2>
    style_submenu_element("Artifact", "%R/artifact/%S", zUuid);
  }else{
    style_copy_button();
    @ <h2>Artifact
    @ <span class="copy-button" id="copy-artifacthash"
    @   data-copytarget="artifacthash" data-copylength="%d(hash_digits(1))">
    if( g.perm.Setup ){
      @ </span>&nbsp;<span id="artifacthash">%s(zUuid)</span> (%d(rid)):</h2>
    }else{
      @ </span>&nbsp;<span id="artifacthash">%s(zUuid)</span>:</h2>
    }
  }
  blob_zero(&downloadName);
  asText = P("txt")!=0;
  if( asText ) objdescFlags &= ~OBJDESC_BASE;
  objType = object_description(rid, objdescFlags, &downloadName);
  if( !descOnly && P("download")!=0 ){
    cgi_redirectf("%R/raw/%T?name=%s", blob_str(&downloadName),

Changes to src/main.mk.

208
209
210
211
212
213
214

215
216
217
218
219
220
221
  $(SRCDIR)/../skins/rounded1/footer.txt \
  $(SRCDIR)/../skins/rounded1/header.txt \
  $(SRCDIR)/../skins/xekri/css.txt \
  $(SRCDIR)/../skins/xekri/details.txt \
  $(SRCDIR)/../skins/xekri/footer.txt \
  $(SRCDIR)/../skins/xekri/header.txt \
  $(SRCDIR)/ci_edit.js \

  $(SRCDIR)/diff.tcl \
  $(SRCDIR)/forum.js \
  $(SRCDIR)/graph.js \
  $(SRCDIR)/href.js \
  $(SRCDIR)/login.js \
  $(SRCDIR)/markdown.md \
  $(SRCDIR)/menu.js \







>







208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
  $(SRCDIR)/../skins/rounded1/footer.txt \
  $(SRCDIR)/../skins/rounded1/header.txt \
  $(SRCDIR)/../skins/xekri/css.txt \
  $(SRCDIR)/../skins/xekri/details.txt \
  $(SRCDIR)/../skins/xekri/footer.txt \
  $(SRCDIR)/../skins/xekri/header.txt \
  $(SRCDIR)/ci_edit.js \
  $(SRCDIR)/copybtn.js \
  $(SRCDIR)/diff.tcl \
  $(SRCDIR)/forum.js \
  $(SRCDIR)/graph.js \
  $(SRCDIR)/href.js \
  $(SRCDIR)/login.js \
  $(SRCDIR)/markdown.md \
  $(SRCDIR)/menu.js \

Changes to src/printf.c.

46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#endif

/*
** Return the number of artifact hash digits to display.  The number is for
** human output if the bForUrl is false and is destined for a URL if
** bForUrl is false.
*/
static int hashDigits(int bForUrl){
  static int nDigitHuman = 0;
  static int nDigitUrl = 0;
  if( nDigitHuman==0 ){
    nDigitHuman = db_get_int("hash-digits", FOSSIL_HASH_DIGITS);
    if( nDigitHuman < 6 ) nDigitHuman = 6;
    if( nDigitHuman > 40 ) nDigitHuman = 40;
    nDigitUrl = nDigitHuman + 6;
    if( nDigitUrl < FOSSIL_HASH_DIGITS_URL ) nDigitUrl = FOSSIL_HASH_DIGITS_URL;
    if( nDigitUrl > 40 ) nDigitUrl = 40;
  }
  return bForUrl ? nDigitUrl : nDigitHuman;
}

/*
** Return the number of characters in a %S output.
*/
int length_of_S_display(void){
  return hashDigits(0);
}

/*
** Conversion types fall into various categories as defined by the
** following enumeration.
*/
#define etRADIX       1 /* Integer types.  %d, %x, %o, and so forth */







|

















|







46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#endif

/*
** Return the number of artifact hash digits to display.  The number is for
** human output if the bForUrl is false and is destined for a URL if
** bForUrl is false.
*/
int hash_digits(int bForUrl){
  static int nDigitHuman = 0;
  static int nDigitUrl = 0;
  if( nDigitHuman==0 ){
    nDigitHuman = db_get_int("hash-digits", FOSSIL_HASH_DIGITS);
    if( nDigitHuman < 6 ) nDigitHuman = 6;
    if( nDigitHuman > 40 ) nDigitHuman = 40;
    nDigitUrl = nDigitHuman + 6;
    if( nDigitUrl < FOSSIL_HASH_DIGITS_URL ) nDigitUrl = FOSSIL_HASH_DIGITS_URL;
    if( nDigitUrl > 40 ) nDigitUrl = 40;
  }
  return bForUrl ? nDigitUrl : nDigitHuman;
}

/*
** Return the number of characters in a %S output.
*/
int length_of_S_display(void){
  return hash_digits(0);
}

/*
** Conversion types fall into various categories as defined by the
** following enumeration.
*/
#define etRADIX       1 /* Integer types.  %d, %x, %o, and so forth */
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
        int limit = flag_alternateform ? va_arg(ap,int) : -1;
        bufpt = va_arg(ap,char*);
        if( bufpt==0 ){
          bufpt = "";
        }else if( xtype==etDYNSTRING ){
          zExtra = bufpt;
        }else if( xtype==etSTRINGID ){
          precision = hashDigits(flag_altform2);
        }
        length = StrNLen32(bufpt, limit);
        if( precision>=0 && precision<length ) length = precision;
        break;
      }
      case etBLOB: {
        int limit = flag_alternateform ? va_arg(ap, int) : -1;







|







677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
        int limit = flag_alternateform ? va_arg(ap,int) : -1;
        bufpt = va_arg(ap,char*);
        if( bufpt==0 ){
          bufpt = "";
        }else if( xtype==etDYNSTRING ){
          zExtra = bufpt;
        }else if( xtype==etSTRINGID ){
          precision = hash_digits(flag_altform2);
        }
        length = StrNLen32(bufpt, limit);
        if( precision>=0 && precision<length ) length = precision;
        break;
      }
      case etBLOB: {
        int limit = flag_alternateform ? va_arg(ap, int) : -1;

Changes to src/setup.c.

747
748
749
750
751
752
753
















754
755
756
757
758
759
760
  @ <hr />
  entry_attribute("Max timeline comment length", 6,
                  "timeline-max-comment", "tmc", "0", 0);
  @ <p>The maximum length of a comment to be displayed in a timeline.
  @ "0" there is no length limit.
  @ (Property: "timeline-max-comment")</p>

















  @ <hr />
  @ <p><input type="submit"  name="submit" value="Apply Changes" /></p>
  @ </div></form>
  db_end_transaction(0);
  style_footer();
}








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







747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
  @ <hr />
  entry_attribute("Max timeline comment length", 6,
                  "timeline-max-comment", "tmc", "0", 0);
  @ <p>The maximum length of a comment to be displayed in a timeline.
  @ "0" there is no length limit.
  @ (Property: "timeline-max-comment")</p>

  @ <hr />
  entry_attribute("Tooltip dwell time (milliseconds)", 6,
                  "timeline-dwelltime", "tdt", "100", 0);
  @ <br>
  entry_attribute("Tooltip close time (milliseconds)", 6,
                  "timeline-closetime", "tct", "250", 0);
  @ <p>The <strong>dwell time</strong> defines how long the mouse pointer
  @ should be stationary above an object of the graph before a tooltip
  @ appears.<br>
  @ The <strong>close time</strong> defines how long the mouse pointer
  @ can be away from an object before a tooltip is closed.</p>
  @ <p>Set <strong>dwell time</strong> to "0" to disable tooltips.<br>
  @ Set <strong>close time</strong> to "0" to keep tooltips visible until
  @ the mouse is clicked elsewhere.<p>
  @ <p>(Properties: "timeline-dwelltime", "timeline-closetime")</p>

  @ <hr />
  @ <p><input type="submit"  name="submit" value="Apply Changes" /></p>
  @ </div></form>
  db_end_transaction(0);
  style_footer();
}

Changes to src/style.c.

80
81
82
83
84
85
86
87
88
89

90
91
92
93
94
95
96
** Ad-unit styles.
*/
static unsigned adUnitFlags = 0;

/*
** Flags for various javascript files needed prior to </body>
*/
static int needHrefJs = 0;   /* href.js */
static int needSortJs = 0;   /* sorttable.js */
static int needGraphJs = 0;  /* graph.js */


/*
** Extra JS added to the end of the file.
*/
static Blob blobOnLoad = BLOB_INITIALIZER;

/*







|
|
|
>







80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
** Ad-unit styles.
*/
static unsigned adUnitFlags = 0;

/*
** Flags for various javascript files needed prior to </body>
*/
static int needHrefJs = 0;      /* href.js */
static int needSortJs = 0;      /* sorttable.js */
static int needGraphJs = 0;     /* graph.js */
static int needCopyBtnJs = 0;   /* copybtn.js */

/*
** Extra JS added to the end of the file.
*/
static Blob blobOnLoad = BLOB_INITIALIZER;

/*
531
532
533
534
535
536
537
538
539
540
541
542







543
544
545
546
547
548
549
** Indicate that the table-sorting javascript is needed.
*/
void style_table_sorter(void){
  needSortJs = 1;
}

/*
** Indicate that the table-sorting javascript is needed.
*/
void style_graph_generator(void){
  needGraphJs = 1;
}








/*
** Generate code to load a single javascript file
*/
void style_load_one_js_file(const char *zFile){
  @ <script src='%R/builtin/%s(zFile)?id=%S(MANIFEST_UUID)'></script>
}







|




>
>
>
>
>
>
>







532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
** Indicate that the table-sorting javascript is needed.
*/
void style_table_sorter(void){
  needSortJs = 1;
}

/*
** Indicate that the timeline graph javascript is needed.
*/
void style_graph_generator(void){
  needGraphJs = 1;
}

/*
** Indicate that the copy button javascript is needed.
*/
void style_copy_button(void){
  needCopyBtnJs = 1;
}

/*
** Generate code to load a single javascript file
*/
void style_load_one_js_file(const char *zFile){
  @ <script src='%R/builtin/%s(zFile)?id=%S(MANIFEST_UUID)'></script>
}
576
577
578
579
580
581
582




583
584
585
586
587
588
589
590
591



592
593
594
595
596
597
598
  if( needHrefJs ){
    int nDelay = db_get_int("auto-hyperlink-delay",0);
    int bMouseover = db_get_boolean("auto-hyperlink-mouseover",0);
    @ <script id='href-data' type='application/json'>\
    @ {"delay":%d(nDelay),"mouseover":%d(bMouseover)}</script>
  }
  @ <script nonce="%h(style_nonce())">




  if( needHrefJs ){
    cgi_append_content(builtin_text("href.js"),-1);
  }
  if( needSortJs ){
    cgi_append_content(builtin_text("sorttable.js"),-1);
  }
  if( needGraphJs ){
    cgi_append_content(builtin_text("graph.js"),-1);
  }



  for(i=0; i<nJsToLoad; i++){
    cgi_append_content(builtin_text(azJsToLoad[i]),-1);
  }
  if( blob_size(&blobOnLoad)>0 ){
    @ window.onload = function(){
    cgi_append_content(blob_buffer(&blobOnLoad), blob_size(&blobOnLoad));
    cgi_append_content("\n}\n", -1);







>
>
>
>









>
>
>







584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
  if( needHrefJs ){
    int nDelay = db_get_int("auto-hyperlink-delay",0);
    int bMouseover = db_get_boolean("auto-hyperlink-mouseover",0);
    @ <script id='href-data' type='application/json'>\
    @ {"delay":%d(nDelay),"mouseover":%d(bMouseover)}</script>
  }
  @ <script nonce="%h(style_nonce())">
  @ function debugMsg(msg){
  @ var n = document.getElementById("debugMsg");
  @ if(n){n.textContent=msg;}
  @ }
  if( needHrefJs ){
    cgi_append_content(builtin_text("href.js"),-1);
  }
  if( needSortJs ){
    cgi_append_content(builtin_text("sorttable.js"),-1);
  }
  if( needGraphJs ){
    cgi_append_content(builtin_text("graph.js"),-1);
  }
  if( needCopyBtnJs ){
    cgi_append_content(builtin_text("copybtn.js"),-1);
  }
  for(i=0; i<nJsToLoad; i++){
    cgi_append_content(builtin_text(azJsToLoad[i]),-1);
  }
  if( blob_size(&blobOnLoad)>0 ){
    @ window.onload = function(){
    cgi_append_content(blob_buffer(&blobOnLoad), blob_size(&blobOnLoad));
    cgi_append_content("\n}\n", -1);
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
    @ </div>
  }else{
    if( zAd ){
      @ <div class="adunit_banner">
      cgi_append_content(zAd, -1);
      @ </div>
    }
    @ <div class="content">
  }
  cgi_destination(CGI_BODY);

  if( sideboxUsed ){
    /* Put the footer at the bottom of the page.
    ** the additional clear/both is needed to extend the content
    ** part to the end of an optional sidebox.







|







756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
    @ </div>
  }else{
    if( zAd ){
      @ <div class="adunit_banner">
      cgi_append_content(zAd, -1);
      @ </div>
    }
    @ <div class="content"><span id="debugMsg"></span>
  }
  cgi_destination(CGI_BODY);

  if( sideboxUsed ){
    /* Put the footer at the bottom of the page.
    ** the additional clear/both is needed to extend the content
    ** part to the end of an optional sidebox.

Changes to src/th_main.c.

265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
** True if output is enabled.  False if disabled.
*/
static int enableOutput = 1;

/*
** TH1 command: enable_output BOOLEAN
**
** Enable or disable the puts and wiki commands.
*/
static int enableOutputCmd(
  Th_Interp *interp,
  void *p,
  int argc,
  const char **argv,
  int *argl







|







265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
** True if output is enabled.  False if disabled.
*/
static int enableOutput = 1;

/*
** TH1 command: enable_output BOOLEAN
**
** Enable or disable the puts, wiki, combobox and copybtn commands.
*/
static int enableOutputCmd(
  Th_Interp *interp,
  void *p,
  int argc,
  const char **argv,
  int *argl
988
989
990
991
992
993
994



























































995
996
997
998
999
1000
1001
      free(z);
    }
    sendText("</select>", -1, 0);
    Th_Free(interp, azElem);
  }
  return TH_OK;
}




























































/*
** TH1 command: linecount STRING MAX MIN
**
** Return one more than the number of \n characters in STRING.  But
** never return less than MIN or more than MAX.
*/







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







988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
      free(z);
    }
    sendText("</select>", -1, 0);
    Th_Free(interp, azElem);
  }
  return TH_OK;
}

/*
** TH1 command: copybtn TARGETID TEXT ?COPYLENGTH?
**
** Output TEXT with a click-to-copy button next to it. Loads the copybtn.js
** Javascript module, and generates HTML elements with the following IDs:
**
**    TARGETID:       The <span> wrapper around TEXT.
**    copy-TARGETID:  The <span> for the copy button.
**
** The optional COPYLENGTH argument defines the length of the substring of TEXT
** copied to clipboard:
**
**    <= 0:   No limit (default if the argument is omitted).
**    >= 3:   Truncate TEXT after COPYLENGTH (single-byte) characters.
**       1:   Use the "hash-digits" setting as the limit.
**       2:   Use the length appropriate for URLs as the limit (defined at
**            compile-time by FOSSIL_HASH_DIGITS_URL, defaults to 16).
*/
static int copybtnCmd(
  Th_Interp *interp,
  void *p,
  int argc,
  const char **argv,
  int *argl
){
  if( argc!=3 && argc!=4 ){
    return Th_WrongNumArgs(interp, "copybtn TARGETID TEXT COPYLENGTH");
  }
  if( enableOutput ){
    int copylength = 0;
    char *zTargetId, *zText, *zResult;
    if( argc==4 && Th_ToInt(interp, argv[3], argl[3], &copylength) ){
      return TH_ERROR;
    }
    if( copylength==1 ) copylength = hash_digits(0);
    else if( copylength==2 ) copylength = hash_digits(1);
    zTargetId = htmlize((char*)argv[1], argl[1]);
    zText = htmlize((char*)argv[2], argl[2]);
    zResult = mprintf(
                "<span "
                "class=\"copy-button\" "
                "id=\"copy-%s\" "
                "data-copytarget=\"%s\" "
                "data-copylength=\"%d\">"
                "</span>"
                "&nbsp;"
                "<span id=\"%s\">"
                "%s"
                "</span>",
                zTargetId, zTargetId, copylength, zTargetId, zText);
    free(zTargetId);
    free(zText);
    style_copy_button();
    sendText(zResult, -1, 0);
    free(zResult);
  }
  return TH_OK;
}

/*
** TH1 command: linecount STRING MAX MIN
**
** Return one more than the number of \n characters in STRING.  But
** never return less than MIN or more than MAX.
*/
2022
2023
2024
2025
2026
2027
2028

2029
2030
2031
2032
2033
2034
2035
  } aCommand[] = {
    {"anoncap",       hascapCmd,            (void*)&anonFlag},
    {"anycap",        anycapCmd,            0},
    {"artifact",      artifactCmd,          0},
    {"cgiHeaderLine", cgiHeaderLineCmd,     0},
    {"checkout",      checkoutCmd,          0},
    {"combobox",      comboboxCmd,          0},

    {"date",          dateCmd,              0},
    {"decorate",      wikiCmd,              (void*)&aFlags[2]},
    {"dir",           dirCmd,               0},
    {"enable_output", enableOutputCmd,      0},
    {"encode64",      encode64Cmd,          0},
    {"getParameter",  getParameterCmd,      0},
    {"glob_match",    globMatchCmd,         0},







>







2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
  } aCommand[] = {
    {"anoncap",       hascapCmd,            (void*)&anonFlag},
    {"anycap",        anycapCmd,            0},
    {"artifact",      artifactCmd,          0},
    {"cgiHeaderLine", cgiHeaderLineCmd,     0},
    {"checkout",      checkoutCmd,          0},
    {"combobox",      comboboxCmd,          0},
    {"copybtn",       copybtnCmd,           0},
    {"date",          dateCmd,              0},
    {"decorate",      wikiCmd,              (void*)&aFlags[2]},
    {"dir",           dirCmd,               0},
    {"enable_output", enableOutputCmd,      0},
    {"encode64",      encode64Cmd,          0},
    {"getParameter",  getParameterCmd,      0},
    {"glob_match",    globMatchCmd,         0},

Changes to src/timeline.c.

828
829
830
831
832
833
834


835
836
837
838
839
840
841
842
843
844


845
846
847
848
849
850
851
852
853
854
855
856
857



858
859
860
861
862
863
864
    int showArrowheads;  /* True to draw arrowheads.  False to omit. */
    int circleNodes;     /* True for circle nodes.  False for square nodes */
    int colorGraph;      /* Use colors for graph lines */
    int iTopRow;         /* Index of the top row of the graph */
    int fileDiff;        /* True for file diff.  False for check-in diff */
    int omitDescenders;  /* True to omit descenders */
    int scrollToSelect;  /* True to scroll to the selection */


    u8 *aiMap;           /* The rail map */

    iRailPitch = atoi(PD("railpitch","0"));
    showArrowheads = skin_detail_boolean("timeline-arrowheads");
    circleNodes = skin_detail_boolean("timeline-circle-nodes");
    colorGraph = skin_detail_boolean("timeline-color-graph-lines");
    iTopRow = pGraph->pFirst ? pGraph->pFirst->idx : 0;
    omitDescenders = (tmFlags & TIMELINE_DISJOINT)!=0;
    fileDiff = (tmFlags & TIMELINE_FILEDIFF)!=0;
    scrollToSelect = (tmFlags & TIMELINE_NOSCROLL)==0;


    @ <script id='timeline-data-%d(iTableId)' type='application/json'>{
    @   "iTableId": %d(iTableId),
    @   "circleNodes": %d(circleNodes),
    @   "showArrowheads": %d(showArrowheads),
    @   "iRailPitch": %d(iRailPitch),
    @   "colorGraph": %d(colorGraph),
    @   "nomo": %d(PB("nomo")),
    @   "iTopRow": %d(iTopRow),
    @   "omitDescenders": %d(omitDescenders),
    @   "fileDiff": %d(fileDiff),
    @   "scrollToSelect": %d(scrollToSelect),
    @   "nrail": %d(pGraph->mxRail+1),
    @   "baseUrl": "%R",



    @   "bottomRowId": "btm-%d(iTableId)",
    if( pGraph->nRow==0 ){
      @   "rowinfo": null
    }else{
      @   "rowinfo": [
    }








>
>










>
>













>
>
>







828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
    int showArrowheads;  /* True to draw arrowheads.  False to omit. */
    int circleNodes;     /* True for circle nodes.  False for square nodes */
    int colorGraph;      /* Use colors for graph lines */
    int iTopRow;         /* Index of the top row of the graph */
    int fileDiff;        /* True for file diff.  False for check-in diff */
    int omitDescenders;  /* True to omit descenders */
    int scrollToSelect;  /* True to scroll to the selection */
    int dwellTimeout;    /* Milliseconds to wait for tooltips to show */
    int closeTimeout;    /* Milliseconds to wait for tooltips to close */
    u8 *aiMap;           /* The rail map */

    iRailPitch = atoi(PD("railpitch","0"));
    showArrowheads = skin_detail_boolean("timeline-arrowheads");
    circleNodes = skin_detail_boolean("timeline-circle-nodes");
    colorGraph = skin_detail_boolean("timeline-color-graph-lines");
    iTopRow = pGraph->pFirst ? pGraph->pFirst->idx : 0;
    omitDescenders = (tmFlags & TIMELINE_DISJOINT)!=0;
    fileDiff = (tmFlags & TIMELINE_FILEDIFF)!=0;
    scrollToSelect = (tmFlags & TIMELINE_NOSCROLL)==0;
    dwellTimeout = atoi(db_get("timeline-dwelltime","100"));
    closeTimeout = atoi(db_get("timeline-closetime","250"));
    @ <script id='timeline-data-%d(iTableId)' type='application/json'>{
    @   "iTableId": %d(iTableId),
    @   "circleNodes": %d(circleNodes),
    @   "showArrowheads": %d(showArrowheads),
    @   "iRailPitch": %d(iRailPitch),
    @   "colorGraph": %d(colorGraph),
    @   "nomo": %d(PB("nomo")),
    @   "iTopRow": %d(iTopRow),
    @   "omitDescenders": %d(omitDescenders),
    @   "fileDiff": %d(fileDiff),
    @   "scrollToSelect": %d(scrollToSelect),
    @   "nrail": %d(pGraph->mxRail+1),
    @   "baseUrl": "%R",
    @   "dwellTimeout": %d(dwellTimeout),
    @   "closeTimeout": %d(closeTimeout),
    @   "hashDigit": %d(hash_digits(1)),
    @   "bottomRowId": "btm-%d(iTableId)",
    if( pGraph->nRow==0 ){
      @   "rowinfo": null
    }else{
      @   "rowinfo": [
    }

902
903
904
905
906
907
908

909
910
911
912
913
914
915
    **        negative, then the rail position is the absolute value of mi[]
    **        and a thin merge-arrow descender is drawn to the bottom of
    **        the screen. This array is omitted if there are no inbound
    **        merges.
    **   ci:  "cherrypick-in". Like "mi" except for cherrypick merges.
    **        omitted if there are no cherrypick merges.
    **    h:  The artifact hash of the object being graphed

    */
    aiMap = pGraph->aiRailMap;
    for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){
      int k = 0;
      cgi_printf("{\"id\":%d,",     pRow->idx);
      cgi_printf("\"bg\":\"%s\",",  pRow->zBgClr);
      cgi_printf("\"r\":%d,",       aiMap[pRow->iRail]);







>







909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
    **        negative, then the rail position is the absolute value of mi[]
    **        and a thin merge-arrow descender is drawn to the bottom of
    **        the screen. This array is omitted if there are no inbound
    **        merges.
    **   ci:  "cherrypick-in". Like "mi" except for cherrypick merges.
    **        omitted if there are no cherrypick merges.
    **    h:  The artifact hash of the object being graphed
    *    br:  The branch to which the artifact belongs
    */
    aiMap = pGraph->aiRailMap;
    for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){
      int k = 0;
      cgi_printf("{\"id\":%d,",     pRow->idx);
      cgi_printf("\"bg\":\"%s\",",  pRow->zBgClr);
      cgi_printf("\"r\":%d,",       aiMap[pRow->iRail]);
976
977
978
979
980
981
982

983
984
985
986
987

988
989
990
991
992
993
994
          }
          k++;
          cgi_printf("%c%d", cSep, mi);
          cSep = ',';
        }
      }
      if( k ) cgi_printf("],");

      cgi_printf("\"h\":\"%!S\"}%s",
                 pRow->zUuid, pRow->pNext ? ",\n" : "]\n");
    }
    @ }</script>
    style_graph_generator();

    graph_free(pGraph);
  }
}

/*
** Create a temporary table suitable for storing timeline data.
*/







>





>







984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
          }
          k++;
          cgi_printf("%c%d", cSep, mi);
          cSep = ',';
        }
      }
      if( k ) cgi_printf("],");
      cgi_printf("\"br\":\"%j\",", pRow->zBranch ? pRow->zBranch : "");
      cgi_printf("\"h\":\"%!S\"}%s",
                 pRow->zUuid, pRow->pNext ? ",\n" : "]\n");
    }
    @ }</script>
    style_graph_generator();
    style_copy_button(); /* Dependency: graph.js requires copybtn.js. */
    graph_free(pGraph);
  }
}

/*
** Create a temporary table suitable for storing timeline data.
*/
1476
1477
1478
1479
1480
1481
1482

1483
1484
1485
1486
1487
1488
1489
** WEBPAGE: timeline
**
** Query parameters:
**
**    a=TIMEORTAG     After this event
**    b=TIMEORTAG     Before this event
**    c=TIMEORTAG     "Circa" this event

**    m=TIMEORTAG     Mark this event
**    n=COUNT         Maximum number of events.  "all" for no limit
**    p=CHECKIN       Parents and ancestors of CHECKIN
**    d=CHECKIN       Children and descendants of CHECKIN
**    dp=CHECKIN      The same as d=CHECKIN&p=CHECKIN
**    t=TAG           Show only check-ins with the given TAG
**    r=TAG           Show check-ins related to TAG, equivalent to t=TAG&rel







>







1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
** WEBPAGE: timeline
**
** Query parameters:
**
**    a=TIMEORTAG     After this event
**    b=TIMEORTAG     Before this event
**    c=TIMEORTAG     "Circa" this event
**    cf=FILEHASH     "Circa" the first use of the file with FILEHASH
**    m=TIMEORTAG     Mark this event
**    n=COUNT         Maximum number of events.  "all" for no limit
**    p=CHECKIN       Parents and ancestors of CHECKIN
**    d=CHECKIN       Children and descendants of CHECKIN
**    dp=CHECKIN      The same as d=CHECKIN&p=CHECKIN
**    t=TAG           Show only check-ins with the given TAG
**    r=TAG           Show check-ins related to TAG, equivalent to t=TAG&rel
1639
1640
1641
1642
1643
1644
1645











1646

1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
  if( zType[0]=='a' || zType[0]=='c' ){
    cookie_write_parameter("y","y",zType);
  }
  cookie_render();
  url_initialize(&url, "timeline");
  cgi_query_parameters_to_url(&url);












  /* Convert r=TAG to t=TAG&rel. */

  if( zBrName && !related ){
    cgi_delete_query_parameter("r");
    cgi_set_query_parameter("t", zBrName);
    cgi_set_query_parameter("rel", "1");
    zTagName = zBrName;
    related = 1;
    zType = "ci";
  }

  /* Ignore empty tag query strings. */
  if( zTagName && !*zTagName ){







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

<
<
<







1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670



1671
1672
1673
1674
1675
1676
1677
  if( zType[0]=='a' || zType[0]=='c' ){
    cookie_write_parameter("y","y",zType);
  }
  cookie_render();
  url_initialize(&url, "timeline");
  cgi_query_parameters_to_url(&url);

  /* Convert the cf=FILEHASH query parameter into a c=CHECKINHASH value */
  if( P("cf")!=0 ){
    zCirca = db_text(0,
      "SELECT (SELECT uuid FROM blob WHERE rid=mlink.mid)"
      "  FROM mlink, event"
      " WHERE mlink.fid=(SELECT rid FROM blob WHERE uuid LIKE '%q%%')"
      "   AND event.objid=mlink.mid"
      " ORDER BY event.mtime LIMIT 1",
      P("cf")
    );
  }

  /* r=TAG works like a combination of t=TAG & rel */
  if( zBrName && !related ){



    zTagName = zBrName;
    related = 1;
    zType = "ci";
  }

  /* Ignore empty tag query strings. */
  if( zTagName && !*zTagName ){

Changes to src/tktsetup.c.

444
445
446
447
448
449
450


451
452
453
454
455
456

457
458
459
460
461
462
463
}

static const char zDefaultView[] =
@ <table cellpadding="5">
@ <tr><td class="tktDspLabel">Ticket&nbsp;UUID:</td>
@ <th1>
@ if {[info exists tkt_uuid]} {


@   if {[hascap s]} {
@     html "<td class='tktDspValue' colspan='3'>$tkt_uuid "
@     html "($tkt_id)</td></tr>\n"
@   } else {
@     html "<td class='tktDspValue' colspan='3'>$tkt_uuid</td></tr>\n"
@   }

@ } else {
@   if {[hascap s]} {
@     html "<td class='tktDspValue' colspan='3'>Deleted "
@     html "(0)</td></tr>\n"
@   } else {
@     html "<td class='tktDspValue' colspan='3'>Deleted</td></tr>\n"
@   }







>
>

<
|
<
<

>







444
445
446
447
448
449
450
451
452
453

454


455
456
457
458
459
460
461
462
463
}

static const char zDefaultView[] =
@ <table cellpadding="5">
@ <tr><td class="tktDspLabel">Ticket&nbsp;UUID:</td>
@ <th1>
@ if {[info exists tkt_uuid]} {
@   html "<td class='tktDspValue' colspan='3'>"
@   copybtn tkt_uuid $tkt_uuid 1
@   if {[hascap s]} {

@     html " ($tkt_id)"


@   }
@   html "</td></tr>\n"
@ } else {
@   if {[hascap s]} {
@     html "<td class='tktDspValue' colspan='3'>Deleted "
@     html "(0)</td></tr>\n"
@   } else {
@     html "<td class='tktDspValue' colspan='3'>Deleted</td></tr>\n"
@   }

Changes to win/Makefile.mingw.

630
631
632
633
634
635
636

637
638
639
640
641
642
643
  $(SRCDIR)/../skins/rounded1/footer.txt \
  $(SRCDIR)/../skins/rounded1/header.txt \
  $(SRCDIR)/../skins/xekri/css.txt \
  $(SRCDIR)/../skins/xekri/details.txt \
  $(SRCDIR)/../skins/xekri/footer.txt \
  $(SRCDIR)/../skins/xekri/header.txt \
  $(SRCDIR)/ci_edit.js \

  $(SRCDIR)/diff.tcl \
  $(SRCDIR)/forum.js \
  $(SRCDIR)/graph.js \
  $(SRCDIR)/href.js \
  $(SRCDIR)/login.js \
  $(SRCDIR)/markdown.md \
  $(SRCDIR)/menu.js \







>







630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
  $(SRCDIR)/../skins/rounded1/footer.txt \
  $(SRCDIR)/../skins/rounded1/header.txt \
  $(SRCDIR)/../skins/xekri/css.txt \
  $(SRCDIR)/../skins/xekri/details.txt \
  $(SRCDIR)/../skins/xekri/footer.txt \
  $(SRCDIR)/../skins/xekri/header.txt \
  $(SRCDIR)/ci_edit.js \
  $(SRCDIR)/copybtn.js \
  $(SRCDIR)/diff.tcl \
  $(SRCDIR)/forum.js \
  $(SRCDIR)/graph.js \
  $(SRCDIR)/href.js \
  $(SRCDIR)/login.js \
  $(SRCDIR)/markdown.md \
  $(SRCDIR)/menu.js \

Changes to win/Makefile.msc.

533
534
535
536
537
538
539

540
541
542
543
544
545
546
        $(SRCDIR)\..\skins\rounded1\footer.txt \
        $(SRCDIR)\..\skins\rounded1\header.txt \
        $(SRCDIR)\..\skins\xekri\css.txt \
        $(SRCDIR)\..\skins\xekri\details.txt \
        $(SRCDIR)\..\skins\xekri\footer.txt \
        $(SRCDIR)\..\skins\xekri\header.txt \
        $(SRCDIR)\ci_edit.js \

        $(SRCDIR)\diff.tcl \
        $(SRCDIR)\forum.js \
        $(SRCDIR)\graph.js \
        $(SRCDIR)\href.js \
        $(SRCDIR)\login.js \
        $(SRCDIR)\markdown.md \
        $(SRCDIR)\menu.js \







>







533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
        $(SRCDIR)\..\skins\rounded1\footer.txt \
        $(SRCDIR)\..\skins\rounded1\header.txt \
        $(SRCDIR)\..\skins\xekri\css.txt \
        $(SRCDIR)\..\skins\xekri\details.txt \
        $(SRCDIR)\..\skins\xekri\footer.txt \
        $(SRCDIR)\..\skins\xekri\header.txt \
        $(SRCDIR)\ci_edit.js \
        $(SRCDIR)\copybtn.js \
        $(SRCDIR)\diff.tcl \
        $(SRCDIR)\forum.js \
        $(SRCDIR)\graph.js \
        $(SRCDIR)\href.js \
        $(SRCDIR)\login.js \
        $(SRCDIR)\markdown.md \
        $(SRCDIR)\menu.js \

Changes to www/th1.md.

168
169
170
171
172
173
174

175
176
177
178
179
180
181

  *  anoncap
  *  anycap
  *  artifact
  *  cgiHeaderLine
  *  checkout
  *  combobox

  *  date
  *  decorate
  *  dir
  *  enable\_output
  *  encode64
  *  getParameter
  *  glob\_match







>







168
169
170
171
172
173
174
175
176
177
178
179
180
181
182

  *  anoncap
  *  anycap
  *  artifact
  *  cgiHeaderLine
  *  checkout
  *  combobox
  *  copybtn
  *  date
  *  decorate
  *  dir
  *  enable\_output
  *  encode64
  *  getParameter
  *  glob\_match
275
276
277
278
279
280
281




















282
283
284
285
286
287
288

Generates and emits an HTML combobox.  NAME is both the name of the
CGI parameter and the name of a variable that contains the currently
selected value.  TEXT-LIST is a list of possible values for the
combobox.  NUMLINES is 1 for a true combobox.  If NUMLINES is greater
than one then the display is a listbox with the number of lines given.





















<a name="date"></a>TH1 date Command
-----------------------------------

  *  date ?-local?

Return a strings which is the current time and date.  If the -local
option is used, the date appears using localtime instead of UTC.







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







276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309

Generates and emits an HTML combobox.  NAME is both the name of the
CGI parameter and the name of a variable that contains the currently
selected value.  TEXT-LIST is a list of possible values for the
combobox.  NUMLINES is 1 for a true combobox.  If NUMLINES is greater
than one then the display is a listbox with the number of lines given.

<a name="copybtn"></a>TH1 copybtn Command
-----------------------------------------

  *  copybtn TARGETID TEXT ?COPYLENGTH?

Output TEXT with a click-to-copy button next to it. Loads the copybtn.js
Javascript module, and generates HTML elements with the following IDs:

  *  TARGETID:       The `<span>` wrapper around TEXT.
  *  copy-TARGETID:  The `<span>` for the copy button.

The optional COPYLENGTH argument defines the length of the substring of TEXT
copied to clipboard:

  *  <= 0:   No limit (default if the argument is omitted).
  *  >= 3:   Truncate TEXT after COPYLENGTH (single-byte) characters.
  *     1:   Use the "hash-digits" setting as the limit.
  *     2:   Use the length appropriate for URLs as the limit (defined at
             compile-time by `FOSSIL_HASH_DIGITS_URL`, defaults to 16).

<a name="date"></a>TH1 date Command
-----------------------------------

  *  date ?-local?

Return a strings which is the current time and date.  If the -local
option is used, the date appears using localtime instead of UTC.
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
time zone configured for the repository).

<a name="enable_output"></a>TH1 enable\_output Command
------------------------------------------------------

  *  enable\_output BOOLEAN

Enable or disable sending output when the combobox, puts, or wiki
commands are used.

<a name="encode64"></a>TH1 encode64 Command
-------------------------------------------

  *  encode64 STRING








|







329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
time zone configured for the repository).

<a name="enable_output"></a>TH1 enable\_output Command
------------------------------------------------------

  *  enable\_output BOOLEAN

Enable or disable sending output when the combobox, copybtn, puts, or wiki
commands are used.

<a name="encode64"></a>TH1 encode64 Command
-------------------------------------------

  *  encode64 STRING