Skip to content

Commit

Permalink
Merge pull request #6986 from readthedocs/mkdocs-indocsearch
Browse files Browse the repository at this point in the history
Allow to enable server side search for MkDocs
  • Loading branch information
stsewd committed Jun 4, 2020
2 parents 6b32b39 + 07730a1 commit a6b60be
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 6 deletions.
180 changes: 175 additions & 5 deletions readthedocs/core/static-src/core/js/doc-embed/search.js
@@ -1,5 +1,5 @@
/*
* Sphinx search overrides
* Sphinx and Mkdocs search overrides
*/

var rtddata = require('./rtd-data');
Expand Down Expand Up @@ -40,7 +40,7 @@ function append_html_to_contents(contents, template, data) {
* Sphinx indexer. This will fall back to the standard indexer on an API
* failure,
*/
function attach_elastic_search_query(data) {
function attach_elastic_search_query_sphinx(data) {
var project = data.project;
var version = data.version;
var language = data.language || 'en';
Expand All @@ -56,7 +56,6 @@ function attach_elastic_search_query(data) {
search_def
.then(function (data) {
var hit_list = data.results || [];
var total_count = data.count || 0;

if (hit_list.length) {
for (var i = 0; i < hit_list.length; i += 1) {
Expand Down Expand Up @@ -265,12 +264,13 @@ function attach_elastic_search_query(data) {
};

if (typeof Search !== 'undefined' && project && version) {

// Do not replace the built-in search if RTD's docsearch is disabled
if (!data.features || !data.features.docsearch_disabled) {
var query_fallback = Search.query;
Search.query_fallback = query_fallback;
Search.query = query_override;
} else {
console.log('Server side search is disabled.');
}
}
$(document).ready(function () {
Expand All @@ -281,9 +281,179 @@ function attach_elastic_search_query(data) {
}


/*
* Mkdocs search override for hitting our API instead of the standard Mkdocs search index.
* This will fall back to the original search on an API failure.
*/
function attach_elastic_search_query_mkdocs(data) {
var project = data.project;
var version = data.version;
var language = data.language || 'en';

var fallbackSearch = function () {
if (typeof window.doSearchFallback !== 'undefined') {
window.doSearchFallback();
} else {
console.log('Unable to fallback to original MkDocs search.');
}
};

var doSearch = function () {
var query = document.getElementById('mkdocs-search-query').value;

var search_def = $.Deferred();

var search_url = document.createElement('a');
search_url.href = data.proxied_api_host + '/api/v2/docsearch/';
search_url.search = '?q=' + encodeURIComponent(query) + '&project=' + project +
'&version=' + version + '&language=' + language;

search_def
.then(function (data) {
var hit_list = data.results || [];

if (hit_list.length) {
var searchResults = $('#mkdocs-search-results');
searchResults.empty();

for (var i = 0; i < hit_list.length; i += 1) {
var doc = hit_list[i];
var inner_hits = doc.inner_hits || [];

var result = $('<article>');
result.append(
$('<h3>').append($('<a>', {'href': doc.link, 'text': doc.title}))
);

if (doc.project !== project) {
var text = '(from project ' + doc.project + ')';
result.append($('<span>', {'text': text}));
}

for (var j = 0; j < inner_hits.length; j += 1) {
var section = inner_hits[j];

if (section.type === 'sections') {
var section_link = doc.link + '#' + section._source.id;
var section_title = section._source.title;
var section_content = section._source.content;
if (section_content.length > MAX_SUBSTRING_LIMIT) {
section_content = section_content.substr(0, MAX_SUBSTRING_LIMIT) + " ...";
}
var section_contents = [section_content];

if (section.highlight) {
if (section.highlight["sections.title"]) {
section_title = section.highlight["sections.title"][0];
}
if (section.highlight["sections.content"]) {
var contents = section.highlight["sections.content"];
section_contents = [];
for (
var k = 0;
k < contents.length && k < MAX_RESULT_PER_SECTION;
k += 1
) {
section_contents.push("... " + contents[k] + " ...");
}
}
}

section_title = xss(section_title)
.replace(/<span>/g, '<mark>')
.replace(/<\/span>/g, '</mark>');
result.append(
$('<h4>')
.append($('<a>', {'href': section_link}).html(section_title))
);
for (var m = 0; m < section_contents.length; m += 1) {
var content = xss(section_contents[m]);
content = content
.replace(/<span>/g, '<mark>')
.replace(/<\/span>/g, '</mark>');
result.append(
$('<p>').html(content)
);
}
searchResults.append(result);
}
}
}
} else {
console.log('Read the Docs search returned 0 result. Falling back to MkDocs search.');
fallbackSearch();
}
})
.fail(function (error) {
console.log('Read the Docs search failed. Falling back to MkDocs search.');
fallbackSearch();
});

$.ajax({
url: search_url.href,
crossDomain: true,
xhrFields: {
withCredentials: true,
},
complete: function (resp, status_code) {
if (
status_code !== 'success' ||
typeof (resp.responseJSON) === 'undefined' ||
resp.responseJSON.count === 0
) {
return search_def.reject();
}
return search_def.resolve(resp.responseJSON);
}
})
.fail(function (resp, status_code, error) {
return search_def.reject();
});
};

var initSearch = function () {
var search_input = document.getElementById('mkdocs-search-query');
if (search_input) {
search_input.addEventListener('keyup', doSearch);
}

var term = window.getSearchTermFromLocation();
if (term) {
search_input.value = term;
doSearch();
}
};

$(document).ready(function () {
// We can't override the search completely,
// because we can't delete the original event listener,
// and MkDocs includes its search functions after ours.
// If MkDocs is loaded before, this will trigger a double search
// (but ours will have precendece).

// Note: this function is only available on Mkdocs >=1.x
window.doSearchFallback = window.doSearch;

window.doSearch = doSearch;
window.initSearch = initSearch;
initSearch();
});
}


function init() {
var data = rtddata.get();
attach_elastic_search_query(data);
if (data.is_sphinx_builder()) {
// Check to disabled server side search for Sphinx
// happens inside the function, because we still need to call Search.init().
attach_elastic_search_query_sphinx(data);
}
// MkDocs projects should have this flag explicitly for now.
else if (data.features && !data.features.docsearch_disabled) {
attach_elastic_search_query_mkdocs(data);
} else {
console.log('Server side search is disabled.');
}
}

module.exports = {
Expand Down
2 changes: 1 addition & 1 deletion readthedocs/core/static/core/js/readthedocs-doc-embed.js

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions readthedocs/doc_builder/backends/mkdocs.py
Expand Up @@ -253,6 +253,12 @@ def generate_rtd_data(self, docs_dir, mkdocs_config):
'commit': self.version.project.vcs_repo(self.version.slug).commit,
'global_analytics_code': settings.GLOBAL_ANALYTICS_CODE,
'user_analytics_code': analytics_code,
'features': {
'docsearch_disabled': (
not self.project.has_feature(Feature.ENABLE_MKDOCS_SERVER_SIDE_SEARCH)
or self.project.has_feature(Feature.DISABLE_SERVER_SIDE_SEARCH)
)
},
}

data_ctx = {
Expand Down
1 change: 1 addition & 0 deletions readthedocs/doc_builder/backends/sphinx.py
Expand Up @@ -181,6 +181,7 @@ def get_config_params(self):
'dont_overwrite_sphinx_context': self.project.has_feature(
Feature.DONT_OVERWRITE_SPHINX_CONTEXT,
),
'docsearch_disabled': self.project.has_feature(Feature.DISABLE_SERVER_SIDE_SEARCH),
}

finalize_sphinx_context_data.send(
Expand Down
1 change: 1 addition & 0 deletions readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl
Expand Up @@ -114,6 +114,7 @@ context = {
'new_theme': (html_theme == "sphinx_rtd_theme"),
'source_suffix': SUFFIX,
'ad_free': {% if project.show_advertising %}False{% else %}True{% endif %},
'docsearch_disabled': {{ docsearch_disabled }},
'user_analytics_code': '{{ project.analytics_code|default_if_none:'' }}',
'global_analytics_code': '{{ settings.GLOBAL_ANALYTICS_CODE }}',
'commit': {% if project.repo_type == 'git' %}'{{ commit|slice:"8" }}'{% else %}'{{ commit }}'{% endif %},
Expand Down
10 changes: 10 additions & 0 deletions readthedocs/projects/models.py
Expand Up @@ -1589,6 +1589,8 @@ def add_features(sender, **kwargs):
SKIP_SYNC_BRANCHES = 'skip_sync_branches'
CACHED_ENVIRONMENT = 'cached_environment'
LIMIT_CONCURRENT_BUILDS = 'limit_concurrent_builds'
DISABLE_SERVER_SIDE_SEARCH = 'disable_server_side_search'
ENABLE_MKDOCS_SERVER_SIDE_SEARCH = 'enable_mkdocs_server_side_search'
FORCE_SPHINX_FROM_VENV = 'force_sphinx_from_venv'
LIST_PACKAGES_INSTALLED_ENV = 'list_packages_installed_env'
VCS_REMOTE_LISTING = 'vcs_remote_listing'
Expand Down Expand Up @@ -1669,6 +1671,14 @@ def add_features(sender, **kwargs):
LIMIT_CONCURRENT_BUILDS,
_('Limit the amount of concurrent builds'),
),
(
DISABLE_SERVER_SIDE_SEARCH,
_('Disable server side search'),
),
(
ENABLE_MKDOCS_SERVER_SIDE_SEARCH,
_('Enable server side search for MkDocs projects'),
),
(
FORCE_SPHINX_FROM_VENV,
_('Force to use Sphinx from the current virtual environment'),
Expand Down

0 comments on commit a6b60be

Please sign in to comment.