Fossil SCM

fossil-scm / test / link-tester.js
Blame History Raw 225 lines
1
/**
2
JS code for link-tester.html. We cannot host this JS inline in that
3
file because fossil's default Content Security Policy won't let it
4
run that way.
5
*/
6
window.addEventListener("DOMContentLoaded", function(){
7
const E = function(s){
8
const e = document.querySelector(s);
9
if( !e ) throw new Error("Missing element: "+s);
10
return e;
11
};
12
const EAll = function(s){
13
const e = document.querySelectorAll(s);
14
if( !e || !e.length ) throw new Error("Missing elements: "+s);
15
return e;
16
};
17
const eIframe = E('#iframe');
18
const eSelect = E('#selectPage');
19
const eCurrentUrl = E('#currentUrl');
20
21
/*
22
Prepend the fossil instance's URL to each link. We have to guess
23
which part of the URL is the fossil CGI/server instance. The
24
following works when run (A) from under /uv or /ext and (B) from
25
/doc/branchname/test/link-tester.html.
26
*/
27
let urlTop;
28
let loc = (''+window.location);
29
let aLoc = loc.split('/')
30
aLoc.pop(); /* file name */
31
const thisDir = aLoc.join('/');
32
const rxDoc = /.*\/doc\/[^/]+\/.*/;
33
//console.log(rxDoc, loc, aLoc);
34
if( loc.match(rxDoc) ){
35
/* We're hopefully now at the top-most fossil-served
36
URL. */
37
aLoc.pop(); aLoc.pop(); /* /doc/foo */
38
aLoc.pop(); /* current dir name */
39
}else{
40
aLoc.pop(); /* current dir name */
41
}
42
urlTop = aLoc.join('/');
43
//console.log(urlTop, aLoc);
44
for( const o of eSelect.options ){
45
o.value = urlTop + (o.value || o.innerText);
46
}
47
48
const updateUrl = function(opt){
49
if( opt ){
50
let url = (opt.value || opt.innerText);
51
eCurrentUrl.innerText = url.replace(urlTop,'');
52
eCurrentUrl.setAttribute('href', url);
53
}else{
54
eCurrentUrl.innerText = '';
55
}
56
};
57
58
eSelect.addEventListener('change',function(ev){
59
const so = ev.target.options[ev.target.selectedIndex];
60
if( so ){
61
eIframe.setAttribute('src', so.value || so.innerText);
62
updateUrl(so);
63
}
64
});
65
66
/** Select the entry at the given ndx and fire a change event. */
67
const selectEntry = function(ndx){
68
if( ndx>=0 ){
69
eSelect.selectedIndex = ndx;
70
eSelect.dispatchEvent(new Event('change',{target:eSelect}));
71
}
72
};
73
74
/* Cycle to the next link in the list, accounting for separators and
75
wrapping around at either end. */
76
const cycleLink = function(dir/*<0 = prev, >0 = next*/){
77
let n = eSelect.selectedIndex + dir;
78
if( n < 0 ) n = eSelect.options.length-1;
79
else if( n>=eSelect.options.length ){
80
n = 0;
81
}
82
const opt = eSelect.options[n];
83
if( opt && opt.disabled ){
84
/* If that OPTION element is disabled, skip over it. */
85
eSelect.selectedIndex = n;
86
cycleLink(dir);
87
}else{
88
selectEntry(n);
89
}
90
};
91
92
E('#btn-prev').addEventListener('click', ()=>cycleLink(-1), false);
93
E('#btn-next').addEventListener('click', ()=>cycleLink(1), false);
94
95
/**
96
We have to adjust the iframe's size dynamically to account for
97
other widgets around it. iframes don't simply like to fill up all
98
available space without some help. If #controlWrapper only
99
contained the one SELECT element, CSS would be sufficient, but
100
once we add text around it, #controlWrapper's size becomes
101
unpredictable and we need JS to calculate it. We do this every
102
time the window size changes.
103
*/
104
const effectiveHeight = function f(e){
105
// Copied from fossil.dom.js
106
if(!e) return 0;
107
if(!f.measure){
108
f.measure = function callee(e, depth){
109
if(!e) return;
110
const m = e.getBoundingClientRect();
111
if(0===depth){
112
callee.top = m.top;
113
callee.bottom = m.bottom;
114
}else{
115
callee.top = m.top ? Math.min(callee.top, m.top) : callee.top;
116
callee.bottom = Math.max(callee.bottom, m.bottom);
117
}
118
Array.prototype.forEach.call(e.children,(e)=>callee(e,depth+1));
119
if(0===depth){
120
//console.debug("measure() height:",e.className, callee.top, callee.bottom, (callee.bottom - callee.top));
121
f.extra += callee.bottom - callee.top;
122
}
123
return f.extra;
124
};
125
}
126
f.extra = 0;
127
f.measure(e,0);
128
return f.extra;
129
};
130
131
/* Helper for the window-resized event handler below, to avoid
132
handling the resize until after it's finished. */
133
const debounce = function f(func, waitMs, immediate) {
134
// Copied from fossil.bootstrap.js
135
var timeoutId;
136
if(!waitMs) waitMs = f.$defaultDelay;
137
return function() {
138
const context = this, args = Array.prototype.slice.call(arguments);
139
const later = function() {
140
timeoutId = undefined;
141
if(!immediate) func.apply(context, args);
142
};
143
const callNow = immediate && !timeoutId;
144
clearTimeout(timeoutId);
145
timeoutId = setTimeout(later, waitMs);
146
if(callNow) func.apply(context, args);
147
};
148
};
149
150
/**
151
Resize eConstrained (the ifame element) so that it fits within
152
the page space not occupied by the list of elements eToAvoid.
153
*/
154
const ForceResizeKludge = (function(eToAvoid, eConstrained){
155
const resized = function f(){
156
if( f.$disabled ) return;
157
const wh = window.innerHeight;
158
let ht;
159
let extra = 0;
160
eToAvoid.forEach((e)=>e ? extra += effectiveHeight(e) : false);
161
ht = wh - extra;
162
if( ht < 100 ) ht = 100;
163
eConstrained.style.top = 'calc('+extra+'px + 2em)';
164
eConstrained.style.height =
165
eConstrained.style.maxHeight = "calc("+ ht+ "px - 2em)";
166
};
167
resized.$disabled = true/* gets deleted later */;
168
window.addEventListener('resize', debounce(resized, 250), false);
169
return resized;
170
})(
171
EAll('body > *:not(iframe)'),
172
eIframe
173
);
174
175
delete ForceResizeKludge.$disabled;
176
ForceResizeKludge();
177
178
selectEntry(0);
179
180
/**
181
Read link-tester.json, which should live in the same directory
182
as this file. It's expected to be an array with entries
183
in one of the following forms:
184
185
- "string" = Separator label (disabled)
186
- ["/path"] = path with itself as a label
187
- ["label", "/path"] = path with the given label
188
189
All paths are expected to have a "/" prefix and this script
190
accounts for mapping that to the fossil part of this script's
191
URL.
192
*/
193
window.fetch(thisDir+'/link-tester.json').then((r)=>r.json()).then(j=>{
194
//console.log("fetched",j);
195
eSelect.innerHTML = '';
196
const opt = function(arg){
197
const o = document.createElement('option');
198
//console.warn(arguments);
199
let rc = true;
200
if( 'string' === typeof arg ){
201
/* Grouping separator */
202
o.innerText = "--- " + arg + " ---";
203
o.setAttribute('disabled','');
204
rc = false;
205
}else if( 1===arg.length ){
206
o.innerText = arg[0];
207
o.value = urlTop + arg[0];
208
}else if( 2==arg.length ){
209
o.innerText = arg[0];
210
o.value = urlTop + arg[1];
211
}
212
eSelect.appendChild(o);
213
return rc;
214
};
215
let ndx = -1/*index of first non-disabled entry*/, i = 0;
216
for(const e of j){
217
if( opt(e) && ndx<0 ){
218
ndx = i;
219
}
220
++i;
221
}
222
selectEntry(ndx);
223
});
224
});
225

Keyboard Shortcuts

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