1/*  DokuWiki MoaiEditor Layout_vphone.js file
2    Version : 0.5a (May 6, 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+'&nbsp;&nbsp;'+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