Mercurial Hosting > hghosting
comparison templates/static/followlines.js @ 0:dfc36e7ed22c
init
author | Vadim Filimonov <fffilimonov@yandex.ru> |
---|---|
date | Thu, 12 May 2022 13:51:59 +0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:dfc36e7ed22c |
---|---|
1 // followlines.js - JavaScript utilities for followlines UI | |
2 // | |
3 // Copyright 2017 Logilab SA <contact@logilab.fr> | |
4 // | |
5 // This software may be used and distributed according to the terms of the | |
6 // GNU General Public License version 2 or any later version. | |
7 | |
8 //** Install event listeners for line block selection and followlines action */ | |
9 document.addEventListener('DOMContentLoaded', function() { | |
10 var sourcelines = document.getElementsByClassName('sourcelines')[0]; | |
11 if (typeof sourcelines === 'undefined') { | |
12 return; | |
13 } | |
14 // URL to complement with "linerange" query parameter | |
15 var targetUri = sourcelines.dataset.logurl; | |
16 if (typeof targetUri === 'undefined') { | |
17 return; | |
18 } | |
19 | |
20 // Tag of children of "sourcelines" element on which to add "line | |
21 // selection" style. | |
22 var selectableTag = sourcelines.dataset.selectabletag; | |
23 if (typeof selectableTag === 'undefined') { | |
24 return; | |
25 } | |
26 | |
27 var isHead = parseInt(sourcelines.dataset.ishead || "0"); | |
28 | |
29 //* position "element" on top-right of cursor */ | |
30 function positionTopRight(element, event) { | |
31 var x = (event.clientX + 10) + 'px', | |
32 y = (event.clientY - 20) + 'px'; | |
33 element.style.top = y; | |
34 element.style.left = x; | |
35 } | |
36 | |
37 // retrieve all direct *selectable* children of class="sourcelines" | |
38 // element | |
39 var selectableElements = Array.prototype.filter.call( | |
40 sourcelines.children, | |
41 function(x) { return x.tagName === selectableTag; }); | |
42 | |
43 var btnTitleStart = 'start following lines history from here'; | |
44 var btnTitleEnd = 'terminate line block selection here'; | |
45 | |
46 //** return a <button> element with +/- spans */ | |
47 function createButton() { | |
48 var btn = document.createElement('button'); | |
49 btn.title = btnTitleStart; | |
50 btn.classList.add('btn-followlines'); | |
51 var plusSpan = document.createElement('span'); | |
52 plusSpan.classList.add('followlines-plus'); | |
53 plusSpan.textContent = '+'; | |
54 btn.appendChild(plusSpan); | |
55 var br = document.createElement('br'); | |
56 btn.appendChild(br); | |
57 var minusSpan = document.createElement('span'); | |
58 minusSpan.classList.add('followlines-minus'); | |
59 minusSpan.textContent = '−'; | |
60 btn.appendChild(minusSpan); | |
61 return btn; | |
62 } | |
63 | |
64 // extend DOM with CSS class for selection highlight and action buttons | |
65 var followlinesButtons = []; | |
66 for (var i = 0; i < selectableElements.length; i++) { | |
67 selectableElements[i].classList.add('followlines-select'); | |
68 var btn = createButton(); | |
69 followlinesButtons.push(btn); | |
70 // insert the <button> as child of `selectableElements[i]` unless the | |
71 // latter has itself a child with a "followlines-btn-parent" class | |
72 // (annotate view) | |
73 var btnSupportElm = selectableElements[i]; | |
74 var childSupportElms = btnSupportElm.getElementsByClassName( | |
75 'followlines-btn-parent'); | |
76 if ( childSupportElms.length > 0 ) { | |
77 btnSupportElm = childSupportElms[0]; | |
78 } | |
79 var refNode = btnSupportElm.childNodes[0]; // node to insert <button> before | |
80 btnSupportElm.insertBefore(btn, refNode); | |
81 } | |
82 | |
83 // ** re-initialize followlines buttons */ | |
84 function resetButtons() { | |
85 for (var i = 0; i < followlinesButtons.length; i++) { | |
86 var btn = followlinesButtons[i]; | |
87 btn.title = btnTitleStart; | |
88 btn.classList.remove('btn-followlines-end'); | |
89 btn.classList.remove('btn-followlines-hidden'); | |
90 } | |
91 } | |
92 | |
93 var lineSelectedCSSClass = 'followlines-selected'; | |
94 | |
95 //** add CSS class on selectable elements in `from`-`to` line range */ | |
96 function addSelectedCSSClass(from, to) { | |
97 for (var i = from; i <= to; i++) { | |
98 selectableElements[i].classList.add(lineSelectedCSSClass); | |
99 } | |
100 } | |
101 | |
102 //** remove CSS class from previously selected lines */ | |
103 function removeSelectedCSSClass() { | |
104 var elements = sourcelines.getElementsByClassName( | |
105 lineSelectedCSSClass); | |
106 while (elements.length) { | |
107 elements[0].classList.remove(lineSelectedCSSClass); | |
108 } | |
109 } | |
110 | |
111 // ** return the element of type "selectableTag" parent of `element` */ | |
112 function selectableParent(element) { | |
113 var parent = element.parentElement; | |
114 if (parent === null) { | |
115 return null; | |
116 } | |
117 if (element.tagName === selectableTag && parent.isSameNode(sourcelines)) { | |
118 return element; | |
119 } | |
120 return selectableParent(parent); | |
121 } | |
122 | |
123 // ** update buttons title and style upon first click */ | |
124 function updateButtons(selectable) { | |
125 for (var i = 0; i < followlinesButtons.length; i++) { | |
126 var btn = followlinesButtons[i]; | |
127 btn.title = btnTitleEnd; | |
128 btn.classList.add('btn-followlines-end'); | |
129 } | |
130 // on clicked button, change title to "cancel" | |
131 var clicked = selectable.getElementsByClassName('btn-followlines')[0]; | |
132 clicked.title = 'cancel'; | |
133 clicked.classList.remove('btn-followlines-end'); | |
134 } | |
135 | |
136 //** add `listener` on "click" event for all `followlinesButtons` */ | |
137 function buttonsAddEventListener(listener) { | |
138 for (var i = 0; i < followlinesButtons.length; i++) { | |
139 followlinesButtons[i].addEventListener('click', listener); | |
140 } | |
141 } | |
142 | |
143 //** remove `listener` on "click" event for all `followlinesButtons` */ | |
144 function buttonsRemoveEventListener(listener) { | |
145 for (var i = 0; i < followlinesButtons.length; i++) { | |
146 followlinesButtons[i].removeEventListener('click', listener); | |
147 } | |
148 } | |
149 | |
150 //** event handler for "click" on the first line of a block */ | |
151 function lineSelectStart(e) { | |
152 var startElement = selectableParent(e.target.parentElement); | |
153 if (startElement === null) { | |
154 // not a "selectable" element (maybe <a>): abort, keeping event | |
155 // listener registered for other click with a "selectable" target | |
156 return; | |
157 } | |
158 | |
159 // update button tooltip text and CSS | |
160 updateButtons(startElement); | |
161 | |
162 var startId = parseInt(startElement.id.slice(1)); | |
163 startElement.classList.add(lineSelectedCSSClass); // CSS | |
164 | |
165 // remove this event listener | |
166 buttonsRemoveEventListener(lineSelectStart); | |
167 | |
168 //** event handler for "click" on the last line of the block */ | |
169 function lineSelectEnd(e) { | |
170 var endElement = selectableParent(e.target.parentElement); | |
171 if (endElement === null) { | |
172 // not a <span> (maybe <a>): abort, keeping event listener | |
173 // registered for other click with <span> target | |
174 return; | |
175 } | |
176 | |
177 // remove this event listener | |
178 buttonsRemoveEventListener(lineSelectEnd); | |
179 | |
180 // reset button tooltip text | |
181 resetButtons(); | |
182 | |
183 // compute line range (startId, endId) | |
184 var endId = parseInt(endElement.id.slice(1)); | |
185 if (endId === startId) { | |
186 // clicked twice the same line, cancel and reset initial state | |
187 // (CSS, event listener for selection start) | |
188 removeSelectedCSSClass(); | |
189 buttonsAddEventListener(lineSelectStart); | |
190 return; | |
191 } | |
192 var inviteElement = endElement; | |
193 if (endId < startId) { | |
194 var tmp = endId; | |
195 endId = startId; | |
196 startId = tmp; | |
197 inviteElement = startElement; | |
198 } | |
199 | |
200 addSelectedCSSClass(startId - 1, endId -1); // CSS | |
201 | |
202 // append the <div id="followlines"> element to last line of the | |
203 // selection block | |
204 var divAndButton = followlinesBox(targetUri, startId, endId, isHead); | |
205 var div = divAndButton[0], | |
206 button = divAndButton[1]; | |
207 inviteElement.appendChild(div); | |
208 // set position close to cursor (top-right) | |
209 positionTopRight(div, e); | |
210 // hide all buttons | |
211 for (var i = 0; i < followlinesButtons.length; i++) { | |
212 followlinesButtons[i].classList.add('btn-followlines-hidden'); | |
213 } | |
214 | |
215 //** event handler for cancelling selection */ | |
216 function cancel() { | |
217 // remove invite box | |
218 div.parentNode.removeChild(div); | |
219 // restore initial event listeners | |
220 buttonsAddEventListener(lineSelectStart); | |
221 buttonsRemoveEventListener(cancel); | |
222 for (var i = 0; i < followlinesButtons.length; i++) { | |
223 followlinesButtons[i].classList.remove('btn-followlines-hidden'); | |
224 } | |
225 // remove styles on selected lines | |
226 removeSelectedCSSClass(); | |
227 resetButtons(); | |
228 } | |
229 | |
230 // bind cancel event to click on <button> | |
231 button.addEventListener('click', cancel); | |
232 // as well as on an click on any source line | |
233 buttonsAddEventListener(cancel); | |
234 } | |
235 | |
236 buttonsAddEventListener(lineSelectEnd); | |
237 | |
238 } | |
239 | |
240 buttonsAddEventListener(lineSelectStart); | |
241 | |
242 //** return a <div id="followlines"> and inner cancel <button> elements */ | |
243 function followlinesBox(targetUri, fromline, toline, isHead) { | |
244 // <div id="followlines"> | |
245 var div = document.createElement('div'); | |
246 div.id = 'followlines'; | |
247 | |
248 // <div class="followlines-cancel"> | |
249 var buttonDiv = document.createElement('div'); | |
250 buttonDiv.classList.add('followlines-cancel'); | |
251 | |
252 // <button>x</button> | |
253 var button = document.createElement('button'); | |
254 button.textContent = 'x'; | |
255 buttonDiv.appendChild(button); | |
256 div.appendChild(buttonDiv); | |
257 | |
258 // <div class="followlines-link"> | |
259 var aDiv = document.createElement('div'); | |
260 aDiv.classList.add('followlines-link'); | |
261 aDiv.textContent = 'follow history of lines ' + fromline + ':' + toline + ':'; | |
262 var linesep = document.createElement('br'); | |
263 aDiv.appendChild(linesep); | |
264 // link to "ascending" followlines | |
265 var aAsc = document.createElement('a'); | |
266 var url = targetUri + '?patch=&linerange=' + fromline + ':' + toline; | |
267 aAsc.setAttribute('href', url); | |
268 aAsc.textContent = 'older'; | |
269 aDiv.appendChild(aAsc); | |
270 | |
271 if (!isHead) { | |
272 var sep = document.createTextNode(' / '); | |
273 aDiv.appendChild(sep); | |
274 // link to "descending" followlines | |
275 var aDesc = document.createElement('a'); | |
276 aDesc.setAttribute('href', url + '&descend='); | |
277 aDesc.textContent = 'newer'; | |
278 aDiv.appendChild(aDesc); | |
279 } | |
280 | |
281 div.appendChild(aDiv); | |
282 | |
283 return [div, button]; | |
284 } | |
285 | |
286 }, false); |