1/* DokuWiki MoaiEditor Scroll_sync.js file 2 Author : MoaiTools <info@moaitools.org> 3 License : GPL 3 (http://www.gnu.org/licenses/gpl.html) */ 4 5/* Scrolls the preview (keeping it synchronized with the editor). 6 7*/ 8MoaiEditor.ScrollSync = class { 9 10 constructor(outer) { 11 12 // Variables 13 this.disabled = false; // synchronization can be disabled temporarily when other processes want to take control 14 } 15 16 init() { 17 this.scroller = new MoaiEditor.Scroller(this); 18 } 19 20 get isEnabled() { 21 if (this.disabled || moaiEditor.buttons.autoscroll.mode == 'off' || !moaiEditor.layoutReady) 22 return false; 23 return true; 24 } 25 26 // This function is called whenever the user scrolls the editor side 27 onScroll(event) { 28 29 // Exit if disabled by the user, by other process, or if the editor is not ready 30 if (!this.isEnabled) 31 return; 32 33 // Exit if the editor scroll has not changed (to avoid interfering right after another process has done scrolling) 34 const scroll = moaiEditor.editor.current.scroll.top; 35 const sameScroll = Math.abs(scroll - this.lastScroll) <= 2; 36 if (sameScroll) 37 return; 38 39 // Synchronize the scroll of the preview 40 const calc = this.calcScroll(); 41 if (calc !== null) { 42 this.scroller.target = calc.right; 43 this.scroller.engaged = true; 44 } 45 // Remember last scroll position 46 this.lastScroll = scroll; 47 } 48 49 calcScroll() { 50 51 // There should be at least one match 52 var matches = moaiEditor.matches.matches; 53 if (matches.length == 0) 54 return null; 55 56 // Get the editor side scroll position 57 const leftScroll = moaiEditor.editor.current.scroll.top; 58 59 // Get the preview container top position relative to the screen 60 this.container = moaiEditor.preview.container; 61 this.containerTop = this.container.getBoundingClientRect().top; 62 63 // Get the scroll points above and below our current scroll position 64 var p1, p2, match; 65 for (var i = 0; i < matches.length; i++) { 66 if (matches[i].scroll === null) 67 continue; 68 match = matches[i]; 69 if (match.scroll.top > leftScroll) { 70 p1 = this.getPoint(i-1, 'bottom'); 71 p2 = this.getPoint(i, 'top'); 72 break; 73 } 74 if (match.scroll.bottom > leftScroll) { 75 p1 = this.getPoint(i, 'top'); 76 p2 = this.getPoint(i, 'bottom'); 77 break; 78 } 79 } 80 // Exit if no scroll points where found 81 if (match === undefined) { 82 return null; 83 } 84 85 86 // Build bottom scroll point if needed 87 if (p1 === undefined) { 88 p1 = this.getPoint(i-1, 'bottom'); 89 p2 = this.getPoint(i, 'top'); 90 } 91 92 // Get the normalized scroll progress between both scrollpoints on the editor side 93 const normalized = (leftScroll - p1.left) / (p2.left - p1.left); 94 95 // Compute the corresponding scroll on the preview side 96 const right = p1.right + normalized * (p2.right - p1.right); 97 98 // Debug line 99 const scroll = {p1:p1, p2:p2, right:right}; 100 this.debugLine(scroll); 101 102 // Return 103 return scroll; 104 } 105 106 getPoint(i, side) { 107 var type, left, right; 108 var linenum = side.toUpperCase(); 109 // Build a TOP scrollpoint if needed 110 if (i < 0) { 111 type = 'TOP'; 112 left = 0; 113 right = 0; 114 // Build a BOTTOM scrollpoint if needed 115 } else if (i >= moaiEditor.matches.matches.length) { 116 type = 'BOTTOM'; 117 left = moaiEditor.editor.current.scroll.max; 118 right = this.container.scrollHeight; 119 // Get the scrollpoint from the match 120 } else { 121 const match = moaiEditor.matches.matches[i]; 122 type = match.type, 123 left = match.scroll[side], 124 right = match.handle.getBoundingClientRect()[side] - this.containerTop + this.container.scrollTop; 125 linenum = match.startline; 126 if (side == 'bottom') 127 linenum = match.endline; 128 } 129 // Pack and return 130 return {linenum:linenum, type:type, left:left, right:right, side:side}; 131 } 132 133 debugLine(scroll) { 134 // Exit if debugline is not enabled 135 const line = document.querySelector("#moai__debug div:nth-child(2)"); 136 if (!line) return; 137 138 // Debug line 139 const p1 = scroll.p1; 140 const p2 = scroll.p2; 141 const txt1 = "<"+scroll.p1.type+"> "+p1.side+" L"+p1.linenum; 142 const txt2 = "<"+scroll.p2.type+"> "+p2.side+" L"+p2.linenum; 143 const text = txt1+" ⇔ "+txt2; 144 line.textContent = text; 145 } 146}; // End Class 147 148// ▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆ 149// ▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆ 150 151MoaiEditor.Scroller = class { 152 153 constructor(outer) { 154 this.outer = outer; 155 this.i = 0; 156 this.target = null; 157 this._engaged = false; 158 this.onFrame(); 159 } 160 get engaged() { 161 return this._engaged; 162 } 163 set engaged(boolean) { 164 this._engaged = boolean; 165 this.lastScroll = moaiEditor.preview.scroll.top; 166 } 167 onFrame() { 168 requestAnimationFrame(this.onFrame.bind(this)); 169 //this.i += 1; if (this.i % 3 !== 0) return; // Test other framerates 170 const dt = Date.now() - this.lastTime; 171 this.lastTime = Date.now(); 172 if (!this.engaged || !this.outer.isEnabled || this.target === null) 173 return; 174 // Disengage if the user scrolled the preview 175 const scroll = moaiEditor.preview.scroll.top; 176 const userScrolled = Math.abs(scroll - this.lastScroll) > 2; 177 if (userScrolled) { 178 this.engaged = false; 179 return; 180 } 181 // Scroll the preview 182 const distance = this.target - scroll; 183 const increment = 1*distance*dt/160; 184 moaiEditor.preview.scroll.top = scroll + increment; 185 this.lastScroll = scroll + increment; 186 } 187}; // End Class 188 189 190