hematite 0.0.11 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 };