|
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
|
|