hematite 0.0.11 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,164 @@
1
+ import { getUrlQuery, Searcher } from "../search.mjs";
2
+ import { stringLookup } from "../strings.mjs";
3
+ import UrlHelper from "../UrlHelper.mjs";
4
+
5
+ function isInPresenterMode(targetDoc) {
6
+ return targetDoc.body.classList.contains("remark-presenter-mode");
7
+ }
8
+
9
+ function focusSearchResult(targetWin, query, targetMatchNo, slideshow, searcher) {
10
+ let matchesFound = 0;
11
+ for (const slide of slideshow.getSlides()) {
12
+ let targetText = (slide.content ?? []).join("\n");
13
+ let currentSlideNo = slide.getSlideIndex() + 1;
14
+
15
+ matchesFound += searcher.getNumberOfMatches(query, targetText);
16
+ console.log("Considering ", targetText, " with ", matchesFound, "matches found so far");
17
+
18
+ if (matchesFound > targetMatchNo) {
19
+ slideshow.gotoSlide(currentSlideNo);
20
+ return;
21
+ }
22
+
23
+ // Now search the notes!
24
+ if (slide.notes) {
25
+ targetText = slide.notes.join("\n");
26
+ matchesFound += searcher.getNumberOfMatches(query, targetText);
27
+ if (matchesFound > targetMatchNo) {
28
+ slideshow.gotoSlide(currentSlideNo);
29
+
30
+ if (!isInPresenterMode(targetWin.document)) {
31
+ slideshow.togglePresenterMode();
32
+ }
33
+ return;
34
+ }
35
+ }
36
+ }
37
+
38
+ console.log("Found ", matchesFound, " matches, which is less than the target of ", targetMatchNo);
39
+ slideshow.gotoLastSlide();
40
+ return -1;
41
+ }
42
+
43
+ /// Show a search result the user requested through the
44
+ /// page's URL.
45
+ function focusSearchResultFromUrl(targetWin, slideshow) {
46
+ let { query, resultIndex } = getUrlQuery() ?? {};
47
+ let index = resultIndex;
48
+
49
+ if (query === undefined || index === undefined) {
50
+ return;
51
+ }
52
+
53
+ console.log("Focusing a search result: ", query, index);
54
+
55
+ let searcher = new Searcher();
56
+ focusSearchResult(targetWin, query, index, slideshow, searcher);
57
+ }
58
+
59
+ function focusSlideFromHash(slideshow) {
60
+ let hash = UrlHelper.getPageHash();
61
+ if (!hash) {
62
+ return;
63
+ }
64
+
65
+ let targetSlide = parseInt(hash.substring(1));
66
+ if (targetSlide) {
67
+ console.log("Navigating to slide", targetSlide);
68
+ slideshow.gotoSlide(targetSlide);
69
+ }
70
+ }
71
+
72
+ async function main(targetWindow, config) {
73
+ if (!targetWindow.remark) {
74
+ // Wait for page load if remark isn't available yet.
75
+ await (new Promise(resolve => {
76
+ targetWindow.addEventListener('load', resolve);
77
+ }));
78
+ }
79
+
80
+ // See https://remarkjs.com/#8
81
+ let slideshow = targetWindow.remark.create(config);
82
+
83
+ // For debugging
84
+ window.slideshow_debug = slideshow;
85
+
86
+ targetWindow.focus();
87
+
88
+ addExtendedControls(targetWindow, slideshow);
89
+ focusSearchResultFromUrl(targetWindow, slideshow);
90
+
91
+ targetWindow.history.replaceState(null, targetWindow.location.href);
92
+ let targetWinHistory = targetWindow.history.state;
93
+
94
+ focusSlideFromHash(slideshow);
95
+
96
+ window.addEventListener('hashchange', () => {
97
+ focusSlideFromHash(slideshow);
98
+ });
99
+
100
+ slideshow.on('showSlide', function(newSlide) {
101
+ if (!newSlide) {
102
+ return;
103
+ }
104
+ let hashId = newSlide.getSlideIndex() + 1;
105
+
106
+ // Update the window's URL to match that of the interior
107
+ // (e.g. slide 3 = #3).
108
+ // Try not to have the forward/back arrows go forward/back in the iframe's history.
109
+ targetWindow.history.replaceState(
110
+ targetWinHistory,
111
+ UrlHelper.withReplacedHash(targetWindow.location.href, hashId));
112
+ window.location.hash = hashId;
113
+ });
114
+ }
115
+
116
+ /// Apply minor adjustments to the default remark layout
117
+ function addExtendedControls(targetWindow, slideshow) {
118
+ let slideContainer = targetWindow.document.querySelector(".remark-slides-area");
119
+
120
+ // Announce changes to the slide (e.g. going to the next slide).
121
+ slideContainer.setAttribute("aria-live", "polite");
122
+
123
+ // Add next/previous buttons
124
+ let nextSlideBtn = targetWindow.document.createElement("button");
125
+ let prevSlideBtn = targetWindow.document.createElement("button");
126
+ let printBtn = targetWindow.document.createElement("button");
127
+ let spacer = targetWindow.document.createElement("div");
128
+
129
+ let nav = targetWindow.document.createElement("nav");
130
+
131
+ nextSlideBtn.innerText = stringLookup('btn_next_slide');
132
+ prevSlideBtn.innerText = stringLookup('btn_prev_slide');
133
+ printBtn.innerText = stringLookup('btn_print');
134
+
135
+ spacer.style.flexGrow = 1;
136
+
137
+ nextSlideBtn.onclick = () => {
138
+ slideshow.gotoNextSlide();
139
+ };
140
+
141
+ prevSlideBtn.onclick = () => {
142
+ slideshow.gotoPreviousSlide();
143
+ };
144
+
145
+ printBtn.onclick = () => {
146
+ targetWindow.print();
147
+ };
148
+
149
+ slideshow.on('showSlide', function(newSlide) {
150
+ if (!newSlide) {
151
+ return;
152
+ }
153
+
154
+ prevSlideBtn.disabled = (newSlide.getSlideIndex() == 0);
155
+ nextSlideBtn.disabled = (newSlide.getSlideIndex() + 1 >= slideshow.getSlideCount());
156
+ });
157
+
158
+
159
+
160
+ nav.replaceChildren(prevSlideBtn, nextSlideBtn, spacer, printBtn);
161
+ targetWindow.document.body.appendChild(nav);
162
+ }
163
+
164
+ export default { start: main };
data/assets/js/search.mjs CHANGED
@@ -54,6 +54,25 @@ class Searcher {
54
54
 
55
55
  }
56
56
 
57
+ /// Get number of full matches for [query] in [text].
58
+ /// @precondition [text] is already in a searchable (i.e.
59
+ /// filtered) form.
60
+ getNumberOfMatches(query, text) {
61
+ query = query.toLowerCase();
62
+
63
+ return text.toLowerCase().split(query).length - 1;
64
+ }
65
+
66
+ /// Get the index of the first full match for [query] in
67
+ /// [text], starting at [startPos]. Returns -1 if no match
68
+ /// is found.
69
+ /// @precondition [text] is already in a searchable form.
70
+ getIdxOfFirstMatch(query, text, startPos) {
71
+ query = query.toLowerCase();
72
+
73
+ return text.toLowerCase().indexOf(query, startPos);
74
+ }
75
+
57
76
  /// Get the container element for the [n]th search
58
77
  /// result for [query] in the given [elem].
59
78
  /// Returns an Element.
@@ -77,7 +96,7 @@ class Searcher {
77
96
 
78
97
  // If the current node is a leaf,
79
98
  if (elem.childNodes.length == 0) {
80
- let numMatches = searchText.split(query).length - 1;
99
+ let numMatches = this.getNumberOfMatches(query, searchText);
81
100
  n -= numMatches;
82
101
 
83
102
  // If we've considered enough matches,
@@ -154,7 +173,7 @@ class Searcher {
154
173
  index ++;
155
174
  pageData.numMatches ++;
156
175
  startPos = matchLoc + query.length;
157
- matchLoc = toSearch.indexOf(query, startPos);
176
+ matchLoc = this.getIdxOfFirstMatch(query, toSearch, startPos);
158
177
  }
159
178
  }
160
179
 
@@ -185,26 +204,52 @@ class Searcher {
185
204
  }
186
205
  }
187
206
 
207
+ /// Extract a search query and index from the current page's URL.
208
+ function getUrlQuery() {
209
+ let query, resultIndex;
210
+
211
+ let urlArgs = UrlHelper.getPageArgs();
212
+ let pageHash = UrlHelper.getPageHash();
213
+
214
+ if (urlArgs === null) {
215
+ return;
216
+ }
217
+
218
+ // The page's hash also causes scrolling. Don't focus
219
+ // if the page has a hash.
220
+ if (pageHash != null) {
221
+ return;
222
+ }
223
+
224
+ query = urlArgs.query;
225
+ resultIndex = parseInt(urlArgs.index);
226
+
227
+ if (urlArgs.index === undefined) {
228
+ resultIndex = 0;
229
+ }
230
+
231
+ return { query, resultIndex };
232
+ }
233
+
188
234
  /// Scrolls to and shows the search result for the given [query]
189
235
  /// If neither [query] nor [resultIndex] are given, attempt to get them from
190
236
  /// the page's arguments (i.e. from https://example.com/...?search=...,n=...).
191
237
  function focusSearchResult(searcher, elem, query, resultIndex) {
192
- if (query === undefined && resultIndex === undefined) {
193
- let urlArgs = UrlHelper.getPageArgs();
194
- let pageHash = UrlHelper.getPageHash();
238
+ if (elem === undefined) {
239
+ console.warn(`focusSearchResult requires [elem] to function. Not focusing a result.`);
240
+ return;
241
+ }
195
242
 
196
- if (urlArgs === null) {
197
- return;
198
- }
243
+ if (query === undefined && resultIndex === undefined) {
244
+ let pageArgs = getUrlQuery();
199
245
 
200
- // The page's hash also causes scrolling. Don't focus
201
- // if the page has a hash.
202
- if (pageHash != null) {
246
+ // No args, nothing to focus.
247
+ if (!pageArgs) {
203
248
  return;
204
249
  }
205
250
 
206
- query = urlArgs.query;
207
- resultIndex = parseInt(urlArgs.index);
251
+ resultIndex = pageArgs.resultIndex;
252
+ query = pageArgs.query;
208
253
 
209
254
  if (isNaN(resultIndex)) {
210
255
  console.warn("Unable to navigate to result. Given idx is NaN");
@@ -355,4 +400,4 @@ function handleSearch(searcher) {
355
400
  }
356
401
 
357
402
  export default handleSearch;
358
- export { handleSearch, Searcher };
403
+ export { handleSearch, getUrlQuery, Searcher };