1/* DokuWiki MoaiEditor Scroll_to.js file 2 Version : 0.5b (2026-05-08) 3 Author : MoaiTools <info@moaitools.org> 4 License : GPL 3 (http://www.gnu.org/licenses/gpl.html) */ 5 6/* This class watches for changes lines in the textarea contents. 7 8 It detects deleted and inserted lines. An edited or replaced line is interpreted 9 as a deletion folowed by an insertion. 10 11 Handling textarea input events and toolbar button clicks might not be sufficient 12 to detect changes. Javascript can change the text without generating any events, 13 so unknown plugins might bypass this detection. 14 15 This is why we also check regularly if the text has changed even if no user input 16 event has been detected. This process is not very cpu intensive even when the page 17 is huge (it takes less than 1 ms on a 400 Kb page, which is larger than the largest 18 english wikipedia page). 19 20 We have a timer class to make these background checks more often if the user has 21 interacted recently and less often otherwise (to minimize CPU usage when there is 22 no interaction). 23*/ 24MoaiEditor.WatchText = class { 25 26 constructor(outer) { 27 28 // Arguments 29 this.outer = outer; 30 31 // Objects 32 this.timer = new MoaiEditor.Timer(); 33 34 // Intervals 35 this.interval = setInterval(this.onInterval.bind(this), 200); 36 } 37 // ──────────────────────────────────── 38 onInterval() { 39 40 // Return if the parent is disabled 41 if (!this.outer.enabled) 42 return; 43 44 // Return if not enough time has elapsed 45 if (!this.timer.due()) 46 return; 47 48 // Return if the editor is not enabled and ready 49 if (!moaiEditor.layoutReady) 50 return; 51 52 // Check for text change 53 this.checkTextChange(); 54 55 // Optional debug actions 56 this.debug(); 57 58 } 59 // ──────────────────────────────────── 60 onInput() { 61 this.timer.resetPeriod(); 62 this.timer.restart(); 63 this.checkTextChange(); 64 } 65 // ──────────────────────────────────── 66 getTextLines() { 67 this.string = moaiEditor.layout.textarea.value; 68 return this.string.split("\n"); 69 } 70 // ──────────────────────────────────── 71 checkTextChange() { 72 // Init 73 var i; 74 const start = Date.now(); 75 var previous = this.lines; 76 var current = this.getTextLines(); 77 78 // Get shift (delta) 79 const shift = current.length - previous.length; 80 81 // Walk lines form top to bottom and detect the first change 82 for (i=0; i<previous.length; i++) { 83 if (current.length <= i) 84 break; 85 if (previous[i] != current[i]) 86 break; 87 } 88 // Return if the text is the same 89 if (i == previous.length && shift == 0) { 90 return null; 91 } 92 // First changed line (top to bottom) 93 const i0 = i; 94 95 // Walk lines form bottom to top and detect the first change 96 for (var k=0; k<previous.length; k++) { 97 i = previous.length - k - 1; 98 var j = current.length - k - 1; 99 if (j < 0) 100 break; 101 if (previous[i] != current[j]) 102 break; 103 } 104 // First changed line (bottom to top) 105 const i1 = i; 106 107 // Calc some values 108 var removed = Math.max(0, i1 - i0 + 1); 109 if (previous.length == 0) { 110 removed = 0; 111 } 112 var inserted = removed + shift; 113 if (inserted < 0) { 114 removed -= inserted; 115 inserted = 0; 116 } 117 // Pack the result 118 const elapsed = Date.now() - start; 119 const data = { num:{keepfirst:i0, remove:removed, insert:inserted}, index:{keeplast: (i0+removed)}, i0:i0, i1:i1, shift:shift, elapsed:elapsed }; 120 121 // Store current lines 122 this.lines = current; 123 124 // Start checking more frequently for changes 125 this.timer.resetPeriod(); 126 127 // Enable "This page is asking you to confirm that you want to leave" popup from: lib/scripts/edit.js 128 window.textChanged = true; 129 130 // Style the Commit and Cancel buttons 131 moaiEditor.buttons.styleExitButtons(); 132 133 // Inform parent object of the changes 134 this.outer.onTextChanged(data); 135 } 136 // ──────────────────────────────────── 137 debug() { 138 // Placeholder 139 } 140}; // End Class 141 142// ▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆ 143// ▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆ 144 145MoaiEditor.Timer = class { 146 147 constructor () { 148 this.period = 200; 149 this.minPeriod = 200; 150 this.maxPeriod = 2000; 151 this.inc = 50; 152 this.last = Date.now(); 153 } 154 restart () { 155 this.last = Date.now(); 156 this.period += this.inc; 157 this.period = Math.min(this.period, this.maxPeriod); 158 } 159 due () { 160 this.debug(); 161 const elapsed = Date.now() - this.last; 162 if (elapsed > this.period) { 163 this.restart(); 164 return true; 165 } 166 return false; 167 } 168 set (period) { 169 this.period = Math.round(period); 170 } 171 setMin (period) { 172 this.minPeriod = Math.round(period); 173 } 174 setMax (period) { 175 this.minPeriod = Math.round(period); 176 } 177 setInc (inc) { 178 this.inc = inc; 179 } 180 resetPeriod () { 181 this.period = this.minPeriod; 182 } 183 debug () { 184 const line = document.querySelector("#moai__debug div:nth-child(1)"); 185 if (!line) return; 186 const elapsed = '<span style="opacity:0.2">'+(Date.now()-this.last).toString().padStart(4," ")+'</span>'; 187 const text = "TIMER: "+elapsed+" period:"+this.period+" min:"+this.minPeriod+" max:"+this.maxPeriod; 188 line.innerHTML = text; 189 line.title = 'Texarea '; 190 } 191}; // End Class 192 193 194