Fossil SCM

Add link-tester.json, a set of links for link-tester.html (format is described near the bottom of link-tester.js). Add a link to link-tester which opens the current page in a new tab.

stephan 2025-06-20 13:57 trunk
Commit 1acf766268326759e6bffc1ba50076d630939d5c25b513e2a3839acd99cbf1ed
--- test/link-tester.html
+++ test/link-tester.html
@@ -20,27 +20,41 @@
2020
}
2121
header {
2222
margin: 0.5em 0.25em 0 0.25em;
2323
padding: 0;
2424
}
25
+ #controlWrapper {
26
+ display: flex;
27
+ flex-direction: column;
28
+ z-index: 1;
29
+ background: white /*do not bleed iframe through */;
30
+ border-bottom: 2px dotted;
31
+ padding-bottom: 0.5em;
32
+ }
2533
#controls {
2634
display: flex;
2735
flex-direction: row;
2836
flex-grow: 0;
29
- border-bottom: 2px dotted;
30
- z-index: 1;
31
- background: white /*don't bleed iframe through */;
3237
}
3338
#controls > button {
3439
flex-grow: 1;
3540
margin: 0.5em;
3641
}
37
- #selectPage {
42
+ #selectWrapper {
43
+ display: flex;
44
+ flex-direction: column;
3845
flex-grow: 8;
46
+ }
47
+ #selectPage {
48
+ flex-grow: 1;
3949
margin: 1em;
4050
padding: 1em;
4151
}
52
+ #currentUrl {
53
+ font-family: monospace;
54
+ text-align: center;
55
+ }
4256
#iframe {
4357
flex-grow: 1; border: none; margin: 0; padding: 0;
4458
display: block;
4559
/* Absolute positioning is apparently the only way to get
4660
the iframe to stretch to fill the page, but we have to
@@ -56,16 +70,21 @@
5670
Fossil link test app. Select links from the list below to load
5771
them. Use the arrow keys to cycle through the list. The fossil
5872
instance is run within an iframe, so navigation within it
5973
will stay within that frame.
6074
</header>
61
- <div id='controls'>
62
- <button id='btn-prev'>&larr;</button>
63
- <select id='selectPage'>
64
- <option>/timeline</option>
65
- <option>/dir</option>
66
- </select>
67
- <button id='btn-next'>&rarr;</button>
68
- </div>
75
+ <header id='controlWrapper'>
76
+ <div id='controls'>
77
+ <button id='btn-prev'>&larr;</button>
78
+ <div id='selectWrapper'>
79
+ <select id='selectPage'>
80
+ <option>/timeline</option>
81
+ <option>/dir</option>
82
+ </select>
83
+ <a target='_blank' id='currentUrl'></a>
84
+ </div>
85
+ <button id='btn-next'>&rarr;</button>
86
+ </div>
87
+ </header>
6988
<iframe id='iframe'><!--populated via the UI--></iframe>
7089
<script src='link-tester.js'></script>
7190
<body>
7291
--- test/link-tester.html
+++ test/link-tester.html
@@ -20,27 +20,41 @@
20 }
21 header {
22 margin: 0.5em 0.25em 0 0.25em;
23 padding: 0;
24 }
 
 
 
 
 
 
 
 
25 #controls {
26 display: flex;
27 flex-direction: row;
28 flex-grow: 0;
29 border-bottom: 2px dotted;
30 z-index: 1;
31 background: white /*don't bleed iframe through */;
32 }
33 #controls > button {
34 flex-grow: 1;
35 margin: 0.5em;
36 }
37 #selectPage {
 
 
38 flex-grow: 8;
 
 
 
39 margin: 1em;
40 padding: 1em;
41 }
 
 
 
 
42 #iframe {
43 flex-grow: 1; border: none; margin: 0; padding: 0;
44 display: block;
45 /* Absolute positioning is apparently the only way to get
46 the iframe to stretch to fill the page, but we have to
@@ -56,16 +70,21 @@
56 Fossil link test app. Select links from the list below to load
57 them. Use the arrow keys to cycle through the list. The fossil
58 instance is run within an iframe, so navigation within it
59 will stay within that frame.
60 </header>
61 <div id='controls'>
62 <button id='btn-prev'>&larr;</button>
63 <select id='selectPage'>
64 <option>/timeline</option>
65 <option>/dir</option>
66 </select>
67 <button id='btn-next'>&rarr;</button>
68 </div>
 
 
 
 
 
69 <iframe id='iframe'><!--populated via the UI--></iframe>
70 <script src='link-tester.js'></script>
71 <body>
72
--- test/link-tester.html
+++ test/link-tester.html
@@ -20,27 +20,41 @@
20 }
21 header {
22 margin: 0.5em 0.25em 0 0.25em;
23 padding: 0;
24 }
25 #controlWrapper {
26 display: flex;
27 flex-direction: column;
28 z-index: 1;
29 background: white /*do not bleed iframe through */;
30 border-bottom: 2px dotted;
31 padding-bottom: 0.5em;
32 }
33 #controls {
34 display: flex;
35 flex-direction: row;
36 flex-grow: 0;
 
 
 
37 }
38 #controls > button {
39 flex-grow: 1;
40 margin: 0.5em;
41 }
42 #selectWrapper {
43 display: flex;
44 flex-direction: column;
45 flex-grow: 8;
46 }
47 #selectPage {
48 flex-grow: 1;
49 margin: 1em;
50 padding: 1em;
51 }
52 #currentUrl {
53 font-family: monospace;
54 text-align: center;
55 }
56 #iframe {
57 flex-grow: 1; border: none; margin: 0; padding: 0;
58 display: block;
59 /* Absolute positioning is apparently the only way to get
60 the iframe to stretch to fill the page, but we have to
@@ -56,16 +70,21 @@
70 Fossil link test app. Select links from the list below to load
71 them. Use the arrow keys to cycle through the list. The fossil
72 instance is run within an iframe, so navigation within it
73 will stay within that frame.
74 </header>
75 <header id='controlWrapper'>
76 <div id='controls'>
77 <button id='btn-prev'>&larr;</button>
78 <div id='selectWrapper'>
79 <select id='selectPage'>
80 <option>/timeline</option>
81 <option>/dir</option>
82 </select>
83 <a target='_blank' id='currentUrl'></a>
84 </div>
85 <button id='btn-next'>&rarr;</button>
86 </div>
87 </header>
88 <iframe id='iframe'><!--populated via the UI--></iframe>
89 <script src='link-tester.js'></script>
90 <body>
91
--- test/link-tester.js
+++ test/link-tester.js
@@ -14,16 +14,11 @@
1414
if( !e || !e.length ) throw new Error("Missing elements: "+s);
1515
return e;
1616
};
1717
const eIframe = E('#iframe');
1818
const eSelect = E('#selectPage');
19
- eSelect.addEventListener('change',function(ev){
20
- const so = ev.target.options[ev.target.selectedIndex];
21
- if( so ){
22
- eIframe.setAttribute('src', so.value || so.innerText);
23
- }
24
- });
19
+ const eCurrentUrl = E('#currentUrl');
2520
2621
/*
2722
Prepend the fossil instance's URL to each link. We have to guess
2823
which part of the URL is the fossil CGI/server instance. The
2924
following works when run (A) from under /uv or /ext and (B) from
@@ -30,11 +25,12 @@
3025
/doc/branchname/test/link-tester.html.
3126
*/
3227
let urlTop;
3328
let loc = (''+window.location);
3429
let aLoc = loc.split('/')
35
- aLoc.pop(); /* this file name */
30
+ aLoc.pop(); /* file name */
31
+ const thisDir = aLoc.join('/');
3632
const rxDoc = /.*\/doc\/[^/]+\/.*/;
3733
//console.log(rxDoc, loc, aLoc);
3834
if( loc.match(rxDoc) ){
3935
/* We're hopefully now at the top-most fossil-served
4036
URL. */
@@ -49,21 +45,50 @@
4945
o.value = urlTop + (o.value || o.innerText);
5046
}
5147
5248
const eBtnPrev = E('#btn-prev');
5349
const eBtnNext = E('#btn-next');
50
+
51
+ const updateUrl = function(opt){
52
+ if( opt ){
53
+ let url = (opt.value || opt.innerText);
54
+ eCurrentUrl.innerText = url.replace(urlTop,'');
55
+ eCurrentUrl.setAttribute('href', url);
56
+ }else{
57
+ eCurrentUrl.innerText = '';
58
+ }
59
+ };
60
+
61
+ eSelect.addEventListener('change',function(ev){
62
+ const so = ev.target.options[ev.target.selectedIndex];
63
+ if( so ){
64
+ eIframe.setAttribute('src', so.value || so.innerText);
65
+ updateUrl(so);
66
+ }
67
+ });
68
+
69
+ const selectEntry = function(ndx){
70
+ if( ndx>=0 ){
71
+ eSelect.selectedIndex = ndx;
72
+ eSelect.dispatchEvent(new Event('change',{target:eSelect}));
73
+ }
74
+ };
5475
5576
const cycleLink = function(dir){
5677
let n = eSelect.selectedIndex + dir;
5778
if( n < 0 ) n = eSelect.options.length-1;
5879
else if( n>=eSelect.options.length ){
5980
n = 0;
6081
}
61
- if( n>=0 ){
82
+ const opt = eSelect.options[n];
83
+ if( opt && opt.disabled ){
84
+ /* If that OPTION element is disabled, skip over it. */
6285
eSelect.selectedIndex = n;
86
+ cycleLink(dir);
87
+ }else{
88
+ selectEntry(n);
6389
}
64
- eSelect.dispatchEvent(new Event('change',{target:eSelect}));
6590
};
6691
6792
eBtnPrev.addEventListener('click', ()=>cycleLink(-1), false);
6893
eBtnNext.addEventListener('click', ()=>cycleLink(1), false);
6994
@@ -127,11 +152,11 @@
127152
let ht;
128153
let extra = 0;
129154
eToAvoid.forEach((e)=>e ? extra += effectiveHeight(e) : false);
130155
ht = wh - extra;
131156
if( ht < 100 ) ht = 100;
132
- eConstrained.style.top = 'calc('+extra+'px + 0.25em)';
157
+ eConstrained.style.top = 'calc('+extra+'px + 1.5em)';
133158
eConstrained.style.height =
134159
eConstrained.style.maxHeight = [
135160
"calc(", ht, "px",
136161
" - 0.65em"/*fudge value*/,")"
137162
/* ^^^^ hypothetically not needed, but both Chrome/FF on
@@ -148,7 +173,52 @@
148173
);
149174
150175
delete ForceResizeKludge.$disabled;
151176
ForceResizeKludge();
152177
153
- eSelect.dispatchEvent(new Event('change',{target:eSelect}));
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
+ });
154224
});
155225
156226
ADDED test/link-tester.json
--- test/link-tester.js
+++ test/link-tester.js
@@ -14,16 +14,11 @@
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 eSelect.addEventListener('change',function(ev){
20 const so = ev.target.options[ev.target.selectedIndex];
21 if( so ){
22 eIframe.setAttribute('src', so.value || so.innerText);
23 }
24 });
25
26 /*
27 Prepend the fossil instance's URL to each link. We have to guess
28 which part of the URL is the fossil CGI/server instance. The
29 following works when run (A) from under /uv or /ext and (B) from
@@ -30,11 +25,12 @@
30 /doc/branchname/test/link-tester.html.
31 */
32 let urlTop;
33 let loc = (''+window.location);
34 let aLoc = loc.split('/')
35 aLoc.pop(); /* this file name */
 
36 const rxDoc = /.*\/doc\/[^/]+\/.*/;
37 //console.log(rxDoc, loc, aLoc);
38 if( loc.match(rxDoc) ){
39 /* We're hopefully now at the top-most fossil-served
40 URL. */
@@ -49,21 +45,50 @@
49 o.value = urlTop + (o.value || o.innerText);
50 }
51
52 const eBtnPrev = E('#btn-prev');
53 const eBtnNext = E('#btn-next');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
55 const cycleLink = function(dir){
56 let n = eSelect.selectedIndex + dir;
57 if( n < 0 ) n = eSelect.options.length-1;
58 else if( n>=eSelect.options.length ){
59 n = 0;
60 }
61 if( n>=0 ){
 
 
62 eSelect.selectedIndex = n;
 
 
 
63 }
64 eSelect.dispatchEvent(new Event('change',{target:eSelect}));
65 };
66
67 eBtnPrev.addEventListener('click', ()=>cycleLink(-1), false);
68 eBtnNext.addEventListener('click', ()=>cycleLink(1), false);
69
@@ -127,11 +152,11 @@
127 let ht;
128 let extra = 0;
129 eToAvoid.forEach((e)=>e ? extra += effectiveHeight(e) : false);
130 ht = wh - extra;
131 if( ht < 100 ) ht = 100;
132 eConstrained.style.top = 'calc('+extra+'px + 0.25em)';
133 eConstrained.style.height =
134 eConstrained.style.maxHeight = [
135 "calc(", ht, "px",
136 " - 0.65em"/*fudge value*/,")"
137 /* ^^^^ hypothetically not needed, but both Chrome/FF on
@@ -148,7 +173,52 @@
148 );
149
150 delete ForceResizeKludge.$disabled;
151 ForceResizeKludge();
152
153 eSelect.dispatchEvent(new Event('change',{target:eSelect}));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154 });
155
156 DDED test/link-tester.json
--- test/link-tester.js
+++ test/link-tester.js
@@ -14,16 +14,11 @@
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
@@ -30,11 +25,12 @@
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. */
@@ -49,21 +45,50 @@
45 o.value = urlTop + (o.value || o.innerText);
46 }
47
48 const eBtnPrev = E('#btn-prev');
49 const eBtnNext = E('#btn-next');
50
51 const updateUrl = function(opt){
52 if( opt ){
53 let url = (opt.value || opt.innerText);
54 eCurrentUrl.innerText = url.replace(urlTop,'');
55 eCurrentUrl.setAttribute('href', url);
56 }else{
57 eCurrentUrl.innerText = '';
58 }
59 };
60
61 eSelect.addEventListener('change',function(ev){
62 const so = ev.target.options[ev.target.selectedIndex];
63 if( so ){
64 eIframe.setAttribute('src', so.value || so.innerText);
65 updateUrl(so);
66 }
67 });
68
69 const selectEntry = function(ndx){
70 if( ndx>=0 ){
71 eSelect.selectedIndex = ndx;
72 eSelect.dispatchEvent(new Event('change',{target:eSelect}));
73 }
74 };
75
76 const cycleLink = function(dir){
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 eBtnPrev.addEventListener('click', ()=>cycleLink(-1), false);
93 eBtnNext.addEventListener('click', ()=>cycleLink(1), false);
94
@@ -127,11 +152,11 @@
152 let ht;
153 let extra = 0;
154 eToAvoid.forEach((e)=>e ? extra += effectiveHeight(e) : false);
155 ht = wh - extra;
156 if( ht < 100 ) ht = 100;
157 eConstrained.style.top = 'calc('+extra+'px + 1.5em)';
158 eConstrained.style.height =
159 eConstrained.style.maxHeight = [
160 "calc(", ht, "px",
161 " - 0.65em"/*fudge value*/,")"
162 /* ^^^^ hypothetically not needed, but both Chrome/FF on
@@ -148,7 +173,52 @@
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
226 DDED test/link-tester.json
--- a/test/link-tester.json
+++ b/test/link-tester.json
@@ -0,0 +1,7 @@
1
+[
2
+ "Timelines",
3
+ ["Timeline", "/timeline"],
4
+ ["Timeline efault", "/timeline"],
5
+ ["anonTip infoos"15dc52d61&v2=af6df3466e3c4a88"],
6
+ ["Column alignment with multibyte c
7
+]
--- a/test/link-tester.json
+++ b/test/link-tester.json
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
--- a/test/link-tester.json
+++ b/test/link-tester.json
@@ -0,0 +1,7 @@
1 [
2 "Timelines",
3 ["Timeline", "/timeline"],
4 ["Timeline efault", "/timeline"],
5 ["anonTip infoos"15dc52d61&v2=af6df3466e3c4a88"],
6 ["Column alignment with multibyte c
7 ]

Keyboard Shortcuts

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