bookclub-advr

DSLC Advanced R Book Club
git clone https://git.eamoncaddigan.net/bookclub-advr.git
Log | Files | Refs | README | LICENSE

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;