Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

* Documenter now generates a `.documenter-siteinfo.json` file in the HTML build, that contains some metadata about the build. ([#2181])

* The client-side search engine has been changed from LunrJs to MinisearchJs. Additionally, the search results will now contain additional context and have an improved UI. ([#2141])

### Fixed

* Line endings in Markdown source files are now normalized to `LF` before parsing, to work around [a bug in the Julia Markdown parser][julia-29344] where parsing is sensitive to line endings, and can therefore cause platform-dependent behavior. ([#1906])
Expand Down Expand Up @@ -1603,6 +1605,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#2128]: https://github.com/JuliaDocs/Documenter.jl/issues/2128
[#2130]: https://github.com/JuliaDocs/Documenter.jl/issues/2130
[#2134]: https://github.com/JuliaDocs/Documenter.jl/issues/2134
[#2141]: https://github.com/JuliaDocs/Documenter.jl/issues/2141
[#2145]: https://github.com/JuliaDocs/Documenter.jl/issues/2145
[#2153]: https://github.com/JuliaDocs/Documenter.jl/issues/2153
[#2157]: https://github.com/JuliaDocs/Documenter.jl/issues/2157
Expand Down
4 changes: 4 additions & 0 deletions assets/html/scss/documenter-dark.scss
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ $input-background-color: $body-background-color;
$input-border-color: $border;
$input-placeholder-color: rgba($input-color, 0.3);

$search-result-link-text-color: #333;
$search-result-link-text-background-color: #f1f5f9;
$search-result-title-text-color: whitesmoke;

$button-static-color: $grey-lighter;
$button-static-background-color: $background;
$button-static-border-color: $border;
Expand Down
2 changes: 1 addition & 1 deletion assets/html/scss/documenter-light.scss
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@ code.language-julia-repl > span.hljs-meta {

// Workaround to compile in highlightjs theme, so that we could have different
// themes for both
@import "highlightjs/default"
@import "highlightjs/default";
10 changes: 10 additions & 0 deletions assets/html/scss/documenter/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,13 @@ $documenter-docstring-header-padding: 0.5rem $documenter-container-left-padding;
$documenter-docstring-body-padding-h: $documenter-container-left-padding;
$documenter-docstring-body-padding-v: 0.75rem;
$documenter-docstring-body-padding: $documenter-docstring-body-padding-v $documenter-docstring-body-padding-h;

// Search Results variables
$search-result-link-hover: rgba(0, 128, 128, 0.1) !default;
$search-result-link-text-color: #f1f5f9 !default;
$search-result-link-text-background-color: #333 !default;
$search-result-title-text-color: #333 !default;
$search-result-badge-color: whitesmoke !default;
$search-result-badge-background-color: #33415580 !default;

$search-result-highlight: hsl(48, 100%, 67%) !default;
55 changes: 53 additions & 2 deletions assets/html/scss/documenter/layout/_search.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,59 @@
li {
margin-left: 2rem;
}
.docs-highlight {
background-color: yellow;

.search-result-link {
border-radius: 0.7em;
transition: all 300ms;
}

.search-result-link:hover, .search-result-link:focus {
background-color: $search-result-link-hover;
}

.search-result-link .property-search-result-badge {
transition: all 300ms;
}

.property-search-result-badge {
padding: 0.15em 0.5em;
font-size: 0.8em;
font-style: italic;
text-transform: none !important;
line-height: 1.5;
color: $search-result-badge-color;
background-color: $search-result-badge-background-color;
border-radius: 0.6rem;
}

.search-result-link:hover .property-search-result-badge, .search-result-link:focus .property-search-result-badge {
color: $search-result-link-text-color;
background-color: $search-result-link-text-background-color;
}

.search-result-highlight {
background-color: $search-result-highlight;
color: black;
}

.search-divider {
border-bottom: 1px solid $border;
}

.search-result-title {
color: $search-result-title-text-color;
}

.w-100 {
width: 100%;
}

.gap-2 {
gap: 0.5rem;
}

.gap-4 {
gap: 1rem;
}
}
}
241 changes: 141 additions & 100 deletions assets/html/search.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// libraries: jquery, lunr, lodash
// arguments: $, lunr, _
// libraries: jquery, minisearch, lodash
// arguments: $, minisearch, _

$(document).ready(function () {
$(function () {
// parseUri 1.2.2
// (c) Steven Levithan <stevenlevithan.com>
// MIT License
Expand Down Expand Up @@ -54,10 +54,15 @@ $(document).ready(function () {
e.preventDefault();
});

let ms_data = documenterSearchIndex["docs"].map((x, key) => {
x["id"] = key;
return x;
});

// list below is the lunr 2.1.3 list minus the intersect with names(Base)
// (all, any, get, in, is, only, which) and (do, else, for, let, where, while, with)
// ideally we'd just filter the original list but it's not available as a variable
lunr.stopWordFilter = lunr.generateStopWordFilter([
const stopWords = new Set([
"a",
"able",
"about",
Expand Down Expand Up @@ -165,114 +170,150 @@ $(document).ready(function () {
"your",
]);

// add . as a separator, because otherwise "title": "Documenter.Anchors.add!"
// would not find anything if searching for "add!", only for the entire qualification
lunr.tokenizer.separator = /[\s\-\.]+/;

// custom trimmer that doesn't strip @ and !, which are used in julia macro and function names
lunr.trimmer = function (token) {
return token.update(function (s) {
return s.replace(/^[^a-zA-Z0-9@!]+/, "").replace(/[^a-zA-Z0-9@!]+$/, "");
});
};
let index = new minisearch({
fields: ["title", "text"], // fields to index for full-text search
storeFields: ["location", "title", "text", "category", "page"], // fields to return with search results
processTerm: (term) => {
let word = stopWords.has(term) ? null : term;
if (word) {
// custom trimmer that doesn't strip @ and !, which are used in julia macro and function names
word = word
.replace(/^[^a-zA-Z0-9@!]+/, "")
.replace(/[^a-zA-Z0-9@!]+$/, "");
}

lunr.Pipeline.registerFunction(lunr.stopWordFilter, "juliaStopWordFilter");
lunr.Pipeline.registerFunction(lunr.trimmer, "juliaTrimmer");
return word ?? null;
},
// add . as a separator, because otherwise "title": "Documenter.Anchors.add!", would not find anything if searching for "add!", only for the entire qualification
tokenize: (string) => string.split(/[\s\-\.]+/),
searchOptions: {
boost: { title: 100 },
fuzzy: 2,
processTerm: (term) => {
let word = stopWords.has(term) ? null : term;
if (word) {
word = word
.replace(/^[^a-zA-Z0-9@!]+/, "")
.replace(/[^a-zA-Z0-9@!]+$/, "");
}

var index = lunr(function () {
this.ref("location");
this.field("title", { boost: 100 });
this.field("text");
documenterSearchIndex["docs"].forEach(function (e) {
this.add(e);
}, this);
return word ?? null;
},
tokenize: (string) => string.split(/[\s\-\.]+/),
},
});
var store = {};

documenterSearchIndex["docs"].forEach(function (e) {
store[e.location] = { title: e.title, category: e.category, page: e.page };
});
index.addAll(ms_data);

$(function () {
searchresults = $("#documenter-search-results");
searchinfo = $("#documenter-search-info");
searchbox = $("#documenter-search-query");
searchform = $(".docs-search");
sidebar = $(".docs-sidebar");
function update_search(querystring) {
tokens = lunr.tokenizer(querystring);
results = index.query(function (q) {
tokens.forEach(function (t) {
q.term(t.toString(), {
fields: ["title"],
boost: 100,
usePipeline: true,
editDistance: 0,
wildcard: lunr.Query.wildcard.NONE,
});
q.term(t.toString(), {
fields: ["title"],
boost: 10,
usePipeline: true,
editDistance: 2,
wildcard: lunr.Query.wildcard.NONE,
});
q.term(t.toString(), {
fields: ["text"],
boost: 1,
usePipeline: true,
editDistance: 0,
wildcard: lunr.Query.wildcard.NONE,
});
});
});
searchinfo.text("Number of results: " + results.length);
searchresults.empty();
results.forEach(function (result) {
data = store[result.ref];
link = $('<a class="docs-label">' + data.title + "</a>");
link.attr("href", documenterBaseURL + "/" + result.ref);
if (data.category != "page") {
cat = $(
'<span class="docs-category">(' +
data.category +
", " +
data.page +
")</span>"
);
} else {
cat = $('<span class="docs-category">(' + data.category + ")</span>");
}
li = $("<li>").append(link).append(" ").append(cat);
searchresults.append(li);
});
}
searchresults = $("#documenter-search-results");
searchinfo = $("#documenter-search-info");
searchbox = $("#documenter-search-query");
searchform = $(".docs-search");
sidebar = $(".docs-sidebar");

function update_search_box() {
querystring = searchbox.val();
update_search(querystring);
}
function update_search(querystring) {
let results = [];
results = index.search(querystring, {
filter: (result) => result.score >= 1,
});

searchbox.keyup(_.debounce(update_search_box, 250));
searchbox.change(update_search_box);
searchresults.empty();

// Disable enter-key form submission for the searchbox on the search page
// and just re-run search rather than refresh the whole page.
searchform.keypress(function (event) {
if (event.which == "13") {
if (sidebar.hasClass("visible")) {
sidebar.removeClass("visible");
let links = [];
let count = 0;

results.forEach(function (result) {
if (result.location) {
if (!links.includes(result.location)) {
searchresults.append(make_search_result(result, querystring));
count++;
}
update_search_box();
event.preventDefault();

links.push(result.location);
}
});

search_query_uri = parseUri(window.location).queryKey["q"];
if (search_query_uri !== undefined) {
search_query = decodeURIComponent(search_query_uri.replace(/\+/g, "%20"));
searchbox.val(search_query);
searchinfo.text("Number of results: " + count);
}

function make_search_result(result, querystring) {
let display_link =
result.location.slice(Math.max(0), Math.min(50, result.location.length)) +
(result.location.length > 30 ? "..." : "");

let textindex = new RegExp(`\\b${querystring}\\b`, "i").exec(result.text);
let text =
textindex !== null
? result.text.slice(
Math.max(textindex.index - 100, 0),
Math.min(
textindex.index + querystring.length + 100,
result.text.length
)
)
: "";

let display_result = text.length
? "..." +
text.replace(
new RegExp(`\\b${querystring}\\b`, "i"), // For first occurrence
'<span class="search-result-highlight p-1">$&</span>'
) +
"..."
: "";

let result_div = `
<a href="${
documenterBaseURL + "/" + result.location
}" class="search-result-link px-4 py-2 w-100 is-flex is-flex-direction-column gap-2 my-4">
<div class="w-100 is-flex is-flex-wrap-wrap is-justify-content-space-between is-align-items-center">
<div class="search-result-title has-text-weight-semi-bold">${
result.title
}</div>
<div class="property-search-result-badge">${result.category}</div>
</div>
<p>
${display_result}
</p>
<div
class="has-text-left"
style="font-size: smaller;"
title="${result.location}"
>
<i class="fas fa-link"></i> ${display_link}
</div>
</a>
<div class="search-divider"></div>
`;
return result_div;
}

function update_search_box() {
querystring = searchbox.val();
update_search(querystring);
}

searchbox.keyup(_.debounce(update_search_box, 250));
searchbox.change(update_search_box);

// Disable enter-key form submission for the searchbox on the search page
// and just re-run search rather than refresh the whole page.
searchform.keypress(function (event) {
if (event.which == "13") {
if (sidebar.hasClass("visible")) {
sidebar.removeClass("visible");
}
update_search_box();
event.preventDefault();
}
update_search_box();
});

search_query_uri = parseUri(window.location).queryKey["q"];

if (search_query_uri !== undefined) {
search_query = decodeURIComponent(search_query_uri.replace(/\+/g, "%20"));
searchbox.val(search_query);
}

update_search_box();
});
2 changes: 1 addition & 1 deletion assets/html/themes/documenter-dark.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion assets/html/themes/documenter-light.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/html/HTMLWriter.jl
Original file line number Diff line number Diff line change
Expand Up @@ -686,7 +686,7 @@ function render(doc::Documenter.Document, settings::HTML=HTML())
if isfile(joinpath(doc.user.source, "assets", "search.js"))
@warn "not creating 'search.js', provided by the user."
else
r = JSDependencies.RequireJS([RD.jquery, RD.lunr, RD.lodash])
r = JSDependencies.RequireJS([RD.jquery, RD.minisearch, RD.lodash])
push!(r, JSDependencies.parse_snippet(joinpath(ASSETS, "search.js")))
JSDependencies.verify(r; verbose=true) || error("RequireJS declaration is invalid")
JSDependencies.writejs(joinpath(doc.user.build, "assets", "search.js"), r)
Expand Down
Loading