1/** 2 * ReadProgress 3 * Visually indicate remaining length of an article 4 * Input: 5 * - progressElement (dom-element) Element to display reader progress with 6 * - options 7 * - targetSelectors (array) List of selectors. Children of these elements are used to measure progress. 8 * - minElementCount (int) Minimum number of elements required to use this functionality. 9 * - debug (bool) If true the class will print debug-info to the console. If false, only warnings are printed. 10 */ 11 12window.Dac = window.Dac || {}; 13window.Dac.ReadProgress = class { 14 15 constructor(progressElement, options) { 16 this.debug = !!options.debug; 17 this.log('Creating instance ...'); 18 this.progressElement = progressElement; 19 let elements = []; 20 for (const target of (options.targetSelectors || [])) { 21 const targetElement = document.querySelector(target); 22 if (targetElement) { 23 elements = elements.concat(Array.from(targetElement.children).filter((el) => !el.classList.contains('column'))); 24 } else { 25 this.log(`Cannot find element for selector "${ target }"`, true); 26 } 27 } 28 if (!elements.length) { 29 this.log(`No elements found. Cannot measure reading-progress`, true); 30 return; 31 } 32 const minElementCount = parseInt(options.minElementCount || 25, 10); 33 if (elements.length < minElementCount) { 34 this.log(`Number of elements (${ elements.length }) is below required count (${ minElementCount }). Skipping.`); 35 return; 36 } 37 let index = 0; 38 this.items = elements.map(el => ({ 39 isIntersecting: false, 40 index: index++, 41 element: el 42 })); 43 setTimeout(() => { 44 this.observeElements(elements); 45 this.progressElement.classList.remove('dac-hidden'); 46 }, 1000); 47 } 48 49 log(msg, isCritical = false) { 50 if (isCritical) { 51 console.warn('ReadProgress:', msg); 52 return; 53 } 54 if (!this.debug) { return; } 55 console.log('ReadProgress:', msg); 56 } 57 58 getIndex(element) { 59 for (const item of this.items) { 60 if (item.element === element) { 61 return item.index; 62 } 63 } 64 return null; 65 } 66 67 observeElements(items) { 68 this.log(`Will register IntersectionObserver for ${ items.length } elements.`); 69 const options = { 70 // root: root, 71 rootMargin: '0px', 72 threshold: [0.5] 73 // threshold: [0, 0.25, 0.5, 0.75, 1] 74 }; 75 let needUpdate = false; 76 const observer = new IntersectionObserver((entries, theObserver) => { 77 for (const entry of entries) { 78 const index = this.getIndex(entry.target); 79 if (this.items[index].isIntersecting !== entry.isIntersecting) { 80 this.items[index].isIntersecting = entry.isIntersecting; 81 needUpdate = true; 82 } 83 } 84 if (needUpdate) { 85 this.updateGui(); 86 } 87 }, options); 88 for (const item of items) { 89 observer.observe(item); 90 } 91 } 92 93 // Update nav-elements based on visibility of first / last item 94 updateGui() { 95 const itemCount = this.items.length; 96 let firstVisible = -1; 97 let firstInvisible = -1; 98 for (const item of this.items) { 99 const index = this.items.indexOf(item); 100 if (firstVisible === -1 && item.isIntersecting) { 101 firstVisible = index; 102 } 103 if (firstInvisible === -1 && firstVisible > -1 && !item.isIntersecting) { 104 firstInvisible = index; 105 this.updateGuiByRange(firstVisible, firstInvisible, itemCount); 106 return; 107 } 108 } 109 } 110 111 updateGuiByRange(start, end, count) { 112 const endIndex = (end > -1 ? end : count) + 1; 113 const percent = Number((endIndex / count).toFixed(2)); 114 this.log(`Found progress-value of ${ percent }. Start-index: ${ start }, end-index: ${ endIndex }. Will update GUI.`); 115 this.progressElement.style.transform = (`scaleX(${ percent })`); 116 } 117 118}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.