Skip to content

Commit a6b60be

Browse files
authored
Merge pull request #6986 from readthedocs/mkdocs-indocsearch
Allow to enable server side search for MkDocs
2 parents 6b32b39 + 07730a1 commit a6b60be

File tree

6 files changed

+194
-6
lines changed

6 files changed

+194
-6
lines changed

readthedocs/core/static-src/core/js/doc-embed/search.js

+175-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Sphinx search overrides
2+
* Sphinx and Mkdocs search overrides
33
*/
44

55
var rtddata = require('./rtd-data');
@@ -40,7 +40,7 @@ function append_html_to_contents(contents, template, data) {
4040
* Sphinx indexer. This will fall back to the standard indexer on an API
4141
* failure,
4242
*/
43-
function attach_elastic_search_query(data) {
43+
function attach_elastic_search_query_sphinx(data) {
4444
var project = data.project;
4545
var version = data.version;
4646
var language = data.language || 'en';
@@ -56,7 +56,6 @@ function attach_elastic_search_query(data) {
5656
search_def
5757
.then(function (data) {
5858
var hit_list = data.results || [];
59-
var total_count = data.count || 0;
6059

6160
if (hit_list.length) {
6261
for (var i = 0; i < hit_list.length; i += 1) {
@@ -265,12 +264,13 @@ function attach_elastic_search_query(data) {
265264
};
266265

267266
if (typeof Search !== 'undefined' && project && version) {
268-
269267
// Do not replace the built-in search if RTD's docsearch is disabled
270268
if (!data.features || !data.features.docsearch_disabled) {
271269
var query_fallback = Search.query;
272270
Search.query_fallback = query_fallback;
273271
Search.query = query_override;
272+
} else {
273+
console.log('Server side search is disabled.');
274274
}
275275
}
276276
$(document).ready(function () {
@@ -281,9 +281,179 @@ function attach_elastic_search_query(data) {
281281
}
282282

283283

284+
/*
285+
* Mkdocs search override for hitting our API instead of the standard Mkdocs search index.
286+
* This will fall back to the original search on an API failure.
287+
*/
288+
function attach_elastic_search_query_mkdocs(data) {
289+
var project = data.project;
290+
var version = data.version;
291+
var language = data.language || 'en';
292+
293+
var fallbackSearch = function () {
294+
if (typeof window.doSearchFallback !== 'undefined') {
295+
window.doSearchFallback();
296+
} else {
297+
console.log('Unable to fallback to original MkDocs search.');
298+
}
299+
};
300+
301+
var doSearch = function () {
302+
var query = document.getElementById('mkdocs-search-query').value;
303+
304+
var search_def = $.Deferred();
305+
306+
var search_url = document.createElement('a');
307+
search_url.href = data.proxied_api_host + '/api/v2/docsearch/';
308+
search_url.search = '?q=' + encodeURIComponent(query) + '&project=' + project +
309+
'&version=' + version + '&language=' + language;
310+
311+
search_def
312+
.then(function (data) {
313+
var hit_list = data.results || [];
314+
315+
if (hit_list.length) {
316+
var searchResults = $('#mkdocs-search-results');
317+
searchResults.empty();
318+
319+
for (var i = 0; i < hit_list.length; i += 1) {
320+
var doc = hit_list[i];
321+
var inner_hits = doc.inner_hits || [];
322+
323+
var result = $('<article>');
324+
result.append(
325+
$('<h3>').append($('<a>', {'href': doc.link, 'text': doc.title}))
326+
);
327+
328+
if (doc.project !== project) {
329+
var text = '(from project ' + doc.project + ')';
330+
result.append($('<span>', {'text': text}));
331+
}
332+
333+
for (var j = 0; j < inner_hits.length; j += 1) {
334+
var section = inner_hits[j];
335+
336+
if (section.type === 'sections') {
337+
var section_link = doc.link + '#' + section._source.id;
338+
var section_title = section._source.title;
339+
var section_content = section._source.content;
340+
if (section_content.length > MAX_SUBSTRING_LIMIT) {
341+
section_content = section_content.substr(0, MAX_SUBSTRING_LIMIT) + " ...";
342+
}
343+
var section_contents = [section_content];
344+
345+
if (section.highlight) {
346+
if (section.highlight["sections.title"]) {
347+
section_title = section.highlight["sections.title"][0];
348+
}
349+
if (section.highlight["sections.content"]) {
350+
var contents = section.highlight["sections.content"];
351+
section_contents = [];
352+
for (
353+
var k = 0;
354+
k < contents.length && k < MAX_RESULT_PER_SECTION;
355+
k += 1
356+
) {
357+
section_contents.push("... " + contents[k] + " ...");
358+
}
359+
}
360+
}
361+
362+
section_title = xss(section_title)
363+
.replace(/<span>/g, '<mark>')
364+
.replace(/<\/span>/g, '</mark>');
365+
result.append(
366+
$('<h4>')
367+
.append($('<a>', {'href': section_link}).html(section_title))
368+
);
369+
for (var m = 0; m < section_contents.length; m += 1) {
370+
var content = xss(section_contents[m]);
371+
content = content
372+
.replace(/<span>/g, '<mark>')
373+
.replace(/<\/span>/g, '</mark>');
374+
result.append(
375+
$('<p>').html(content)
376+
);
377+
}
378+
searchResults.append(result);
379+
}
380+
}
381+
}
382+
} else {
383+
console.log('Read the Docs search returned 0 result. Falling back to MkDocs search.');
384+
fallbackSearch();
385+
}
386+
})
387+
.fail(function (error) {
388+
console.log('Read the Docs search failed. Falling back to MkDocs search.');
389+
fallbackSearch();
390+
});
391+
392+
$.ajax({
393+
url: search_url.href,
394+
crossDomain: true,
395+
xhrFields: {
396+
withCredentials: true,
397+
},
398+
complete: function (resp, status_code) {
399+
if (
400+
status_code !== 'success' ||
401+
typeof (resp.responseJSON) === 'undefined' ||
402+
resp.responseJSON.count === 0
403+
) {
404+
return search_def.reject();
405+
}
406+
return search_def.resolve(resp.responseJSON);
407+
}
408+
})
409+
.fail(function (resp, status_code, error) {
410+
return search_def.reject();
411+
});
412+
};
413+
414+
var initSearch = function () {
415+
var search_input = document.getElementById('mkdocs-search-query');
416+
if (search_input) {
417+
search_input.addEventListener('keyup', doSearch);
418+
}
419+
420+
var term = window.getSearchTermFromLocation();
421+
if (term) {
422+
search_input.value = term;
423+
doSearch();
424+
}
425+
};
426+
427+
$(document).ready(function () {
428+
// We can't override the search completely,
429+
// because we can't delete the original event listener,
430+
// and MkDocs includes its search functions after ours.
431+
// If MkDocs is loaded before, this will trigger a double search
432+
// (but ours will have precendece).
433+
434+
// Note: this function is only available on Mkdocs >=1.x
435+
window.doSearchFallback = window.doSearch;
436+
437+
window.doSearch = doSearch;
438+
window.initSearch = initSearch;
439+
initSearch();
440+
});
441+
}
442+
443+
284444
function init() {
285445
var data = rtddata.get();
286-
attach_elastic_search_query(data);
446+
if (data.is_sphinx_builder()) {
447+
// Check to disabled server side search for Sphinx
448+
// happens inside the function, because we still need to call Search.init().
449+
attach_elastic_search_query_sphinx(data);
450+
}
451+
// MkDocs projects should have this flag explicitly for now.
452+
else if (data.features && !data.features.docsearch_disabled) {
453+
attach_elastic_search_query_mkdocs(data);
454+
} else {
455+
console.log('Server side search is disabled.');
456+
}
287457
}
288458

289459
module.exports = {

readthedocs/core/static/core/js/readthedocs-doc-embed.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

readthedocs/doc_builder/backends/mkdocs.py

+6
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,12 @@ def generate_rtd_data(self, docs_dir, mkdocs_config):
253253
'commit': self.version.project.vcs_repo(self.version.slug).commit,
254254
'global_analytics_code': settings.GLOBAL_ANALYTICS_CODE,
255255
'user_analytics_code': analytics_code,
256+
'features': {
257+
'docsearch_disabled': (
258+
not self.project.has_feature(Feature.ENABLE_MKDOCS_SERVER_SIDE_SEARCH)
259+
or self.project.has_feature(Feature.DISABLE_SERVER_SIDE_SEARCH)
260+
)
261+
},
256262
}
257263

258264
data_ctx = {

readthedocs/doc_builder/backends/sphinx.py

+1
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ def get_config_params(self):
181181
'dont_overwrite_sphinx_context': self.project.has_feature(
182182
Feature.DONT_OVERWRITE_SPHINX_CONTEXT,
183183
),
184+
'docsearch_disabled': self.project.has_feature(Feature.DISABLE_SERVER_SIDE_SEARCH),
184185
}
185186

186187
finalize_sphinx_context_data.send(

readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl

+1
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ context = {
114114
'new_theme': (html_theme == "sphinx_rtd_theme"),
115115
'source_suffix': SUFFIX,
116116
'ad_free': {% if project.show_advertising %}False{% else %}True{% endif %},
117+
'docsearch_disabled': {{ docsearch_disabled }},
117118
'user_analytics_code': '{{ project.analytics_code|default_if_none:'' }}',
118119
'global_analytics_code': '{{ settings.GLOBAL_ANALYTICS_CODE }}',
119120
'commit': {% if project.repo_type == 'git' %}'{{ commit|slice:"8" }}'{% else %}'{{ commit }}'{% endif %},

readthedocs/projects/models.py

+10
Original file line numberDiff line numberDiff line change
@@ -1589,6 +1589,8 @@ def add_features(sender, **kwargs):
15891589
SKIP_SYNC_BRANCHES = 'skip_sync_branches'
15901590
CACHED_ENVIRONMENT = 'cached_environment'
15911591
LIMIT_CONCURRENT_BUILDS = 'limit_concurrent_builds'
1592+
DISABLE_SERVER_SIDE_SEARCH = 'disable_server_side_search'
1593+
ENABLE_MKDOCS_SERVER_SIDE_SEARCH = 'enable_mkdocs_server_side_search'
15921594
FORCE_SPHINX_FROM_VENV = 'force_sphinx_from_venv'
15931595
LIST_PACKAGES_INSTALLED_ENV = 'list_packages_installed_env'
15941596
VCS_REMOTE_LISTING = 'vcs_remote_listing'
@@ -1669,6 +1671,14 @@ def add_features(sender, **kwargs):
16691671
LIMIT_CONCURRENT_BUILDS,
16701672
_('Limit the amount of concurrent builds'),
16711673
),
1674+
(
1675+
DISABLE_SERVER_SIDE_SEARCH,
1676+
_('Disable server side search'),
1677+
),
1678+
(
1679+
ENABLE_MKDOCS_SERVER_SIDE_SEARCH,
1680+
_('Enable server side search for MkDocs projects'),
1681+
),
16721682
(
16731683
FORCE_SPHINX_FROM_VENV,
16741684
_('Force to use Sphinx from the current virtual environment'),

0 commit comments

Comments
 (0)