1(function () { 2 const { mergeObjects } = BreakdanceFrontend.utils; 3 4 function slugify(text) { 5 return text 6 .toString() 7 .normalize("NFD") 8 .replace(/[\u0300-\u036f]/g, "") 9 .toLowerCase() 10 .trim() 11 .replace(/\s+/g, "-") 12 .replace(/[^\w-]+/g, "") 13 .replace(/--+/g, "-"); 14 } 15 16 class BreakdanceTOC { 17 options = { 18 content: { 19 table: { 20 advanced: { 21 container_selector: "body.breakdance", 22 }, 23 }, 24 }, 25 design: { 26 collapse: false, 27 style: "none", 28 scroll_offset: { 29 number: 10, 30 }, 31 heading_offset: { 32 number: 10, 33 }, 34 }, 35 tocSelector: ".js-breakdance-toc", 36 }; 37 38 constructor(selector, options) { 39 this.selector = selector; 40 this.element = document.querySelector(`${this.selector}`); 41 if (!this.element) return; 42 this.tocSelector = this.element.querySelector( 43 `${this.options.tocSelector}` 44 ); 45 this.options = mergeObjects(this.options, options || {}); 46 this.headingSelectors = this.generateSelectors(); 47 48 this.init(); 49 } 50 51 generateSelectors() { 52 const selectors = this.options.content.table.included_headings; 53 if (selectors) { 54 const keys = Object.keys(selectors) 55 .filter((k) => selectors[k] === true) 56 .toString(); 57 return keys; 58 } else { 59 return "h2,h3,h4"; 60 } 61 } 62 63 getHeadings() { 64 const containerSelector = 65 this.options.content.table.advanced.container_selector; 66 if (containerSelector.length <= 1) return []; 67 const container = document.querySelector(containerSelector); 68 69 if (!container) return []; 70 71 return [].slice.call(container.querySelectorAll(this.headingSelectors)); 72 } 73 74 setHeadingIds() { 75 const headings = this.getHeadings(); 76
77 headings.forEach((heading, index) => { 78 const slug = slugify(heading.textContent); 79 let ref = `${slug}-${index}`; 80 81 if (!heading.id) { 82 heading.setAttribute("id", ref); 83 } 84 }); 85 } 86 87 maybeInitAccordion() { 88 this.accordion = this.element.querySelector(".js-toc-accordion"); 89 if (!this.accordion) return; 90 this.accordionClickHandler = this.accordionClick.bind(this); 91 this.accordion.classList.remove("is-on"); 92 this.accordion.addEventListener("click", this.accordionClickHandler); 93 } 94 95 accordionClick(event) { 96 event.preventDefault(); 97 this.accordion.classList.toggle("is-on"); 98 } 99 100 destroyAccordionClick() { 101 this.accordion.removeEventListener("click", this.accordionClickHandler); 102 } 103 104 generateJsonLd() { 105 const links = [].slice 106 .call(this.element.querySelectorAll(".toc-link")) 107 .map((link) => { 108 return { 109 "@context": "https://schema.org", 110 "@type": "SiteNavigationElement", 111 "@id": "#bde-toc", 112 name: link.textContent, 113 url: `${link.href}`, 114 }; 115 }); 116 117 const script = document.createElement("script"); 118 script.id = "bde-toc-json-ld"; 119 120 script.type = "application/ld+json"; 121 script.innerHTML = JSON.stringify({ 122 "@context": "https://schema.org", 123 "@graph": [links], 124 }); 125 126 document.head.appendChild(script); 127 } 128 129 initTocbot() { 130 this.tocbotInstance = tocbot; 131 const collapseDepth = this.options.design.collapse == true ? 0 : 6; 132 133 this.tocbotInstance.init({ 134 // Where to render the table of contents. 135 tocSelector: this.options.tocSelector, 136 // Where to grab the headings to build the table of contents. 137 contentSelector: this.options.content.table.advanced.container_selector, 138 // Which headings to grab inside of the contentSelector element. 139 headingSelector: this.headingSelectors, 140 // Headings that match the ignoreSelector will be skipped. 141 ignoreSelector: 142 this.options.content.table.advanced.ignore_selectors || ".toc-ignore", 143 // How many heading levels should not be collapsed. 144 // For example, number 6 will show everything since 145 // there are only 6 heading levels and number 0 will collapse them all. 146 // The sections that are hidden will open 147 // and close as you scroll to headings within them. 148 collapseDepth: collapseDepth, 149 // Smooth scroll offset. 150 scrollSmoothOffset: this.options.design.scroll_offset.number * -1, 151 headingsOffset: this.options.design.heading_offset.number, 152 hasInnerContainers: true, 153 headingObjectCallback: this.headingObjectCallback.bind(this), 154 }); 155 this.tocbotInstance.refresh(); 156 } 157 158 headingObjectCallback(obj, node) { 159 const isHeadingWithinPopup = node.closest(".bde-popup"); 160 const isTocWithinPopup = this.element.closest(".bde-popup"); 161 162 // If the heading is within a popup and the TOC is not, don't show the popup heading in the TOC 163 if (isHeadingWithinPopup && !isTocWithinPopup) { 164 return null; 165 } 166 return obj; 167 } 168 169 init() { 170 this.setHeadingIds(); 171 this.maybeInitAccordion(); 172 this.initTocbot(); 173 this.generateJsonLd(); 174 } 175 176 update() { 177 if (!this.tocbotInstance) return; 178 this.tocbotInstance.refresh(); 179 } 180 181 destroy() { 182 if (!this.tocbotInstance) return; 183 this.tocbotInstance.destroy(); 184 this.tocbotInstance = null; 185 186 document.querySelector("#bde-toc-json-ld")?.remove(); 187 } 188 } 189 190 window.BreakdanceTOC = BreakdanceTOC; 191})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.