plugin.js (6439B)
1 /*! 2 * Handles finding a text string anywhere in the slides and showing the next occurrence to the user 3 * by navigatating to that slide and highlighting it. 4 * 5 * @author Jon Snyder <snyder.jon@gmail.com>, February 2013 6 */ 7 8 const Plugin = () => { 9 10 // The reveal.js instance this plugin is attached to 11 let deck; 12 13 let searchElement; 14 let searchButton; 15 let searchInput; 16 17 let matchedSlides; 18 let currentMatchedIndex; 19 let searchboxDirty; 20 let hilitor; 21 22 function render() { 23 24 searchElement = document.createElement( 'div' ); 25 searchElement.classList.add( 'searchbox' ); 26 searchElement.style.position = 'absolute'; 27 searchElement.style.top = '10px'; 28 searchElement.style.right = '10px'; 29 searchElement.style.zIndex = 10; 30 31 //embedded base64 search icon Designed by Sketchdock - http://www.sketchdock.com/: 32 searchElement.innerHTML = `<input type="search" class="searchinput" placeholder="Search..." style="vertical-align: top;"/> 33 </span>`; 34 35 searchInput = searchElement.querySelector( '.searchinput' ); 36 searchInput.style.width = '240px'; 37 searchInput.style.fontSize = '14px'; 38 searchInput.style.padding = '4px 6px'; 39 searchInput.style.color = '#000'; 40 searchInput.style.background = '#fff'; 41 searchInput.style.borderRadius = '2px'; 42 searchInput.style.border = '0'; 43 searchInput.style.outline = '0'; 44 searchInput.style.boxShadow = '0 2px 18px rgba(0, 0, 0, 0.2)'; 45 searchInput.style['-webkit-appearance'] = 'none'; 46 47 deck.getRevealElement().appendChild( searchElement ); 48 49 // searchButton.addEventListener( 'click', function(event) { 50 // doSearch(); 51 // }, false ); 52 53 searchInput.addEventListener( 'keyup', function( event ) { 54 switch (event.keyCode) { 55 case 13: 56 event.preventDefault(); 57 doSearch(); 58 searchboxDirty = false; 59 break; 60 default: 61 searchboxDirty = true; 62 } 63 }, false ); 64 65 closeSearch(); 66 67 } 68 69 function openSearch() { 70 if( !searchElement ) render(); 71 72 searchElement.style.display = 'inline'; 73 searchInput.focus(); 74 searchInput.select(); 75 } 76 77 function closeSearch() { 78 if( !searchElement ) render(); 79 80 searchElement.style.display = 'none'; 81 if(hilitor) hilitor.remove(); 82 } 83 84 function toggleSearch() { 85 if( !searchElement ) render(); 86 87 if (searchElement.style.display !== 'inline') { 88 openSearch(); 89 } 90 else { 91 closeSearch(); 92 } 93 } 94 95 function doSearch() { 96 //if there's been a change in the search term, perform a new search: 97 if (searchboxDirty) { 98 var searchstring = searchInput.value; 99 100 if (searchstring === '') { 101 if(hilitor) hilitor.remove(); 102 matchedSlides = null; 103 } 104 else { 105 //find the keyword amongst the slides 106 hilitor = new Hilitor("slidecontent"); 107 matchedSlides = hilitor.apply(searchstring); 108 currentMatchedIndex = 0; 109 } 110 } 111 112 if (matchedSlides) { 113 //navigate to the next slide that has the keyword, wrapping to the first if necessary 114 if (matchedSlides.length && (matchedSlides.length <= currentMatchedIndex)) { 115 currentMatchedIndex = 0; 116 } 117 if (matchedSlides.length > currentMatchedIndex) { 118 deck.slide(matchedSlides[currentMatchedIndex].h, matchedSlides[currentMatchedIndex].v); 119 currentMatchedIndex++; 120 } 121 } 122 } 123 124 // Original JavaScript code by Chirp Internet: www.chirp.com.au 125 // Please acknowledge use of this code by including this header. 126 // 2/2013 jon: modified regex to display any match, not restricted to word boundaries. 127 function Hilitor(id, tag) { 128 129 var targetNode = document.getElementById(id) || document.body; 130 var hiliteTag = tag || "EM"; 131 var skipTags = new RegExp("^(?:" + hiliteTag + "|SCRIPT|FORM)$"); 132 var colors = ["#ff6", "#a0ffff", "#9f9", "#f99", "#f6f"]; 133 var wordColor = []; 134 var colorIdx = 0; 135 var matchRegex = ""; 136 var matchingSlides = []; 137 138 this.setRegex = function(input) 139 { 140 input = input.trim(); 141 matchRegex = new RegExp("(" + input + ")","i"); 142 } 143 144 this.getRegex = function() 145 { 146 return matchRegex.toString().replace(/^\/\\b\(|\)\\b\/i$/g, "").replace(/\|/g, " "); 147 } 148 149 // recursively apply word highlighting 150 this.hiliteWords = function(node) 151 { 152 if(node == undefined || !node) return; 153 if(!matchRegex) return; 154 if(skipTags.test(node.nodeName)) return; 155 156 if(node.hasChildNodes()) { 157 for(var i=0; i < node.childNodes.length; i++) 158 this.hiliteWords(node.childNodes[i]); 159 } 160 if(node.nodeType == 3) { // NODE_TEXT 161 var nv, regs; 162 if((nv = node.nodeValue) && (regs = matchRegex.exec(nv))) { 163 //find the slide's section element and save it in our list of matching slides 164 var secnode = node; 165 while (secnode != null && secnode.nodeName != 'SECTION') { 166 secnode = secnode.parentNode; 167 } 168 169 var slideIndex = deck.getIndices(secnode); 170 var slidelen = matchingSlides.length; 171 var alreadyAdded = false; 172 for (var i=0; i < slidelen; i++) { 173 if ( (matchingSlides[i].h === slideIndex.h) && (matchingSlides[i].v === slideIndex.v) ) { 174 alreadyAdded = true; 175 } 176 } 177 if (! alreadyAdded) { 178 matchingSlides.push(slideIndex); 179 } 180 181 if(!wordColor[regs[0].toLowerCase()]) { 182 wordColor[regs[0].toLowerCase()] = colors[colorIdx++ % colors.length]; 183 } 184 185 var match = document.createElement(hiliteTag); 186 match.appendChild(document.createTextNode(regs[0])); 187 match.style.backgroundColor = wordColor[regs[0].toLowerCase()]; 188 match.style.fontStyle = "inherit"; 189 match.style.color = "#000"; 190 191 var after = node.splitText(regs.index); 192 after.nodeValue = after.nodeValue.substring(regs[0].length); 193 node.parentNode.insertBefore(match, after); 194 } 195 } 196 }; 197 198 // remove highlighting 199 this.remove = function() 200 { 201 var arr = document.getElementsByTagName(hiliteTag); 202 var el; 203 while(arr.length && (el = arr[0])) { 204 el.parentNode.replaceChild(el.firstChild, el); 205 } 206 }; 207 208 // start highlighting at target node 209 this.apply = function(input) 210 { 211 if(input == undefined || !input) return; 212 this.remove(); 213 this.setRegex(input); 214 this.hiliteWords(targetNode); 215 return matchingSlides; 216 }; 217 218 } 219 220 return { 221 222 id: 'search', 223 224 init: reveal => { 225 226 deck = reveal; 227 deck.registerKeyboardShortcut( 'CTRL + Shift + F', 'Search' ); 228 229 document.addEventListener( 'keydown', function( event ) { 230 if( event.key == "F" && (event.ctrlKey || event.metaKey) ) { //Control+Shift+f 231 event.preventDefault(); 232 toggleSearch(); 233 } 234 }, false ); 235 236 }, 237 238 open: openSearch 239 240 } 241 }; 242 243 export default Plugin;