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 = " ".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