changeset 53: |
d25f982fb8a6 |
author: |
Richard Westhaver <ellis@rwest.io> |
date: |
Sat, 20 Jul 2024 22:31:54 -0400 |
permissions: |
-rwxr-xr-x |
description: |
init vc |
1 // followlines.js - JavaScript utilities for followlines UI 3 // Copyright 2017 Logilab SA <contact@logilab.fr> 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. 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') { 14 // URL to complement with "linerange" query parameter 15 var targetUri = sourcelines.dataset.logurl; 16 if (typeof targetUri === 'undefined') { 20 // Tag of children of "sourcelines" element on which to add "line 22 var selectableTag = sourcelines.dataset.selectabletag; 23 if (typeof selectableTag === 'undefined') { 27 var isHead = parseInt(sourcelines.dataset.ishead || "0"); 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; 37 // retrieve all direct *selectable* children of class="sourcelines" 39 var selectableElements = Array.prototype.filter.call( 41 function(x) { return x.tagName === selectableTag; }); 43 var btnTitleStart = 'start following lines history from here'; 44 var btnTitleEnd = 'terminate line block selection here'; 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'); 57 var minusSpan = document.createElement('span'); 58 minusSpan.classList.add('followlines-minus'); 59 minusSpan.textContent = '−'; 60 btn.appendChild(minusSpan); 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 73 var btnSupportElm = selectableElements[i]; 74 var childSupportElms = btnSupportElm.getElementsByClassName( 75 'followlines-btn-parent'); 76 if ( childSupportElms.length > 0 ) { 77 btnSupportElm = childSupportElms[0]; 79 var refNode = btnSupportElm.childNodes[0]; // node to insert <button> before 80 btnSupportElm.insertBefore(btn, refNode); 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'); 93 var lineSelectedCSSClass = 'followlines-selected'; 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); 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); 111 // ** return the element of type "selectableTag" parent of `element` */ 112 function selectableParent(element) { 113 var parent = element.parentElement; 114 if (parent === null) { 117 if (element.tagName === selectableTag && parent.isSameNode(sourcelines)) { 120 return selectableParent(parent); 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'); 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'); 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); 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); 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 159 // update button tooltip text and CSS 160 updateButtons(startElement); 162 var startId = parseInt(startElement.id.slice(1)); 163 startElement.classList.add(lineSelectedCSSClass); // CSS 165 // remove this event listener 166 buttonsRemoveEventListener(lineSelectStart); 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 177 // remove this event listener 178 buttonsRemoveEventListener(lineSelectEnd); 180 // reset button tooltip text 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); 192 var inviteElement = endElement; 193 if (endId < startId) { 197 inviteElement = startElement; 200 addSelectedCSSClass(startId - 1, endId -1); // CSS 202 // append the <div id="followlines"> element to last line of the 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); 211 for (var i = 0; i < followlinesButtons.length; i++) { 212 followlinesButtons[i].classList.add('btn-followlines-hidden'); 215 //** event handler for cancelling selection */ 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'); 225 // remove styles on selected lines 226 removeSelectedCSSClass(); 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); 236 buttonsAddEventListener(lineSelectEnd); 240 buttonsAddEventListener(lineSelectStart); 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'; 248 // <div class="followlines-cancel"> 249 var buttonDiv = document.createElement('div'); 250 buttonDiv.classList.add('followlines-cancel'); 252 // <button>x</button> 253 var button = document.createElement('button'); 254 button.textContent = 'x'; 255 buttonDiv.appendChild(button); 256 div.appendChild(buttonDiv); 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); 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); 281 div.appendChild(aDiv); 283 return [div, button];