1/* DokuWiki MoaiEditor Layout_vphone.js file 2 Version : 0.5 (May 5, 2026) 3 Author : MoaiTools <info@moaitools.org> 4 License : GPL 3 (http://www.gnu.org/licenses/gpl.html) */ 5 6/* Vertical phone layout class 7 ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ 8 This class handles the vertical phone layout. 9 10 Note: Check 'style.css' to see the media queries that handle the other part of the responsive behavior. 11 Those look like this: @media (max-width: 600px) 12 13 Layout 14 ‾‾‾‾‾‾ 15 body -- 16 #moaied__wrapper -- semitransparent overlay <-- scrolls horizontally to show left and right side 17 #moaied__editor -- holds both sides 18 #moaied__phone_left -- several options and buttons 19 #moaied__phone_right -- toolbar, editor, preview 20*/ 21MoaiEditor.LayoutPhoneVertical = class { 22 23 constructor(layout) { 24 25 // Arguments 26 this.layout = layout; 27 } 28 // ──────────────────────────────────── 29 deactivate () { 30 // Placeholder 31 } 32 // ──────────────────────────────────── 33 activate () { 34 35 // Handles 36 const editor = this.layout.editor; 37 const elements = this.layout.elements; 38 39 // Bind functions to buttons 40 this.btn_goLeft = moaiEditor.buttons.goLeft; 41 this.btn_goRight = moaiEditor.buttons.goRight; 42 this.btn_goLeft.onClick = this.goLeft; 43 this.btn_goRight.onClick = this.goRight; 44 45 // Create the basic layout 46 this.left = moaiEditor.createHTML('<div id="moaied__phone_left"></div>'); // Buttons and settings 47 this.right = moaiEditor.createHTML('<div id="moaied__phone_right"></div>'); // Both panes (editor and preview) 48 editor.appendChild(this.left); 49 editor.appendChild(this.right); 50 51 // Show right side initially 52 document.getElementById('moaied__wrapper').scrollLeft = window.innerWidth; 53 54 // ──────────────── Left side ──────────────────── 55 56 // Rows 57 this.leftRow1 = moaiEditor.createHTML('<div id="moaied__phone_left_row1" class="moaied-phone-row"><div id="moaied__phone_topleft"></div><div></div></div>'); 58 this.leftRow2 = moaiEditor.createHTML('<div id="moaied__phone_left_row2" class="moaied-phone-row"></div>'); 59 this.left.appendChild(this.leftRow1); 60 this.left.appendChild(this.leftRow2); 61 62 // Row 1 - left side (back button and page id) 63 var left = this.leftRow1.firstChild; 64 left.appendChild(moaiEditor.buttons.back.handle); 65 left.appendChild(this.layout.pageid); 66 67 // Row 1 - right side (go right button) 68 var right = this.leftRow1.lastChild; 69 right.appendChild(this.btn_goRight.handle); 70 71 // Row 2 - container for the rest of the page 72 var main = moaiEditor.createHTML('<div id="moaied__phone_left_main"></div>'); // Container for: table of contents, summary edit, document info, etc 73 var sidebar = moaiEditor.createHTML('<div id="moaied__phone_sidebar"></div>'); 74 this.leftRow2.appendChild(main); 75 this.leftRow2.appendChild(sidebar); 76 77 // Sidebar 78 sidebar.appendChild(this.layout.btn_linewrap); 79 sidebar.appendChild(this.layout.btn_fullscreen); 80 sidebar.appendChild(this.layout.btn_editor); 81 sidebar.appendChild(this.layout.btn_scrolltop); 82 sidebar.appendChild(this.layout.btn_scrollbottom); 83 sidebar.appendChild(this.layout.bottomRight); 84 85 // Editor buttons 86 this.leftButtons = moaiEditor.createHTML('<div id="moaied__phone_left_buttons"></div>'); 87 main.appendChild(this.leftButtons); 88 var names = [ 89 'livepreview', 90 'partialpreview', 91 'autoscroll', 92 'sep', 93 'settings', 94 'enabled', 95 ]; 96 this.layout.addButtons(this.leftButtons, names); 97 98 // Message area 99 main.appendChild(this.layout.msgarea); 100 101 // Table of contents 102 var toc = moaiEditor.toc.container; 103 toc.classList.add('phone'); 104 main.appendChild(toc); 105 106 // Edit summary 107 main.appendChild(this.layout.summary); 108 109 // Separator 110 var sep = moaiEditor.createHTML('<div id="moaied__phone_left_main_separator"></div>'); 111 main.appendChild(sep); 112 113 // Docinfo (will not exist when creating a document) 114 const data = JSINFO.plugin_moaieditor.docinfo; 115 if (data !== null) { 116 117 // Document path on disk 118 const diskpath = moaiEditor.createHTML('<div id="moaied__phone_diskpath"><label>'+data.labelPath+'</label>'+data.path+'</div>'); 119 main.appendChild(diskpath); 120 121 // Last modified 122 const lastmodified = moaiEditor.createHTML('<div id="moaied__phone_lastmodified"><label>'+data.labelMod+'</label>'+data.time+' '+data.by+'</div>'); 123 main.appendChild(lastmodified); 124 } 125 // ──────────────── Right side ──────────────────── 126 127 // Main elements 128 this.rightHeaderContainer = moaiEditor.createHTML('<div id="moaied__phone_right_header_container"></div>'); 129 this.rightHeaderDetachable = moaiEditor.createHTML('<div id="moaied__phone_right_header_detachable"></div>'); // Detachable header needed because of the weird way browsers work in iOS, which requires hacks to have a fixed header or footer in your page 130 this.right.appendChild(this.rightHeaderContainer); 131 this.right.appendChild(this.layout.panes); 132 133 // Header 134 this.rightToolbar = moaiEditor.createHTML('<div id="moaied__phone_right_toolbar"></div>'); 135 this.rightButtons = moaiEditor.createHTML('<div id="moaied__phone_right_buttons" class="moaied-phone-row"><div></div><div></div></div>'); 136 this.rightHeaderDetachable.appendChild(this.rightButtons); 137 this.rightHeaderDetachable.appendChild(this.rightToolbar); 138 this.rightHeaderContainer.appendChild(this.rightHeaderDetachable); 139 140 // Editor buttons row (Preview, Save, Cancel, etc) 141 var left = this.rightButtons.firstChild; 142 var right = this.rightButtons.lastChild; 143 left.appendChild(this.btn_goLeft.handle); 144 right.appendChild(moaiEditor.buttons.preview.handle); 145 right.appendChild(moaiEditor.buttons.save.handle); 146 right.appendChild(moaiEditor.buttons.cancel.handle); 147 148 // Default Dokuwiki toolbar row 149 this.toolbar = elements.toolbar; 150 this.rightToolbar.prepend(this.toolbar); 151 152 // Handle the weird behavior of iOS browsers by having a detachable header which does not dissapear whenever the on-screen-keyboard is opened 153 this.height = { 154 rightToolbar: this.rightToolbar.getBoundingClientRect().height, 155 rightButtons: this.rightButtons.getBoundingClientRect().height, 156 }; 157 window.visualViewport.addEventListener('scroll', this.onVisualViewportChange.bind(this)); 158 window.visualViewport.addEventListener('resize', this.onVisualViewportChange.bind(this)); 159 160 // ─────────────── Cosmetic things ───────────────── 161 162 this.btn_goRight.handle.style.height = this.btn_goLeft.handle.getBoundingClientRect().height + 'px'; 163 164 // ─────────────────── Debug ─────────────────────── 165 166 // Debug line 167 /* 168 this.debugline = moaiEditor.createHTML('<div id="moaied__phone_debugline"></div>'); 169 this.rightToolbar.prepend(this.debugline); 170 var interval = setInterval(this.onDebugInterval.bind(this), 200); 171 */ 172 173 // ───────────── Swipes and scroll ───────────────── 174 175 /* 176 document.querySelector("#moaied__phone_right").addEventListener('touchmove', event => { 177 event.preventDefault(); 178 }); 179 */ 180 // Detect swipes 181 new MoaiEditor.Swipe(this.left, this.onSwipe.bind(this), 120); 182 new MoaiEditor.Swipe(this.rightButtons, this.onSwipe.bind(this), 120); 183 184 // Show a little bit of the preview (to let the user know there is a preview available) 185 this.layout.panes.scrollLeft = window.innerWidth/5; 186 } 187 // ──────────────────────────────────── 188 onWindowResize() { 189 // Placeholder 190 } 191 // ──────────────────────────────────── 192 onVisualViewportChange() { 193 194 // Handles 195 const container = this.rightHeaderContainer; 196 const detachable = this.rightHeaderDetachable; 197 198 // Detach the header and position it into view if the browser has iOS behavior 199 if (window.visualViewport.pageTop > 3 && window.visualViewport.scale < 1.3) { 200 detachable.classList.add('moaied-header-detached'); 201 detachable.style.top = window.visualViewport.pageTop+'px'; 202 } else 203 detachable.classList.remove('moaied-header-detached'); 204 205 // Hide the editor buttons if the vertical space is small (due to on-screen-keyboard opened on a small phone) 206 if (window.visualViewport.height < 380) 207 this.rightButtons.style.display = 'none'; 208 else 209 this.rightButtons.style.display = 'flex'; 210 } 211 // ──────────────────────────────────── 212 onSwipe(direction) { 213 if (direction == 'left') 214 this.goRight(); 215 if (direction == 'right') 216 this.goLeft(); 217 } 218 goLeft() { 219 document.getElementById('moaied__wrapper').scrollLeft = 0; 220 } 221 goRight() { 222 document.getElementById('moaied__wrapper').scrollLeft = window.innerWidth; 223 } 224 // ──────────────────────────────────── 225 onDebugInterval() { 226 const html = document.documentElement; 227 const body = document.body; 228 const htmlHeight = html.getBoundingClientRect().height; 229 const bodyHeight = body.getBoundingClientRect().height; 230 this.debugline.textContent = "innerHeight: "+window.innerHeight+" - Scroll: "+html.scrollTop+" - htmlH: "+htmlHeight+" - bodyH: "+bodyHeight; 231 } 232}; // End Class 233 234 235 236