|
51c1efd…
|
stephan
|
1 |
/** |
|
51c1efd…
|
stephan
|
2 |
diff-related JS APIs for fossil. |
|
51c1efd…
|
stephan
|
3 |
*/ |
|
51c1efd…
|
stephan
|
4 |
"use strict"; |
|
a718a76…
|
stephan
|
5 |
/* Locate the UI element (if any) into which we can inject some diff-related |
|
a718a76…
|
stephan
|
6 |
UI controls. */ |
|
a718a76…
|
stephan
|
7 |
window.fossil.onPageLoad(function(){ |
|
a718a76…
|
stephan
|
8 |
const potentialParents = window.fossil.page.diffControlContainers = [ |
|
a718a76…
|
stephan
|
9 |
/* CSS selectors for possible parents for injected diff-related UI |
|
a718a76…
|
stephan
|
10 |
controls. */ |
|
a718a76…
|
stephan
|
11 |
/* Put the most likely pages at the end, as array.pop() is more |
|
a718a76…
|
stephan
|
12 |
efficient than array.shift() (see loop below). */ |
|
a718a76…
|
stephan
|
13 |
/* /filedit */ 'body.cpage-fileedit #fileedit-tab-diff-buttons', |
|
a718a76…
|
stephan
|
14 |
/* /wikiedit */ 'body.cpage-wikiedit #wikiedit-tab-diff-buttons', |
|
a718a76…
|
stephan
|
15 |
/* /fdiff */ 'body.fdiff form div.submenu', |
|
a718a76…
|
stephan
|
16 |
/* /vdiff */ 'body.vdiff form div.submenu', |
|
a718a76…
|
stephan
|
17 |
/* /info, /vinfo, /ckout */ 'body.vinfo div.sectionmenu.info-changes-menu' |
|
a718a76…
|
stephan
|
18 |
]; |
|
a718a76…
|
stephan
|
19 |
window.fossil.page.diffControlContainer = undefined; |
|
a718a76…
|
stephan
|
20 |
while( potentialParents.length ){ |
|
a718a76…
|
stephan
|
21 |
if( (window.fossil.page.diffControlContainer |
|
a718a76…
|
stephan
|
22 |
= document.querySelector(potentialParents.pop())) ){ |
|
a718a76…
|
stephan
|
23 |
break; |
|
a718a76…
|
stephan
|
24 |
} |
|
a718a76…
|
stephan
|
25 |
} |
|
a718a76…
|
stephan
|
26 |
}); |
|
a718a76…
|
stephan
|
27 |
|
|
51c1efd…
|
stephan
|
28 |
window.fossil.onPageLoad(function(){ |
|
51c1efd…
|
stephan
|
29 |
/** |
|
51c1efd…
|
stephan
|
30 |
Adds toggle checkboxes to each file entry in the diff views for |
|
51c1efd…
|
stephan
|
31 |
/info and similar pages. |
|
51c1efd…
|
stephan
|
32 |
*/ |
|
6f26395…
|
stephan
|
33 |
if( !window.fossil.page.diffControlContainer ){ |
|
6f26395…
|
stephan
|
34 |
return; |
|
6f26395…
|
stephan
|
35 |
} |
|
51c1efd…
|
stephan
|
36 |
const D = window.fossil.dom; |
|
903142f…
|
stephan
|
37 |
const allToggles = [/*collection of all diff-toggle checkboxes*/]; |
|
903142f…
|
stephan
|
38 |
let checkedCount = |
|
903142f…
|
stephan
|
39 |
0 /* When showing more than one diff, keep track of how many |
|
d83638e…
|
danield
|
40 |
"show/hide" checkboxes are checked so we can update the |
|
903142f…
|
stephan
|
41 |
"show/hide all" label dynamically. */; |
|
6f26395…
|
stephan
|
42 |
let btnAll /* UI control to show/hide all diffs */; |
|
6f26395…
|
stephan
|
43 |
/* Install a diff-toggle button for the given diff table element. */ |
|
51c1efd…
|
stephan
|
44 |
const addToggle = function(diffElem){ |
|
51c1efd…
|
stephan
|
45 |
const sib = diffElem.previousElementSibling, |
|
a718a76…
|
stephan
|
46 |
ckbox = sib ? D.addClass(D.checkbox(true), 'diff-toggle') : 0; |
|
51c1efd…
|
stephan
|
47 |
if(!sib) return; |
|
a718a76…
|
stephan
|
48 |
const lblToggle = D.label(); |
|
a718a76…
|
stephan
|
49 |
D.append(lblToggle, ckbox, D.text(" show/hide ")); |
|
a718a76…
|
stephan
|
50 |
allToggles.push(ckbox); |
|
903142f…
|
stephan
|
51 |
++checkedCount; |
|
6bc8c5f…
|
stephan
|
52 |
/* Make all of the available empty space a click zone for the checkbox */ |
|
6bc8c5f…
|
stephan
|
53 |
lblToggle.style.flexGrow = 1; |
|
6bc8c5f…
|
stephan
|
54 |
lblToggle.style.textAlign = 'right'; |
|
6bc8c5f…
|
stephan
|
55 |
D.append(sib, lblToggle); |
|
a718a76…
|
stephan
|
56 |
ckbox.addEventListener('change', function(){ |
|
a11d245…
|
stephan
|
57 |
diffElem.classList[this.checked ? 'remove' : 'add']('hidden'); |
|
903142f…
|
stephan
|
58 |
if(btnAll){ |
|
903142f…
|
stephan
|
59 |
checkedCount += (this.checked ? 1 : -1); |
|
903142f…
|
stephan
|
60 |
btnAll.innerText = (checkedCount < allToggles.length) |
|
903142f…
|
stephan
|
61 |
? "Show diffs" : "Hide diffs"; |
|
7f2b3a2…
|
stephan
|
62 |
} |
|
7f2b3a2…
|
stephan
|
63 |
}, false); |
|
7f2b3a2…
|
stephan
|
64 |
/* Extend the toggle click zone to all of the non-hyperlink |
|
7f2b3a2…
|
stephan
|
65 |
elements in the left of this area (filenames and hashes). */ |
|
7f2b3a2…
|
stephan
|
66 |
sib.firstElementChild.addEventListener('click', (event)=>{ |
|
7f2b3a2…
|
stephan
|
67 |
if( event.target===sib.firstElementChild ){ |
|
7f2b3a2…
|
stephan
|
68 |
/* Don't respond to clicks bubbling via hyperlink children */ |
|
88d8255…
|
stephan
|
69 |
ckbox.click();; |
|
6f26395…
|
stephan
|
70 |
} |
|
6f26395…
|
stephan
|
71 |
}, false); |
|
a11d245…
|
stephan
|
72 |
}; |
|
a11d245…
|
stephan
|
73 |
if( !document.querySelector('body.fdiff') ){ |
|
a11d245…
|
stephan
|
74 |
/* Don't show the diff toggle button for /fdiff because it only |
|
a11d245…
|
stephan
|
75 |
has a single file to show (and also a different DOM layout). */ |
|
a11d245…
|
stephan
|
76 |
document.querySelectorAll('table.diff').forEach(addToggle); |
|
a718a76…
|
stephan
|
77 |
} |
|
6f26395…
|
stephan
|
78 |
/** |
|
6f26395…
|
stephan
|
79 |
Set up a "toggle all diffs" button which toggles all of the |
|
6f26395…
|
stephan
|
80 |
above-installed checkboxes, but only if more than one diff is |
|
6f26395…
|
stephan
|
81 |
rendered. |
|
6f26395…
|
stephan
|
82 |
*/ |
|
a718a76…
|
stephan
|
83 |
const icm = allToggles.length>1 ? window.fossil.page.diffControlContainer : 0; |
|
a718a76…
|
stephan
|
84 |
if(icm) { |
|
903142f…
|
stephan
|
85 |
btnAll = D.addClass(D.a("#", "Hide diffs"), "button"); |
|
a718a76…
|
stephan
|
86 |
D.append( icm, btnAll ); |
|
a718a76…
|
stephan
|
87 |
btnAll.addEventListener('click', function(ev){ |
|
a718a76…
|
stephan
|
88 |
ev.preventDefault(); |
|
a718a76…
|
stephan
|
89 |
ev.stopPropagation(); |
|
903142f…
|
stephan
|
90 |
const show = checkedCount < allToggles.length; |
|
903142f…
|
stephan
|
91 |
for( const ckbox of allToggles ){ |
|
a718a76…
|
stephan
|
92 |
/* Toggle all entries to match this new state. We use click() |
|
a718a76…
|
stephan
|
93 |
instead of ckbox.checked=... so that the on-change event handler |
|
a718a76…
|
stephan
|
94 |
fires. */ |
|
a718a76…
|
stephan
|
95 |
if(ckbox.checked!==show) ckbox.click(); |
|
a718a76…
|
stephan
|
96 |
} |
|
a718a76…
|
stephan
|
97 |
}, false); |
|
a11d245…
|
stephan
|
98 |
} |
|
51c1efd…
|
stephan
|
99 |
}); |
|
51c1efd…
|
stephan
|
100 |
|
|
51c1efd…
|
stephan
|
101 |
window.fossil.onPageLoad(function(){ |
|
51c1efd…
|
stephan
|
102 |
const F = window.fossil, D = F.dom; |
|
51c1efd…
|
stephan
|
103 |
const Diff = F.diff = { |
|
51c1efd…
|
stephan
|
104 |
e:{/*certain cached DOM elements*/}, |
|
51c1efd…
|
stephan
|
105 |
config: { |
|
51c1efd…
|
stephan
|
106 |
chunkLoadLines: ( |
|
51c1efd…
|
stephan
|
107 |
F.config.diffContextLines * 3 |
|
51c1efd…
|
stephan
|
108 |
/*per /chat discussion*/ |
|
51c1efd…
|
stephan
|
109 |
) || 20, |
|
51c1efd…
|
stephan
|
110 |
chunkFetch: { |
|
e2bdc10…
|
danield
|
111 |
/* Default callback handlers for Diff.fetchArtifactChunk(), |
|
51c1efd…
|
stephan
|
112 |
unless overridden by options passeed to that function. */ |
|
51c1efd…
|
stephan
|
113 |
beforesend: function(){}, |
|
51c1efd…
|
stephan
|
114 |
aftersend: function(){}, |
|
51c1efd…
|
stephan
|
115 |
onerror: function(e){ |
|
ba40082…
|
stephan
|
116 |
console.error("XHR error: ",e); |
|
51c1efd…
|
stephan
|
117 |
} |
|
51c1efd…
|
stephan
|
118 |
} |
|
51c1efd…
|
stephan
|
119 |
} |
|
51c1efd…
|
stephan
|
120 |
}; |
|
51c1efd…
|
stephan
|
121 |
/** |
|
51c1efd…
|
stephan
|
122 |
Uses the /jchunk AJAX route to fetch specific lines of a given |
|
51c1efd…
|
stephan
|
123 |
artifact. The argument must be an Object suitable for passing as |
|
51c1efd…
|
stephan
|
124 |
the second argument to fossil.fetch(). Its urlParams property |
|
51c1efd…
|
stephan
|
125 |
must be an object with these properties: |
|
51c1efd…
|
stephan
|
126 |
|
|
51c1efd…
|
stephan
|
127 |
{ |
|
51c1efd…
|
stephan
|
128 |
name: full hash of the target file, |
|
51c1efd…
|
stephan
|
129 |
from: first 1-based line number of the file to fetch (inclusive), |
|
51c1efd…
|
stephan
|
130 |
to: last 1-based line number of the file to fetch (inclusive) |
|
51c1efd…
|
stephan
|
131 |
} |
|
51c1efd…
|
stephan
|
132 |
|
|
51c1efd…
|
stephan
|
133 |
The fetchOpt object is NOT cloned for use by the call: it is used |
|
51c1efd…
|
stephan
|
134 |
as-is and may be modified by this call. Thus callers "really |
|
51c1efd…
|
stephan
|
135 |
should" pass a temporary object, not a long-lived one. |
|
51c1efd…
|
stephan
|
136 |
|
|
51c1efd…
|
stephan
|
137 |
If fetchOpt does not define any of the (beforesend, aftersend, |
|
51c1efd…
|
stephan
|
138 |
onerror) callbacks, the defaults from fossil.diff.config.chunkFetch |
|
51c1efd…
|
stephan
|
139 |
are used, so any given client page may override those to provide |
|
51c1efd…
|
stephan
|
140 |
page-level default handling. |
|
51c1efd…
|
stephan
|
141 |
|
|
51c1efd…
|
stephan
|
142 |
Note that onload callback is ostensibly optional but this |
|
51c1efd…
|
stephan
|
143 |
function is not of much use without an onload |
|
51c1efd…
|
stephan
|
144 |
handler. Conversely, the default onerror handler is often |
|
51c1efd…
|
stephan
|
145 |
customized on a per-page basis to send the error output somewhere |
|
51c1efd…
|
stephan
|
146 |
where the user can see it. |
|
51c1efd…
|
stephan
|
147 |
|
|
51c1efd…
|
stephan
|
148 |
The response, on success, will be an array of strings, each entry |
|
51c1efd…
|
stephan
|
149 |
being one line from the requested artifact. If the 'to' line is |
|
51c1efd…
|
stephan
|
150 |
greater than the length of the file, the array will be shorter |
|
51c1efd…
|
stephan
|
151 |
than (to-from) lines. |
|
51c1efd…
|
stephan
|
152 |
|
|
51c1efd…
|
stephan
|
153 |
The /jchunk route reports errors via JSON objects with |
|
51c1efd…
|
stephan
|
154 |
an "error" string property describing the problem. |
|
51c1efd…
|
stephan
|
155 |
|
|
51c1efd…
|
stephan
|
156 |
This is an async operation. Returns the fossil object. |
|
51c1efd…
|
stephan
|
157 |
*/ |
|
51c1efd…
|
stephan
|
158 |
Diff.fetchArtifactChunk = function(fetchOpt){ |
|
51c1efd…
|
stephan
|
159 |
if(!fetchOpt.beforesend) fetchOpt.beforesend = Diff.config.chunkFetch.beforesend; |
|
51c1efd…
|
stephan
|
160 |
if(!fetchOpt.aftersend) fetchOpt.aftersend = Diff.config.chunkFetch.aftersend; |
|
51c1efd…
|
stephan
|
161 |
if(!fetchOpt.onerror) fetchOpt.onerror = Diff.config.chunkFetch.onerror; |
|
51c1efd…
|
stephan
|
162 |
fetchOpt.responseType = 'json'; |
|
51c1efd…
|
stephan
|
163 |
return F.fetch('jchunk', fetchOpt); |
|
51c1efd…
|
stephan
|
164 |
}; |
|
51c1efd…
|
stephan
|
165 |
|
|
51c1efd…
|
stephan
|
166 |
|
|
51c1efd…
|
stephan
|
167 |
/** |
|
51c1efd…
|
stephan
|
168 |
Extracts either the starting or ending line number from a |
|
51c1efd…
|
stephan
|
169 |
line-numer column in the given tr. isSplit must be true if tr |
|
51c1efd…
|
stephan
|
170 |
represents a split diff, else false. Expects its tr to be valid: |
|
51c1efd…
|
stephan
|
171 |
GIGO applies. Returns the starting line number if getStart, else |
|
51c1efd…
|
stephan
|
172 |
the ending line number. Returns the line number from the LHS file |
|
51c1efd…
|
stephan
|
173 |
if getLHS is true, else the RHS. |
|
51c1efd…
|
stephan
|
174 |
*/ |
|
51c1efd…
|
stephan
|
175 |
const extractLineNo = function f(getLHS, getStart, tr, isSplit){ |
|
51c1efd…
|
stephan
|
176 |
if(!f.rx){ |
|
51c1efd…
|
stephan
|
177 |
f.rx = { |
|
51c1efd…
|
stephan
|
178 |
start: /^\s*(\d+)/, |
|
51c1efd…
|
stephan
|
179 |
end: /(\d+)\n?$/ |
|
51c1efd…
|
stephan
|
180 |
} |
|
51c1efd…
|
stephan
|
181 |
} |
|
51c1efd…
|
stephan
|
182 |
const td = tr.querySelector('td:nth-child('+( |
|
51c1efd…
|
stephan
|
183 |
/* TD element with the line numbers */ |
|
51c1efd…
|
stephan
|
184 |
getLHS ? 1 : (isSplit ? 4 : 2) |
|
51c1efd…
|
stephan
|
185 |
)+')'); |
|
51c1efd…
|
stephan
|
186 |
const m = f.rx[getStart ? 'start' : 'end'].exec(td.innerText); |
|
51c1efd…
|
stephan
|
187 |
return m ? +m[1] : undefined/*"shouldn't happen"*/; |
|
51c1efd…
|
stephan
|
188 |
}; |
|
71e9ca7…
|
stephan
|
189 |
|
|
51c1efd…
|
stephan
|
190 |
/** |
|
51c1efd…
|
stephan
|
191 |
Installs chunk-loading controls into TR.diffskip element tr. |
|
51c1efd…
|
stephan
|
192 |
Each instance corresponds to a single TR.diffskip element. |
|
51c1efd…
|
stephan
|
193 |
|
|
51c1efd…
|
stephan
|
194 |
The goal is to base these controls roughly on github's, a good |
|
51c1efd…
|
stephan
|
195 |
example of which, for use as a model, is: |
|
51c1efd…
|
stephan
|
196 |
|
|
51c1efd…
|
stephan
|
197 |
https://github.com/msteveb/autosetup/commit/235925e914a52a542 |
|
51c1efd…
|
stephan
|
198 |
*/ |
|
51c1efd…
|
stephan
|
199 |
const ChunkLoadControls = function(tr){ |
|
a49393a…
|
stephan
|
200 |
this.$fetchQueue = []; |
|
51c1efd…
|
stephan
|
201 |
this.e = {/*DOM elements*/ |
|
51c1efd…
|
stephan
|
202 |
tr: tr, |
|
51c1efd…
|
stephan
|
203 |
table: tr.parentElement/*TBODY*/.parentElement |
|
51c1efd…
|
stephan
|
204 |
}; |
|
51c1efd…
|
stephan
|
205 |
this.isSplit = this.e.table.classList.contains('splitdiff')/*else udiff*/; |
|
51c1efd…
|
stephan
|
206 |
this.fileHash = this.e.table.dataset.lefthash; |
|
51c1efd…
|
stephan
|
207 |
tr.$chunker = this /* keep GC from reaping this */; |
|
51c1efd…
|
stephan
|
208 |
this.pos = { |
|
51c1efd…
|
stephan
|
209 |
/* These line numbers correspond to the LHS file. Because the |
|
51c1efd…
|
stephan
|
210 |
contents are common to both sides, we have the same number |
|
51c1efd…
|
stephan
|
211 |
for the RHS, but need to extract those line numbers from the |
|
51c1efd…
|
stephan
|
212 |
neighboring TR blocks */ |
|
51c1efd…
|
stephan
|
213 |
startLhs: +tr.dataset.startln, |
|
51c1efd…
|
stephan
|
214 |
endLhs: +tr.dataset.endln |
|
51c1efd…
|
stephan
|
215 |
}; |
|
51c1efd…
|
stephan
|
216 |
D.clearElement(tr); |
|
51c1efd…
|
stephan
|
217 |
this.e.td = D.addClass( |
|
51c1efd…
|
stephan
|
218 |
/* Holder for our UI controls */ |
|
51c1efd…
|
stephan
|
219 |
D.attr(D.td(tr), 'colspan', this.isSplit ? 5 : 4), |
|
51c1efd…
|
stephan
|
220 |
'chunkctrl' |
|
51c1efd…
|
stephan
|
221 |
); |
|
ba40082…
|
stephan
|
222 |
this.e.msgWidget = D.addClass(D.span(), 'hidden'); |
|
51c1efd…
|
stephan
|
223 |
this.e.btnWrapper = D.div(); |
|
51c1efd…
|
stephan
|
224 |
D.append(this.e.td, this.e.btnWrapper); |
|
51c1efd…
|
stephan
|
225 |
/** |
|
51c1efd…
|
stephan
|
226 |
Depending on various factors, we need one or more of: |
|
51c1efd…
|
stephan
|
227 |
|
|
51c1efd…
|
stephan
|
228 |
- A single button to load the initial chunk incrementally |
|
51c1efd…
|
stephan
|
229 |
|
|
51c1efd…
|
stephan
|
230 |
- A single button to load all lines then remove this control |
|
51c1efd…
|
stephan
|
231 |
|
|
51c1efd…
|
stephan
|
232 |
- Two buttons: one to load upwards, one to load downwards |
|
51c1efd…
|
stephan
|
233 |
|
|
51c1efd…
|
stephan
|
234 |
- A single button to load the final chunk incrementally |
|
51c1efd…
|
stephan
|
235 |
*/ |
|
51c1efd…
|
stephan
|
236 |
if(tr.nextElementSibling){ |
|
51c1efd…
|
stephan
|
237 |
this.pos.next = { |
|
51c1efd…
|
stephan
|
238 |
startLhs: extractLineNo(true, true, tr.nextElementSibling, this.isSplit), |
|
51c1efd…
|
stephan
|
239 |
startRhs: extractLineNo(false, true, tr.nextElementSibling, this.isSplit) |
|
51c1efd…
|
stephan
|
240 |
}; |
|
51c1efd…
|
stephan
|
241 |
} |
|
51c1efd…
|
stephan
|
242 |
if(tr.previousElementSibling){ |
|
51c1efd…
|
stephan
|
243 |
this.pos.prev = { |
|
51c1efd…
|
stephan
|
244 |
endLhs: extractLineNo(true, false, tr.previousElementSibling, this.isSplit), |
|
51c1efd…
|
stephan
|
245 |
endRhs: extractLineNo(false, false, tr.previousElementSibling, this.isSplit) |
|
51c1efd…
|
stephan
|
246 |
}; |
|
51c1efd…
|
stephan
|
247 |
} |
|
51c1efd…
|
stephan
|
248 |
let btnUp = false, btnDown = false; |
|
51c1efd…
|
stephan
|
249 |
/** |
|
51c1efd…
|
stephan
|
250 |
this.pos.next refers to the line numbers in the next TR's chunk. |
|
51c1efd…
|
stephan
|
251 |
this.pos.prev refers to the line numbers in the previous TR's chunk. |
|
8347c4a…
|
florian
|
252 |
this.pos corresponds to the line numbers of the gap. |
|
51c1efd…
|
stephan
|
253 |
*/ |
|
7b1e2aa…
|
stephan
|
254 |
if(this.pos.prev && this.pos.next |
|
8347c4a…
|
florian
|
255 |
&& ((this.pos.endLhs - this.pos.startLhs) |
|
7b1e2aa…
|
stephan
|
256 |
<= Diff.config.chunkLoadLines)){ |
|
51c1efd…
|
stephan
|
257 |
/* Place a single button to load the whole block, rather |
|
51c1efd…
|
stephan
|
258 |
than separate up/down buttons. */ |
|
51c1efd…
|
stephan
|
259 |
btnDown = false; |
|
51c1efd…
|
stephan
|
260 |
btnUp = this.createButton(this.FetchType.FillGap); |
|
51c1efd…
|
stephan
|
261 |
}else{ |
|
51c1efd…
|
stephan
|
262 |
/* Figure out which chunk-load buttons to add... */ |
|
51c1efd…
|
stephan
|
263 |
if(this.pos.prev){ |
|
51c1efd…
|
stephan
|
264 |
btnDown = this.createButton(this.FetchType.PrevDown); |
|
51c1efd…
|
stephan
|
265 |
} |
|
51c1efd…
|
stephan
|
266 |
if(this.pos.next){ |
|
51c1efd…
|
stephan
|
267 |
btnUp = this.createButton(this.FetchType.NextUp); |
|
51c1efd…
|
stephan
|
268 |
} |
|
51c1efd…
|
stephan
|
269 |
} |
|
51c1efd…
|
stephan
|
270 |
//this.e.btnUp = btnUp; |
|
51c1efd…
|
stephan
|
271 |
//this.e.btnDown = btnDown; |
|
51c1efd…
|
stephan
|
272 |
if(btnUp) D.append(this.e.btnWrapper, btnUp); |
|
4e45fcc…
|
stephan
|
273 |
if(btnDown) D.append(this.e.btnWrapper, btnDown); |
|
ba40082…
|
stephan
|
274 |
D.append(this.e.btnWrapper, this.e.msgWidget); |
|
51c1efd…
|
stephan
|
275 |
/* For debugging only... */ |
|
51c1efd…
|
stephan
|
276 |
this.e.posState = D.span(); |
|
51c1efd…
|
stephan
|
277 |
D.append(this.e.btnWrapper, this.e.posState); |
|
51c1efd…
|
stephan
|
278 |
this.updatePosDebug(); |
|
51c1efd…
|
stephan
|
279 |
}; |
|
51c1efd…
|
stephan
|
280 |
|
|
51c1efd…
|
stephan
|
281 |
ChunkLoadControls.prototype = { |
|
51c1efd…
|
stephan
|
282 |
/** An "enum" of values describing the types of context |
|
51c1efd…
|
stephan
|
283 |
fetches/operations performed by this type. The values in this |
|
51c1efd…
|
stephan
|
284 |
object must not be changed without modifying all logic which |
|
51c1efd…
|
stephan
|
285 |
relies on their relative order. */ |
|
51c1efd…
|
stephan
|
286 |
FetchType:{ |
|
51c1efd…
|
stephan
|
287 |
/** Append context to the bottom of the previous diff chunk. */ |
|
51c1efd…
|
stephan
|
288 |
PrevDown: 1, |
|
51c1efd…
|
stephan
|
289 |
/** Fill a complete gap between the previous/next diff chunks |
|
51c1efd…
|
stephan
|
290 |
or at the start of the next chunk or end of the previous |
|
51c1efd…
|
stephan
|
291 |
chunks. */ |
|
51c1efd…
|
stephan
|
292 |
FillGap: 0, |
|
51c1efd…
|
stephan
|
293 |
/** Prepend context to the start of the next diff chunk. */ |
|
a49393a…
|
stephan
|
294 |
NextUp: -1, |
|
a49393a…
|
stephan
|
295 |
/** Process the next queued action. */ |
|
a49393a…
|
stephan
|
296 |
ProcessQueue: 0x7fffffff |
|
51c1efd…
|
stephan
|
297 |
}, |
|
51c1efd…
|
stephan
|
298 |
|
|
51c1efd…
|
stephan
|
299 |
/** |
|
51c1efd…
|
stephan
|
300 |
Creates and returns a button element for fetching a chunk in |
|
51c1efd…
|
stephan
|
301 |
the given fetchType (as documented for fetchChunk()). |
|
51c1efd…
|
stephan
|
302 |
*/ |
|
51c1efd…
|
stephan
|
303 |
createButton: function(fetchType){ |
|
51c1efd…
|
stephan
|
304 |
let b; |
|
51c1efd…
|
stephan
|
305 |
switch(fetchType){ |
|
51c1efd…
|
stephan
|
306 |
case this.FetchType.PrevDown: |
|
51c1efd…
|
stephan
|
307 |
b = D.append( |
|
51c1efd…
|
stephan
|
308 |
D.addClass(D.span(), 'down'), |
|
51c1efd…
|
stephan
|
309 |
D.span(/*glyph holder*/) |
|
51c1efd…
|
stephan
|
310 |
); |
|
51c1efd…
|
stephan
|
311 |
break; |
|
51c1efd…
|
stephan
|
312 |
case this.FetchType.FillGap: |
|
51c1efd…
|
stephan
|
313 |
b = D.append( |
|
51c1efd…
|
stephan
|
314 |
D.addClass(D.span(), 'up', 'down'), |
|
51c1efd…
|
stephan
|
315 |
D.span(/*glyph holder*/) |
|
51c1efd…
|
stephan
|
316 |
); |
|
51c1efd…
|
stephan
|
317 |
break; |
|
51c1efd…
|
stephan
|
318 |
case this.FetchType.NextUp: |
|
51c1efd…
|
stephan
|
319 |
b = D.append( |
|
51c1efd…
|
stephan
|
320 |
D.addClass(D.span(), 'up'), |
|
51c1efd…
|
stephan
|
321 |
D.span(/*glyph holder*/) |
|
51c1efd…
|
stephan
|
322 |
); |
|
51c1efd…
|
stephan
|
323 |
break; |
|
51c1efd…
|
stephan
|
324 |
default: |
|
51c1efd…
|
stephan
|
325 |
throw new Error("Internal API misuse: unexpected fetchType value "+fetchType); |
|
51c1efd…
|
stephan
|
326 |
} |
|
51c1efd…
|
stephan
|
327 |
D.addClass(b, 'jcbutton'); |
|
51c1efd…
|
stephan
|
328 |
b.addEventListener('click', ()=>this.fetchChunk(fetchType),false); |
|
51c1efd…
|
stephan
|
329 |
return b; |
|
51c1efd…
|
stephan
|
330 |
}, |
|
51c1efd…
|
stephan
|
331 |
|
|
51c1efd…
|
stephan
|
332 |
updatePosDebug: function(){ |
|
51c1efd…
|
stephan
|
333 |
if(this.e.posState){ |
|
51c1efd…
|
stephan
|
334 |
D.clearElement(this.e.posState); |
|
51c1efd…
|
stephan
|
335 |
//D.append(D.clearElement(this.e.posState), JSON.stringify(this.pos)); |
|
51c1efd…
|
stephan
|
336 |
} |
|
51c1efd…
|
stephan
|
337 |
return this; |
|
51c1efd…
|
stephan
|
338 |
}, |
|
51c1efd…
|
stephan
|
339 |
|
|
51c1efd…
|
stephan
|
340 |
/* Attempt to clean up resources and remove some circular references to |
|
51c1efd…
|
stephan
|
341 |
that GC can do the right thing. */ |
|
51c1efd…
|
stephan
|
342 |
destroy: function(){ |
|
a49393a…
|
stephan
|
343 |
delete this.$fetchQueue; |
|
51c1efd…
|
stephan
|
344 |
D.remove(this.e.tr); |
|
51c1efd…
|
stephan
|
345 |
delete this.e.tr.$chunker; |
|
51c1efd…
|
stephan
|
346 |
delete this.e.tr; |
|
51c1efd…
|
stephan
|
347 |
delete this.e; |
|
51c1efd…
|
stephan
|
348 |
delete this.pos; |
|
51c1efd…
|
stephan
|
349 |
}, |
|
51c1efd…
|
stephan
|
350 |
|
|
51c1efd…
|
stephan
|
351 |
/** |
|
51c1efd…
|
stephan
|
352 |
If the gap between this.pos.endLhs/startLhs is less than or equal to |
|
51c1efd…
|
stephan
|
353 |
Diff.config.chunkLoadLines then this function replaces any up/down buttons |
|
51c1efd…
|
stephan
|
354 |
with a gap-filler button, else it's a no-op. Returns this object. |
|
7b1e2aa…
|
stephan
|
355 |
|
|
7b1e2aa…
|
stephan
|
356 |
As a special case, do not apply this at the start or bottom |
|
7b1e2aa…
|
stephan
|
357 |
of the diff, only between two diff chunks. |
|
7b1e2aa…
|
stephan
|
358 |
*/ |
|
51c1efd…
|
stephan
|
359 |
maybeReplaceButtons: function(){ |
|
7b1e2aa…
|
stephan
|
360 |
if(this.pos.next && this.pos.prev |
|
7b1e2aa…
|
stephan
|
361 |
&& (this.pos.endLhs - this.pos.startLhs <= Diff.config.chunkLoadLines)){ |
|
51c1efd…
|
stephan
|
362 |
D.clearElement(this.e.btnWrapper); |
|
51c1efd…
|
stephan
|
363 |
D.append(this.e.btnWrapper, this.createButton(this.FetchType.FillGap)); |
|
4b1cf8d…
|
florian
|
364 |
if( this.$fetchQueue && this.$fetchQueue.length>1 ){ |
|
4b1cf8d…
|
florian
|
365 |
this.$fetchQueue[1] = this.FetchType.FillGap; |
|
4b1cf8d…
|
florian
|
366 |
this.$fetchQueue.length = 2; |
|
a49393a…
|
stephan
|
367 |
} |
|
51c1efd…
|
stephan
|
368 |
} |
|
51c1efd…
|
stephan
|
369 |
return this; |
|
51c1efd…
|
stephan
|
370 |
}, |
|
51c1efd…
|
stephan
|
371 |
|
|
51c1efd…
|
stephan
|
372 |
/** |
|
4d575dc…
|
florian
|
373 |
Callback for /jchunk responses. |
|
51c1efd…
|
stephan
|
374 |
*/ |
|
51c1efd…
|
stephan
|
375 |
injectResponse: function f(fetchType/*as for fetchChunk()*/, |
|
51c1efd…
|
stephan
|
376 |
urlParam/*from fetchChunk()*/, |
|
51c1efd…
|
stephan
|
377 |
lines/*response lines*/){ |
|
51c1efd…
|
stephan
|
378 |
if(!lines.length){ |
|
51c1efd…
|
stephan
|
379 |
/* No more data to load */ |
|
51c1efd…
|
stephan
|
380 |
this.destroy(); |
|
51c1efd…
|
stephan
|
381 |
return this; |
|
51c1efd…
|
stephan
|
382 |
} |
|
ba40082…
|
stephan
|
383 |
this.msg(false); |
|
51c1efd…
|
stephan
|
384 |
//console.debug("Loaded line range ", |
|
51c1efd…
|
stephan
|
385 |
//urlParam.from,"-",urlParam.to, "fetchType ",fetchType); |
|
51c1efd…
|
stephan
|
386 |
const lineno = [], |
|
51c1efd…
|
stephan
|
387 |
trPrev = this.e.tr.previousElementSibling, |
|
51c1efd…
|
stephan
|
388 |
trNext = this.e.tr.nextElementSibling, |
|
51c1efd…
|
stephan
|
389 |
doAppend = ( |
|
51c1efd…
|
stephan
|
390 |
!!trPrev && fetchType>=this.FetchType.FillGap |
|
51c1efd…
|
stephan
|
391 |
) /* true to append to previous TR, else prepend to NEXT TR */; |
|
51c1efd…
|
stephan
|
392 |
const tr = doAppend ? trPrev : trNext; |
|
51c1efd…
|
stephan
|
393 |
const joinTr = ( |
|
51c1efd…
|
stephan
|
394 |
this.FetchType.FillGap===fetchType && trPrev && trNext |
|
51c1efd…
|
stephan
|
395 |
) ? trNext : false |
|
51c1efd…
|
stephan
|
396 |
/* Truthy if we want to combine trPrev, the new content, and |
|
51c1efd…
|
stephan
|
397 |
trNext into trPrev and then remove trNext. */; |
|
51c1efd…
|
stephan
|
398 |
let i, td; |
|
51c1efd…
|
stephan
|
399 |
if(!f.convertLines){ |
|
4888719…
|
stephan
|
400 |
/* Reminder: string.replaceAll() is a relatively new |
|
4888719…
|
stephan
|
401 |
JS feature, not available in some still-widely-used |
|
4888719…
|
stephan
|
402 |
browser versions. */ |
|
4888719…
|
stephan
|
403 |
f.rx = [[/&/g, '&'], [/</g, '<']]; |
|
51c1efd…
|
stephan
|
404 |
f.convertLines = function(li){ |
|
4888719…
|
stephan
|
405 |
var s = li.join('\n'); |
|
4888719…
|
stephan
|
406 |
f.rx.forEach((a)=>s=s.replace(a[0],a[1])); |
|
4888719…
|
stephan
|
407 |
return s + '\n'; |
|
51c1efd…
|
stephan
|
408 |
}; |
|
51c1efd…
|
stephan
|
409 |
} |
|
51c1efd…
|
stephan
|
410 |
if(1){ // LHS line numbers... |
|
51c1efd…
|
stephan
|
411 |
const selector = '.difflnl > pre'; |
|
51c1efd…
|
stephan
|
412 |
td = tr.querySelector(selector); |
|
51c1efd…
|
stephan
|
413 |
const lnTo = Math.min(urlParam.to, |
|
51c1efd…
|
stephan
|
414 |
urlParam.from + |
|
51c1efd…
|
stephan
|
415 |
lines.length - 1/*b/c request range is inclusive*/); |
|
51c1efd…
|
stephan
|
416 |
for( i = urlParam.from; i <= lnTo; ++i ){ |
|
51c1efd…
|
stephan
|
417 |
lineno.push(i); |
|
51c1efd…
|
stephan
|
418 |
} |
|
51c1efd…
|
stephan
|
419 |
const lineNoTxt = lineno.join('\n')+'\n'; |
|
51c1efd…
|
stephan
|
420 |
const content = [td.innerHTML]; |
|
51c1efd…
|
stephan
|
421 |
if(doAppend) content.push(lineNoTxt); |
|
51c1efd…
|
stephan
|
422 |
else content.unshift(lineNoTxt); |
|
51c1efd…
|
stephan
|
423 |
if(joinTr){ |
|
51c1efd…
|
stephan
|
424 |
content.push(trNext.querySelector(selector).innerHTML); |
|
51c1efd…
|
stephan
|
425 |
} |
|
51c1efd…
|
stephan
|
426 |
td.innerHTML = content.join(''); |
|
51c1efd…
|
stephan
|
427 |
} |
|
51c1efd…
|
stephan
|
428 |
|
|
51c1efd…
|
stephan
|
429 |
if(1){// code block(s)... |
|
51c1efd…
|
stephan
|
430 |
const selector = '.difftxt > pre'; |
|
51c1efd…
|
stephan
|
431 |
td = tr.querySelectorAll(selector); |
|
51c1efd…
|
stephan
|
432 |
const code = f.convertLines(lines); |
|
4888719…
|
stephan
|
433 |
let joinNdx = 0/*selector[X] index to join together*/; |
|
51c1efd…
|
stephan
|
434 |
td.forEach(function(e){ |
|
51c1efd…
|
stephan
|
435 |
const content = [e.innerHTML]; |
|
51c1efd…
|
stephan
|
436 |
if(doAppend) content.push(code); |
|
51c1efd…
|
stephan
|
437 |
else content.unshift(code); |
|
51c1efd…
|
stephan
|
438 |
if(joinTr){ |
|
51c1efd…
|
stephan
|
439 |
content.push(trNext.querySelectorAll(selector)[joinNdx++].innerHTML) |
|
51c1efd…
|
stephan
|
440 |
} |
|
51c1efd…
|
stephan
|
441 |
e.innerHTML = content.join(''); |
|
51c1efd…
|
stephan
|
442 |
}); |
|
51c1efd…
|
stephan
|
443 |
} |
|
51c1efd…
|
stephan
|
444 |
|
|
51c1efd…
|
stephan
|
445 |
if(1){// Add blank lines in (.diffsep>pre) |
|
51c1efd…
|
stephan
|
446 |
const selector = '.diffsep > pre'; |
|
51c1efd…
|
stephan
|
447 |
td = tr.querySelector(selector); |
|
51c1efd…
|
stephan
|
448 |
for(i = 0; i < lineno.length; ++i) lineno[i] = ''; |
|
51c1efd…
|
stephan
|
449 |
const blanks = lineno.join('\n')+'\n'; |
|
51c1efd…
|
stephan
|
450 |
const content = [td.innerHTML]; |
|
51c1efd…
|
stephan
|
451 |
if(doAppend) content.push(blanks); |
|
51c1efd…
|
stephan
|
452 |
else content.unshift(blanks); |
|
51c1efd…
|
stephan
|
453 |
if(joinTr){ |
|
51c1efd…
|
stephan
|
454 |
content.push(trNext.querySelector(selector).innerHTML); |
|
51c1efd…
|
stephan
|
455 |
} |
|
51c1efd…
|
stephan
|
456 |
td.innerHTML = content.join(''); |
|
51c1efd…
|
stephan
|
457 |
} |
|
51c1efd…
|
stephan
|
458 |
|
|
51c1efd…
|
stephan
|
459 |
if(this.FetchType.FillGap===fetchType){ |
|
51c1efd…
|
stephan
|
460 |
/* Closing the whole gap between two chunks or a whole gap |
|
51c1efd…
|
stephan
|
461 |
at the start or end of a diff. */ |
|
51c1efd…
|
stephan
|
462 |
// RHS line numbers... |
|
51c1efd…
|
stephan
|
463 |
let startLnR = this.pos.prev |
|
51c1efd…
|
stephan
|
464 |
? this.pos.prev.endRhs+1 /* Closing the whole gap between two chunks |
|
51c1efd…
|
stephan
|
465 |
or end-of-file gap. */ |
|
51c1efd…
|
stephan
|
466 |
: this.pos.next.startRhs - lines.length /* start-of-file gap */; |
|
51c1efd…
|
stephan
|
467 |
lineno.length = lines.length; |
|
51c1efd…
|
stephan
|
468 |
for( i = startLnR; i < startLnR + lines.length; ++i ){ |
|
51c1efd…
|
stephan
|
469 |
lineno[i-startLnR] = i; |
|
51c1efd…
|
stephan
|
470 |
} |
|
51c1efd…
|
stephan
|
471 |
const selector = '.difflnr > pre'; |
|
51c1efd…
|
stephan
|
472 |
td = tr.querySelector(selector); |
|
51c1efd…
|
stephan
|
473 |
const lineNoTxt = lineno.join('\n')+'\n'; |
|
51c1efd…
|
stephan
|
474 |
lineno.length = 0; |
|
51c1efd…
|
stephan
|
475 |
const content = [td.innerHTML]; |
|
51c1efd…
|
stephan
|
476 |
if(doAppend) content.push(lineNoTxt); |
|
51c1efd…
|
stephan
|
477 |
else content.unshift(lineNoTxt); |
|
51c1efd…
|
stephan
|
478 |
if(joinTr){ |
|
51c1efd…
|
stephan
|
479 |
content.push(trNext.querySelector(selector).innerHTML); |
|
51c1efd…
|
stephan
|
480 |
} |
|
51c1efd…
|
stephan
|
481 |
td.innerHTML = content.join(''); |
|
51c1efd…
|
stephan
|
482 |
if(joinTr) D.remove(joinTr); |
|
51c1efd…
|
stephan
|
483 |
this.destroy(); |
|
51c1efd…
|
stephan
|
484 |
return this; |
|
51c1efd…
|
stephan
|
485 |
}else if(this.FetchType.PrevDown===fetchType){ |
|
51c1efd…
|
stephan
|
486 |
/* Append context to previous TR. */ |
|
51c1efd…
|
stephan
|
487 |
// RHS line numbers... |
|
51c1efd…
|
stephan
|
488 |
let startLnR = this.pos.prev.endRhs+1; |
|
51c1efd…
|
stephan
|
489 |
lineno.length = lines.length; |
|
51c1efd…
|
stephan
|
490 |
for( i = startLnR; i < startLnR + lines.length; ++i ){ |
|
51c1efd…
|
stephan
|
491 |
lineno[i-startLnR] = i; |
|
51c1efd…
|
stephan
|
492 |
} |
|
51c1efd…
|
stephan
|
493 |
this.pos.startLhs += lines.length; |
|
51c1efd…
|
stephan
|
494 |
this.pos.prev.endRhs += lines.length; |
|
51c1efd…
|
stephan
|
495 |
this.pos.prev.endLhs += lines.length; |
|
51c1efd…
|
stephan
|
496 |
const selector = '.difflnr > pre'; |
|
51c1efd…
|
stephan
|
497 |
td = tr.querySelector(selector); |
|
51c1efd…
|
stephan
|
498 |
const lineNoTxt = lineno.join('\n')+'\n'; |
|
51c1efd…
|
stephan
|
499 |
lineno.length = 0; |
|
51c1efd…
|
stephan
|
500 |
const content = [td.innerHTML]; |
|
51c1efd…
|
stephan
|
501 |
if(doAppend) content.push(lineNoTxt); |
|
51c1efd…
|
stephan
|
502 |
else content.unshift(lineNoTxt); |
|
51c1efd…
|
stephan
|
503 |
td.innerHTML = content.join(''); |
|
51c1efd…
|
stephan
|
504 |
if(lines.length < (urlParam.to - urlParam.from)){ |
|
51c1efd…
|
stephan
|
505 |
/* No more data. */ |
|
51c1efd…
|
stephan
|
506 |
this.destroy(); |
|
51c1efd…
|
stephan
|
507 |
}else{ |
|
51c1efd…
|
stephan
|
508 |
this.maybeReplaceButtons(); |
|
51c1efd…
|
stephan
|
509 |
this.updatePosDebug(); |
|
51c1efd…
|
stephan
|
510 |
} |
|
51c1efd…
|
stephan
|
511 |
return this; |
|
51c1efd…
|
stephan
|
512 |
}else if(this.FetchType.NextUp===fetchType){ |
|
51c1efd…
|
stephan
|
513 |
/* Prepend content to next TR. */ |
|
51c1efd…
|
stephan
|
514 |
// RHS line numbers... |
|
51c1efd…
|
stephan
|
515 |
if(doAppend){ |
|
51c1efd…
|
stephan
|
516 |
throw new Error("Internal precondition violation: doAppend is true."); |
|
51c1efd…
|
stephan
|
517 |
} |
|
51c1efd…
|
stephan
|
518 |
let startLnR = this.pos.next.startRhs - lines.length; |
|
51c1efd…
|
stephan
|
519 |
lineno.length = lines.length; |
|
51c1efd…
|
stephan
|
520 |
for( i = startLnR; i < startLnR + lines.length; ++i ){ |
|
51c1efd…
|
stephan
|
521 |
lineno[i-startLnR] = i; |
|
51c1efd…
|
stephan
|
522 |
} |
|
51c1efd…
|
stephan
|
523 |
this.pos.endLhs -= lines.length; |
|
51c1efd…
|
stephan
|
524 |
this.pos.next.startRhs -= lines.length; |
|
51c1efd…
|
stephan
|
525 |
this.pos.next.startLhs -= lines.length; |
|
51c1efd…
|
stephan
|
526 |
const selector = '.difflnr > pre'; |
|
51c1efd…
|
stephan
|
527 |
td = tr.querySelector(selector); |
|
51c1efd…
|
stephan
|
528 |
const lineNoTxt = lineno.join('\n')+'\n'; |
|
51c1efd…
|
stephan
|
529 |
lineno.length = 0; |
|
51c1efd…
|
stephan
|
530 |
td.innerHTML = lineNoTxt + td.innerHTML; |
|
36bec9a…
|
stephan
|
531 |
if(this.pos.endLhs<1 |
|
51c1efd…
|
stephan
|
532 |
|| lines.length < (urlParam.to - urlParam.from)){ |
|
51c1efd…
|
stephan
|
533 |
/* No more data. */ |
|
51c1efd…
|
stephan
|
534 |
this.destroy(); |
|
51c1efd…
|
stephan
|
535 |
}else{ |
|
51c1efd…
|
stephan
|
536 |
this.maybeReplaceButtons(); |
|
51c1efd…
|
stephan
|
537 |
this.updatePosDebug(); |
|
51c1efd…
|
stephan
|
538 |
} |
|
51c1efd…
|
stephan
|
539 |
return this; |
|
51c1efd…
|
stephan
|
540 |
}else{ |
|
51c1efd…
|
stephan
|
541 |
throw new Error("Unexpected 'fetchType' value."); |
|
51c1efd…
|
stephan
|
542 |
} |
|
ba40082…
|
stephan
|
543 |
}, |
|
ba40082…
|
stephan
|
544 |
|
|
ba40082…
|
stephan
|
545 |
/** |
|
ba40082…
|
stephan
|
546 |
Sets this widget's message to the given text. If the message |
|
ba40082…
|
stephan
|
547 |
represents an error, the first argument must be truthy, else it |
|
ba40082…
|
stephan
|
548 |
must be falsy. Returns this object. |
|
ba40082…
|
stephan
|
549 |
*/ |
|
ba40082…
|
stephan
|
550 |
msg: function(isError,txt){ |
|
ba40082…
|
stephan
|
551 |
if(txt){ |
|
ba40082…
|
stephan
|
552 |
if(isError) D.addClass(this.e.msgWidget, 'error'); |
|
ba40082…
|
stephan
|
553 |
else D.removeClass(this.e.msgWidget, 'error'); |
|
ba40082…
|
stephan
|
554 |
D.append( |
|
ba40082…
|
stephan
|
555 |
D.removeClass(D.clearElement(this.e.msgWidget), 'hidden'), |
|
ba40082…
|
stephan
|
556 |
txt); |
|
ba40082…
|
stephan
|
557 |
}else{ |
|
ba40082…
|
stephan
|
558 |
D.addClass(D.clearElement(this.e.msgWidget), 'hidden'); |
|
ba40082…
|
stephan
|
559 |
} |
|
ba40082…
|
stephan
|
560 |
return this; |
|
51c1efd…
|
stephan
|
561 |
}, |
|
51c1efd…
|
stephan
|
562 |
|
|
51c1efd…
|
stephan
|
563 |
/** |
|
51c1efd…
|
stephan
|
564 |
Fetches and inserts a line chunk. fetchType is: |
|
51c1efd…
|
stephan
|
565 |
|
|
51c1efd…
|
stephan
|
566 |
this.FetchType.NextUp = upwards from next chunk (this.pos.next) |
|
51c1efd…
|
stephan
|
567 |
|
|
51c1efd…
|
stephan
|
568 |
this.FetchType.FillGap = the whole gap between this.pos.prev |
|
51c1efd…
|
stephan
|
569 |
and this.pos.next, or the whole gap before/after the |
|
51c1efd…
|
stephan
|
570 |
initial/final chunk in the diff. |
|
51c1efd…
|
stephan
|
571 |
|
|
51c1efd…
|
stephan
|
572 |
this.FetchType.PrevDown = downwards from the previous chunk |
|
51c1efd…
|
stephan
|
573 |
(this.pos.prev) |
|
51c1efd…
|
stephan
|
574 |
|
|
51c1efd…
|
stephan
|
575 |
Those values are set at the time this object is initialized but |
|
51c1efd…
|
stephan
|
576 |
one instance of this class may have 2 buttons, one each for |
|
51c1efd…
|
stephan
|
577 |
fetchTypes NextUp and PrevDown. |
|
51c1efd…
|
stephan
|
578 |
|
|
51c1efd…
|
stephan
|
579 |
This is an async operation. While it is in transit, any calls |
|
51c1efd…
|
stephan
|
580 |
to this function will have no effect except (possibly) to emit |
|
51c1efd…
|
stephan
|
581 |
a warning. Returns this object. |
|
51c1efd…
|
stephan
|
582 |
*/ |
|
51c1efd…
|
stephan
|
583 |
fetchChunk: function(fetchType){ |
|
a49393a…
|
stephan
|
584 |
if( !this.$fetchQueue ) return this; // HACKHACK: are we destroyed? |
|
a49393a…
|
stephan
|
585 |
if( fetchType==this.FetchType.ProcessQueue ){ |
|
36bec9a…
|
stephan
|
586 |
this.$fetchQueue.shift(); |
|
a49393a…
|
stephan
|
587 |
if( this.$fetchQueue.length==0 ) return this; |
|
a49393a…
|
stephan
|
588 |
//console.log('fetchChunk: processing queue ...'); |
|
a49393a…
|
stephan
|
589 |
} |
|
a49393a…
|
stephan
|
590 |
else{ |
|
a49393a…
|
stephan
|
591 |
this.$fetchQueue.push(fetchType); |
|
a49393a…
|
stephan
|
592 |
if( this.$fetchQueue.length!=1 ) return this; |
|
a49393a…
|
stephan
|
593 |
//console.log('fetchChunk: processing user input ...'); |
|
a49393a…
|
stephan
|
594 |
} |
|
a49393a…
|
stephan
|
595 |
fetchType = this.$fetchQueue[0]; |
|
36bec9a…
|
stephan
|
596 |
if( fetchType==this.FetchType.ProcessQueue ){ |
|
36bec9a…
|
stephan
|
597 |
/* Unexpected! Clear queue so recovery (manual restart) is possible. */ |
|
36bec9a…
|
stephan
|
598 |
this.$fetchQueue.length = 0; |
|
36bec9a…
|
stephan
|
599 |
return this; |
|
36bec9a…
|
stephan
|
600 |
} |
|
51c1efd…
|
stephan
|
601 |
/* Forewarning, this is a bit confusing: when fetching the |
|
51c1efd…
|
stephan
|
602 |
previous lines, we're doing so on behalf of the *next* diff |
|
51c1efd…
|
stephan
|
603 |
chunk (this.pos.next), and vice versa. */ |
|
51c1efd…
|
stephan
|
604 |
if(fetchType===this.FetchType.NextUp && !this.pos.next |
|
51c1efd…
|
stephan
|
605 |
|| fetchType===this.FetchType.PrevDown && !this.pos.prev){ |
|
51c1efd…
|
stephan
|
606 |
console.error("Attempt to fetch diff lines but don't have any."); |
|
51c1efd…
|
stephan
|
607 |
return this; |
|
51c1efd…
|
stephan
|
608 |
} |
|
ba40082…
|
stephan
|
609 |
this.msg(false,"Fetching diff chunk..."); |
|
a49393a…
|
stephan
|
610 |
const self = this; |
|
51c1efd…
|
stephan
|
611 |
const fOpt = { |
|
51c1efd…
|
stephan
|
612 |
urlParams:{ |
|
51c1efd…
|
stephan
|
613 |
name: this.fileHash, from: 0, to: 0 |
|
51c1efd…
|
stephan
|
614 |
}, |
|
a49393a…
|
stephan
|
615 |
aftersend: ()=>this.msg(false), |
|
a49393a…
|
stephan
|
616 |
onload: function(list){ |
|
a49393a…
|
stephan
|
617 |
self.injectResponse(fetchType,up,list); |
|
a49393a…
|
stephan
|
618 |
if( !self.$fetchQueue || self.$fetchQueue.length==0 ) return; |
|
36bec9a…
|
stephan
|
619 |
/* Keep queue length > 0, or clicks stalled during (unusually lengthy) |
|
36bec9a…
|
stephan
|
620 |
injectResponse() may sneak in as soon as setTimeout() allows, find |
|
36bec9a…
|
stephan
|
621 |
an empty queue, and therefore start over with queue processing. */ |
|
36bec9a…
|
stephan
|
622 |
self.$fetchQueue[0] = self.FetchType.ProcessQueue; |
|
a49393a…
|
stephan
|
623 |
setTimeout(self.fetchChunk.bind(self,self.FetchType.ProcessQueue)); |
|
a49393a…
|
stephan
|
624 |
} |
|
51c1efd…
|
stephan
|
625 |
}; |
|
51c1efd…
|
stephan
|
626 |
const up = fOpt.urlParams; |
|
51c1efd…
|
stephan
|
627 |
if(fetchType===this.FetchType.FillGap){ |
|
51c1efd…
|
stephan
|
628 |
/* Easiest case: filling a whole gap. */ |
|
51c1efd…
|
stephan
|
629 |
up.from = this.pos.startLhs; |
|
51c1efd…
|
stephan
|
630 |
up.to = this.pos.endLhs; |
|
51c1efd…
|
stephan
|
631 |
}else if(this.FetchType.PrevDown===fetchType){ |
|
51c1efd…
|
stephan
|
632 |
/* Append to previous TR. */ |
|
51c1efd…
|
stephan
|
633 |
if(!this.pos.prev){ |
|
51c1efd…
|
stephan
|
634 |
console.error("Attempt to fetch next diff lines but don't have any."); |
|
51c1efd…
|
stephan
|
635 |
return this; |
|
51c1efd…
|
stephan
|
636 |
} |
|
51c1efd…
|
stephan
|
637 |
up.from = this.pos.prev.endLhs + 1; |
|
51c1efd…
|
stephan
|
638 |
up.to = up.from + |
|
51c1efd…
|
stephan
|
639 |
Diff.config.chunkLoadLines - 1/*b/c request range is inclusive*/; |
|
51c1efd…
|
stephan
|
640 |
if( this.pos.next && this.pos.next.startLhs <= up.to ){ |
|
51c1efd…
|
stephan
|
641 |
up.to = this.pos.next.startLhs - 1; |
|
51c1efd…
|
stephan
|
642 |
fetchType = this.FetchType.FillGap; |
|
51c1efd…
|
stephan
|
643 |
} |
|
51c1efd…
|
stephan
|
644 |
}else{ |
|
51c1efd…
|
stephan
|
645 |
/* Prepend to next TR */ |
|
51c1efd…
|
stephan
|
646 |
if(!this.pos.next){ |
|
51c1efd…
|
stephan
|
647 |
console.error("Attempt to fetch previous diff lines but don't have any."); |
|
51c1efd…
|
stephan
|
648 |
return this; |
|
51c1efd…
|
stephan
|
649 |
} |
|
51c1efd…
|
stephan
|
650 |
up.to = this.pos.next.startLhs - 1; |
|
51c1efd…
|
stephan
|
651 |
up.from = Math.max(1, up.to - Diff.config.chunkLoadLines + 1); |
|
51c1efd…
|
stephan
|
652 |
if( this.pos.prev && this.pos.prev.endLhs >= up.from ){ |
|
51c1efd…
|
stephan
|
653 |
up.from = this.pos.prev.endLhs + 1; |
|
51c1efd…
|
stephan
|
654 |
fetchType = this.FetchType.FillGap; |
|
51c1efd…
|
stephan
|
655 |
} |
|
51c1efd…
|
stephan
|
656 |
} |
|
51c1efd…
|
stephan
|
657 |
//console.debug("fetchChunk(",fetchType,")",up); |
|
a49393a…
|
stephan
|
658 |
fOpt.onerror = function(err){ |
|
49e3bf7…
|
stephan
|
659 |
if(self.e/*guard against a late-stage onerror() call*/){ |
|
49e3bf7…
|
stephan
|
660 |
self.msg(true,err.message); |
|
49e3bf7…
|
stephan
|
661 |
self.$fetchQueue.length = 0; |
|
49e3bf7…
|
stephan
|
662 |
}else{ |
|
a754761…
|
stephan
|
663 |
Diff.config.chunkFetch.onerror.call(this,err); |
|
49e3bf7…
|
stephan
|
664 |
} |
|
a49393a…
|
stephan
|
665 |
}; |
|
51c1efd…
|
stephan
|
666 |
Diff.fetchArtifactChunk(fOpt); |
|
51c1efd…
|
stephan
|
667 |
return this; |
|
51c1efd…
|
stephan
|
668 |
} |
|
51c1efd…
|
stephan
|
669 |
}; |
|
51c1efd…
|
stephan
|
670 |
|
|
0cbfc02…
|
stephan
|
671 |
/** |
|
0cbfc02…
|
stephan
|
672 |
Adds context-loading buttons to one or more tables. The argument |
|
0cbfc02…
|
stephan
|
673 |
may be a forEach-capable list of diff table elements, a query |
|
0cbfc02…
|
stephan
|
674 |
selector string matching 0 or more diff tables, or falsy, in |
|
0cbfc02…
|
stephan
|
675 |
which case all relevant diff tables are set up. It tags each |
|
0cbfc02…
|
stephan
|
676 |
table it processes to that it will not be processed multiple |
|
0cbfc02…
|
stephan
|
677 |
times by subsequent calls to this function. |
|
0cbfc02…
|
stephan
|
678 |
|
|
0cbfc02…
|
stephan
|
679 |
Note that this only works for diffs which have been marked up |
|
0cbfc02…
|
stephan
|
680 |
with certain state, namely table.dataset.lefthash and TR |
|
0cbfc02…
|
stephan
|
681 |
entries which hold state related to browsing context. |
|
0cbfc02…
|
stephan
|
682 |
*/ |
|
0cbfc02…
|
stephan
|
683 |
Diff.setupDiffContextLoad = function(tables){ |
|
0cbfc02…
|
stephan
|
684 |
if('string'===typeof tables){ |
|
0cbfc02…
|
stephan
|
685 |
tables = document.querySelectorAll(tables); |
|
0cbfc02…
|
stephan
|
686 |
}else if(!tables){ |
|
0cbfc02…
|
stephan
|
687 |
tables = document.querySelectorAll('table.diff[data-lefthash]:not(.diffskipped)'); |
|
0cbfc02…
|
stephan
|
688 |
} |
|
51c1efd…
|
stephan
|
689 |
/* Potential performance-related TODO: instead of installing all |
|
51c1efd…
|
stephan
|
690 |
of these at once, install them as the corresponding TR is |
|
51c1efd…
|
stephan
|
691 |
scrolled into view. */ |
|
51c1efd…
|
stephan
|
692 |
tables.forEach(function(table){ |
|
63eb9d3…
|
stephan
|
693 |
if(table.classList.contains('diffskipped') || !table.dataset.lefthash) return; |
|
51c1efd…
|
stephan
|
694 |
D.addClass(table, 'diffskipped'/*avoid processing these more than once */); |
|
51c1efd…
|
stephan
|
695 |
table.querySelectorAll('tr.diffskip[data-startln]').forEach(function(tr){ |
|
51c1efd…
|
stephan
|
696 |
new ChunkLoadControls(D.addClass(tr, 'jchunk')); |
|
51c1efd…
|
stephan
|
697 |
}); |
|
51c1efd…
|
stephan
|
698 |
}); |
|
51c1efd…
|
stephan
|
699 |
return F; |
|
51c1efd…
|
stephan
|
700 |
}; |
|
0cbfc02…
|
stephan
|
701 |
Diff.setupDiffContextLoad(); |
|
51c1efd…
|
stephan
|
702 |
}); |
|
c03ce0f…
|
stephan
|
703 |
/* |
|
c03ce0f…
|
stephan
|
704 |
** For a side-by-side diff, ensure that horizontal scrolling of either |
|
c03ce0f…
|
stephan
|
705 |
** side of the diff is synchronized with the other side. |
|
c03ce0f…
|
stephan
|
706 |
*/ |
|
c03ce0f…
|
stephan
|
707 |
window.fossil.onPageLoad(function(){ |
|
c03ce0f…
|
stephan
|
708 |
const F = window.fossil, D = F.dom, Diff = F.diff; |
|
c03ce0f…
|
stephan
|
709 |
|
|
c03ce0f…
|
stephan
|
710 |
/* Look for a parent element to hold the sbs-sync-scroll toggle |
|
c03ce0f…
|
stephan
|
711 |
checkbox. This differs per page. If we don't find one, simply |
|
c03ce0f…
|
stephan
|
712 |
elide that toggle and use whatever preference the user last |
|
c03ce0f…
|
stephan
|
713 |
specified (defaulting to on). */ |
|
c03ce0f…
|
stephan
|
714 |
let cbSync /* scroll-sync checkbox */; |
|
a718a76…
|
stephan
|
715 |
let eToggleParent = /* element to put the sync-scroll checkbox in */ |
|
a718a76…
|
stephan
|
716 |
document.querySelector('table.diff.splitdiff') |
|
a718a76…
|
stephan
|
717 |
? window.fossil.page.diffControlContainer |
|
a718a76…
|
stephan
|
718 |
: undefined; |
|
a718a76…
|
stephan
|
719 |
const keySbsScroll = 'sync-diff-scroll' /* F.storage key for persistent user preference */; |
|
c03ce0f…
|
stephan
|
720 |
if( eToggleParent ){ |
|
c03ce0f…
|
stephan
|
721 |
/* Add a checkbox to toggle sbs scroll sync. Remember that in |
|
c03ce0f…
|
stephan
|
722 |
order to be UI-consistent in the /vdiff page we have to ensure |
|
d83638e…
|
danield
|
723 |
that the checkbox is to the LEFT of its label. We store the |
|
c03ce0f…
|
stephan
|
724 |
sync-scroll preference in F.storage (not a cookie) so that it |
|
c03ce0f…
|
stephan
|
725 |
persists across page loads and different apps. */ |
|
c03ce0f…
|
stephan
|
726 |
cbSync = D.checkbox(keySbsScroll, F.storage.getBool(keySbsScroll,true)); |
|
c03ce0f…
|
stephan
|
727 |
D.append(eToggleParent, D.append( |
|
c03ce0f…
|
stephan
|
728 |
D.addClass(D.create('span'), 'input-with-label'), |
|
c03ce0f…
|
stephan
|
729 |
D.append(D.create('label'), |
|
4d2d62d…
|
drh
|
730 |
cbSync, "Scroll Sync") |
|
c03ce0f…
|
stephan
|
731 |
)); |
|
c03ce0f…
|
stephan
|
732 |
cbSync.addEventListener('change', function(e){ |
|
c03ce0f…
|
stephan
|
733 |
F.storage.set(keySbsScroll, e.target.checked); |
|
c03ce0f…
|
stephan
|
734 |
}); |
|
c03ce0f…
|
stephan
|
735 |
} |
|
c03ce0f…
|
stephan
|
736 |
const useSync = cbSync ? ()=>cbSync.checked : ()=>F.storage.getBool(keySbsScroll,true); |
|
c03ce0f…
|
stephan
|
737 |
|
|
c03ce0f…
|
stephan
|
738 |
/* Now set up the events to enable syncronized scrolling... */ |
|
c03ce0f…
|
stephan
|
739 |
const scrollLeft = function(event){ |
|
c03ce0f…
|
stephan
|
740 |
const table = this.parentElement/*TD*/.parentElement/*TR*/. |
|
c03ce0f…
|
stephan
|
741 |
parentElement/*TBODY*/.parentElement/*TABLE*/; |
|
c03ce0f…
|
stephan
|
742 |
if( useSync() ){ |
|
c03ce0f…
|
stephan
|
743 |
table.$txtPres.forEach((e)=>(e===this) ? 1 : (e.scrollLeft = this.scrollLeft)); |
|
c03ce0f…
|
stephan
|
744 |
} |
|
c03ce0f…
|
stephan
|
745 |
return false; |
|
c03ce0f…
|
stephan
|
746 |
}; |
|
c03ce0f…
|
stephan
|
747 |
const SCROLL_LEN = 64/* pixels to scroll for keyboard events */; |
|
c03ce0f…
|
stephan
|
748 |
const keycodes = Object.assign(Object.create(null),{ |
|
c03ce0f…
|
stephan
|
749 |
37/*cursor left*/: -SCROLL_LEN, 39/*cursor right*/: SCROLL_LEN |
|
c03ce0f…
|
stephan
|
750 |
}); |
|
ddeba72…
|
stephan
|
751 |
/** keydown handler for a diff element */ |
|
ddeba72…
|
stephan
|
752 |
const handleDiffKeydown = function(e){ |
|
ddeba72…
|
stephan
|
753 |
const len = keycodes[e.keyCode]; |
|
ddeba72…
|
stephan
|
754 |
if( !len ) return false; |
|
ddeba72…
|
stephan
|
755 |
if( useSync() ){ |
|
ddeba72…
|
stephan
|
756 |
this.$txtPres[0].scrollLeft += len; |
|
ddeba72…
|
stephan
|
757 |
}else if( this.$preCurrent ){ |
|
ddeba72…
|
stephan
|
758 |
this.$preCurrent.scrollLeft += len; |
|
ddeba72…
|
stephan
|
759 |
} |
|
ddeba72…
|
stephan
|
760 |
return false; |
|
ddeba72…
|
stephan
|
761 |
}; |
|
c03ce0f…
|
stephan
|
762 |
/** |
|
c03ce0f…
|
stephan
|
763 |
Sets up synchronized scrolling of table.splitdiff element |
|
c03ce0f…
|
stephan
|
764 |
`diff`. If passed no argument, it scans the dom for elements to |
|
c03ce0f…
|
stephan
|
765 |
initialize. The second argument is for this function's own |
|
c03ce0f…
|
stephan
|
766 |
internal use. |
|
c03ce0f…
|
stephan
|
767 |
|
|
c03ce0f…
|
stephan
|
768 |
It's okay (but wasteful) to pass the same element to this |
|
c03ce0f…
|
stephan
|
769 |
function multiple times: it will only be set up for sync |
|
c03ce0f…
|
stephan
|
770 |
scrolling the first time it's passed to this function. |
|
c03ce0f…
|
stephan
|
771 |
|
|
c03ce0f…
|
stephan
|
772 |
Note that this setup is ignorant of the cbSync toggle: the toggle |
|
c03ce0f…
|
stephan
|
773 |
is checked when scrolling, not when initializing the sync-scroll |
|
c03ce0f…
|
stephan
|
774 |
capability. |
|
c03ce0f…
|
stephan
|
775 |
*/ |
|
c03ce0f…
|
stephan
|
776 |
const initTableDiff = function f(diff, unifiedDiffs){ |
|
c03ce0f…
|
stephan
|
777 |
if(!diff){ |
|
c03ce0f…
|
stephan
|
778 |
let i, diffs; |
|
c03ce0f…
|
stephan
|
779 |
diffs = document.querySelectorAll('table.splitdiff'); |
|
c03ce0f…
|
stephan
|
780 |
for(i=0; i<diffs.length; ++i){ |
|
c03ce0f…
|
stephan
|
781 |
f.call(this, diffs[i], false); |
|
c03ce0f…
|
stephan
|
782 |
} |
|
c03ce0f…
|
stephan
|
783 |
diffs = document.querySelectorAll('table.udiff'); |
|
c03ce0f…
|
stephan
|
784 |
for(i=0; i<diffs.length; ++i){ |
|
c03ce0f…
|
stephan
|
785 |
f.call(this, diffs[i], true); |
|
c03ce0f…
|
stephan
|
786 |
} |
|
c03ce0f…
|
stephan
|
787 |
return this; |
|
c03ce0f…
|
stephan
|
788 |
} |
|
c03ce0f…
|
stephan
|
789 |
diff.$txtCols = diff.querySelectorAll('td.difftxt'); |
|
c03ce0f…
|
stephan
|
790 |
diff.$txtPres = diff.querySelectorAll('td.difftxt pre'); |
|
c03ce0f…
|
stephan
|
791 |
if(!unifiedDiffs){ |
|
c03ce0f…
|
stephan
|
792 |
diff.$txtPres.forEach(function(e){ |
|
c03ce0f…
|
stephan
|
793 |
if(!e.classList.contains('scroller')){ |
|
c03ce0f…
|
stephan
|
794 |
D.addClass(e, 'scroller'); |
|
c03ce0f…
|
stephan
|
795 |
e.addEventListener('scroll', scrollLeft, false); |
|
c03ce0f…
|
stephan
|
796 |
} |
|
c03ce0f…
|
stephan
|
797 |
}); |
|
c03ce0f…
|
stephan
|
798 |
diff.tabIndex = 0; |
|
c03ce0f…
|
stephan
|
799 |
if(!diff.classList.contains('scroller')){ |
|
c03ce0f…
|
stephan
|
800 |
/* Keyboard-based scrolling requires special-case handling to |
|
c03ce0f…
|
stephan
|
801 |
ensure that we scroll the proper side of the diff when sync |
|
c03ce0f…
|
stephan
|
802 |
is off. */ |
|
c03ce0f…
|
stephan
|
803 |
D.addClass(diff, 'scroller'); |
|
ddeba72…
|
stephan
|
804 |
diff.addEventListener('keydown', handleDiffKeydown, false); |
|
ddeba72…
|
stephan
|
805 |
const handleLRClick = function(ev){ |
|
ddeba72…
|
stephan
|
806 |
diff.$preCurrent = this /* NOT ev.target, which is probably a child element */; |
|
ddeba72…
|
stephan
|
807 |
}; |
|
ddeba72…
|
stephan
|
808 |
diff.$txtPres.forEach((e)=>e.addEventListener('click', handleLRClick, false)); |
|
c03ce0f…
|
stephan
|
809 |
} |
|
c03ce0f…
|
stephan
|
810 |
} |
|
c03ce0f…
|
stephan
|
811 |
return this; |
|
c03ce0f…
|
stephan
|
812 |
} |
|
c03ce0f…
|
stephan
|
813 |
window.fossil.page.tweakSbsDiffs = function(){ |
|
c03ce0f…
|
stephan
|
814 |
document.querySelectorAll('table.splitdiff').forEach((e)=>initTableDiff(e)); |
|
c03ce0f…
|
stephan
|
815 |
}; |
|
c03ce0f…
|
stephan
|
816 |
initTableDiff(); |
|
c03ce0f…
|
stephan
|
817 |
}, false); |