Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/resources/formats/revealjs/reveal/plugin/search/plugin.js
12923 views
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 <[email protected]>, 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;
244