Skip to content

Commit 101643c

Browse files
committed
Add basic search spotlighting
1 parent 2555603 commit 101643c

File tree

1 file changed

+86
-15
lines changed

1 file changed

+86
-15
lines changed

devdocs-macos/user-scripts/page-search.js

Lines changed: 86 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
(function() {
22
const DATA_TEXTCONTENT = 'data-dd-original-textcontent';
33

4+
const flatten = function(xs) {
5+
return xs.reduce((acc, x) => {
6+
if (Array.isArray(x)) {
7+
return acc.concat(flatten(x));
8+
}
9+
return acc.concat([x]);
10+
}, []);
11+
};
12+
413
const rootSearchNode = function() {
514
return document.querySelector('main[role="main"]');
615
};
@@ -14,10 +23,7 @@
1423
const mutateDOM = function(mutations) {
1524
return new Promise(function(resolve, _reject) {
1625
requestAnimationFrame(function() {
17-
mutations.forEach(function(mut) {
18-
mut();
19-
});
20-
resolve(true);
26+
resolve(mutations.map((mut) => mut()));
2127
});
2228
});
2329
};
@@ -40,6 +46,7 @@
4046
(match) => `<mark>${match}</mark>`
4147
);
4248
node.innerHTML = newContent;
49+
return Array.from(node.querySelectorAll('mark'));
4350
};
4451
};
4552

@@ -55,7 +62,7 @@
5562
const reset = function() {
5663
const mainNode = rootSearchNode();
5764
const treeWalker = document.createTreeWalker(mainNode, NodeFilter.SHOW_ELEMENT, {
58-
acceptNode: function(node) {
65+
acceptNode(node) {
5966
if (node.hasAttribute(DATA_TEXTCONTENT)) {
6067
return NodeFilter.FILTER_ACCEPT;
6168
}
@@ -72,7 +79,7 @@
7279
const search = function(term) {
7380
const mainNode = rootSearchNode();
7481
const treeWalker = document.createTreeWalker(mainNode, NodeFilter.SHOW_TEXT, {
75-
acceptNode: function(node) {
82+
acceptNode(node) {
7683
var parent = node.parentNode;
7784
if (parent.tagName === 'MARK') {
7885
return NodeFilter.FILTER_REJECT;
@@ -94,11 +101,11 @@
94101
return mutateDOM(domMutations);
95102
};
96103

97-
// hacky, but seems to be the only reliable way to clear search results on
98-
// page change.
104+
// No reliable API to detect route transitions so that we can restore the
105+
// original page state.
99106
(function() {
100107
const observer = new MutationObserver(() => {
101-
reset();
108+
window.resetSearch();
102109
});
103110
const titleEl = document.querySelector('title');
104111
observer.observe(titleEl, {
@@ -108,15 +115,79 @@
108115
});
109116
})();
110117

111-
window.search = function(term) {
112-
if (term === '' || typeof term !== 'string') {
113-
return;
118+
// Install custom CSS
119+
(function() {
120+
const styleEl = document.createElement('style');
121+
styleEl.setAttribute('type', 'text/css');
122+
styleEl.textContent = `
123+
mark.dd-macos-current {
124+
font-size: 2em;
125+
}
126+
`;
127+
document.querySelector('head').appendChild(styleEl);
128+
})();
129+
130+
class SearchState {
131+
constructor({ term, marks }) {
132+
this.term = term;
133+
this.marks = marks;
134+
}
135+
136+
isCurrentTerm(term) {
137+
return this.term === term;
138+
}
139+
140+
async spotlightMark() {
141+
if (this.marks.length === 0) {
142+
return this;
143+
}
144+
const [next, ...rest] = this.marks;
145+
const prev = this.marks[this.marks.length - 1];
146+
147+
await mutateDOM([() => {
148+
prev.removeAttribute('class');
149+
next.setAttribute('class', 'dd-macos-current');
150+
// Use DevDocs own utilities.
151+
$.scrollTo(next);
152+
}]);
153+
154+
this.marks = rest.concat([next]);
155+
return this;
114156
}
115-
return reset().then(() => search(term));
157+
}
158+
159+
let searchState;
160+
161+
// Public API
162+
163+
window.search = async function(term) {
164+
if (typeof term !== 'string') {
165+
return false;
166+
}
167+
const searchTerm = term.trim();
168+
if (searchTerm === '') {
169+
return false;
170+
}
171+
if (searchState && searchState.isCurrentTerm(searchTerm)) {
172+
await searchState.spotlightMark();
173+
return true;
174+
}
175+
176+
await reset();
177+
const insertedMarks = await search(searchTerm);
178+
const ss = new SearchState({
179+
term,
180+
marks: flatten(insertedMarks),
181+
});
182+
await ss.spotlightMark();
183+
searchState = ss;
184+
return true;
116185
};
117186

118-
window.resetSearch = function() {
119-
return reset();
187+
window.resetSearch = async function() {
188+
await reset();
189+
searchState = null
190+
return true;
120191
};
121192
})();
122193

0 commit comments

Comments
 (0)