1/*  DokuWiki MoaiEditor Toc.js file
2    Author  : MoaiTools <info@moaitools.org>
3    License : GPL 3 (http://www.gnu.org/licenses/gpl.html) */
4
5/*  Shows a table of contents (at the top of the editor) which scrolls both panes to the
6    chosen section. It has a header depth selector on the side to reduce the amount of
7    headers shown in large documents.
8
9    Html structure:     #moaied__toc                        -- div
10                            #moaied__toc_title              -- span
11                            #moaied__toc_dropdowns          -- div
12                                #moaied__toc_depth          -- select
13                                #moaied__toc_wrapper        -- div
14                                    #moaied__toc_sections   -- select
15                                    #moaied__toc_dummy      -- select
16*/
17MoaiEditor.ToC = class {
18
19    constructor () {
20
21        // Variables
22        this.text = '';     // Text for dummy dropdown (for illusion of main dropdown persistence)
23        this.headers = [];  // Array of {i, type, level, text, handle}
24
25        // Objects
26        this.depth = new MoaiEditor.LocalStorage('toc_depth', 5, [2,3,4,5]);
27
28        // Create the elements
29        const container = moaiEditor.createHTML('   <div id="moaied__toc" style="display:none"></div>');
30        const title     = moaiEditor.createHTML(' <label id="moaied__toc_label">Table of contents</label>');
31        const dropdowns = moaiEditor.createHTML('   <div id="moaied__toc_dropdowns"></div>');
32        const depth     = moaiEditor.createHTML('<select id="moaied__toc_depth"></select>');
33        const wrapper   = moaiEditor.createHTML('   <div id="moaied__toc_wrapper"></div>');
34        const sections  = moaiEditor.createHTML('<select id="moaied__toc_sections"></select>');
35        const dummy     = moaiEditor.createHTML('<select id="moaied__toc_dummy"></select>');
36
37        // Add children
38        container.appendChild(title);
39        container.appendChild(dropdowns);
40        dropdowns.appendChild(wrapper);
41        dropdowns.appendChild(depth);
42        wrapper.appendChild(sections);
43        wrapper.appendChild(dummy);
44
45        // Add user input event handlers
46        sections.addEventListener("change", this.onSectionChange.bind(this));
47        depth.addEventListener("change", this.onDepthChange.bind(this));
48
49        // Add tooltips
50        sections.title = "Scroll to a specific section";
51        depth.title = "Choose the depth of the table of contents";
52
53        // Populate the depth chooser (h2, h3, h4, h5)
54        for (let i of [2,3,4,5])
55            depth.appendChild(moaiEditor.createHTML('<option value="'+i+'">h'+i+'</option>'));
56
57        // Keep some handles to the HTML elements
58        this.container = container;
59        this.sections = sections;
60        this.dummy = dummy;
61        this.depthSelector = depth;
62    }
63
64    // Called whenever matches are recalculated (typically on preview updates)
65    update () {
66        let i = 0;
67        this.headers = [];
68        // Gather all matched headers
69        for (let match of moaiEditor.matches.matches)
70            if (['H1','H2','H3','H4','H5'].includes(match.type)) {
71                this.headers.push({
72                    i      : i,
73                    type   : match.type,
74                    level  : Number(match.type.substr(1,1)),
75                    text   : match.handle.textContent,
76                    handle : match.handle
77                });
78                i += 1;
79            }
80        // Update UI
81        this.redraw();
82    }
83
84    redraw () {
85
86        // Update the depth dropdown selected option to what is saved
87        const depth = this.depth.value;
88        this.depthSelector.selectedIndex = depth-2;
89
90        // Remove all previous options
91        this.sections.options.length = 0;
92
93        // If there is only one <h1> dont show it (makes the element wider because everything else will be indented)
94        var ignoreH1 = 0;
95        var count = 0;
96        for (let header of this.headers)
97            if (header.type == 'H1')
98                count += 1;
99        if (count < 2)
100            ignoreH1 = 1;
101
102        // Add an empty option first
103        this.sections.appendChild(moaiEditor.createHTML('<option></option>'));
104
105        // Add options
106        for (let header of this.headers) {
107            if (ignoreH1 == 1  &&  header.type == "H1")
108                continue;
109            if (header.level > depth)
110                continue;
111            const indent = "&nbsp;".repeat(4*(header.level-1-ignoreH1));
112            const html = moaiEditor.createHTML('<option value="' + header.i + '">' + indent + header.text + '</option>');
113            this.sections.appendChild(html);
114        }
115        // Hide the elements if there are no sections to show
116        if (this.sections.options.length < 2) {
117            this.container.style.display = 'none';
118            return;
119        }
120        // Show them otherwise
121        this.container.style.display = 'flex';
122
123        // Set dummy width
124        const width = this.sections.getBoundingClientRect().width;
125        this.dummy.style.width = width+'px';
126
127        // Set dummy value
128        this.dummy.options.length = 0;
129        this.dummy.appendChild(moaiEditor.createHTML('<option>'+this.text+'</option>'));
130    }
131
132    onDepthChange () {
133
134
135        // Store the value
136        const value = this.depthSelector.value;
137        this.depth.value = parseInt(value);
138
139        // Update the UI
140        this.text = '';
141        this.redraw();
142    }
143
144    onSectionChange () {
145
146        // Update the text in the dummy
147        const value = this.sections.value;
148        this.text = '';
149        if (value != '')
150            this.text = this.headers[value].text;
151
152        // Update the UI
153        this.redraw();
154
155        // Return if no section was selected
156        if (value == '')
157            return;
158
159        // Scroll
160        const element = this.headers[value].handle;
161        moaiEditor.scroll.toc (element);
162    }
163
164}; // End Class
165