mirror of
https://github.com/squidfunk/mkdocs-material.git
synced 2024-06-14 11:52:32 +03:00
Merge pull request #425 from squidfunk/feat/search-separator
Added support for lazy rendering of search results
This commit is contained in:
commit
14f85c52de
@ -267,6 +267,10 @@ Furthermore, if `repo_url` points to a GitHub, BitBucket or GitLab repository,
|
|||||||
the respective service logo will be shown next to the name of the repository.
|
the respective service logo will be shown next to the name of the repository.
|
||||||
Additionally, for GitHub, the number of stars and forks is shown.
|
Additionally, for GitHub, the number of stars and forks is shown.
|
||||||
|
|
||||||
|
If the repository is hosted in a private environment, the service logo can be
|
||||||
|
set explicitly by setting `extra.repo_icon` to `github`, `gitlab` or
|
||||||
|
`bitbucket`.
|
||||||
|
|
||||||
!!! warning "Why is there an edit button at the top of every article?"
|
!!! warning "Why is there an edit button at the top of every article?"
|
||||||
|
|
||||||
If the `repo_url` is set to a GitHub or BitBucket repository, and the
|
If the `repo_url` is set to a GitHub or BitBucket repository, and the
|
||||||
@ -382,6 +386,7 @@ macro `t`:
|
|||||||
"search.result.none": "No matching documents",
|
"search.result.none": "No matching documents",
|
||||||
"search.result.one": "1 matching document",
|
"search.result.one": "1 matching document",
|
||||||
"search.result.other": "# matching documents",
|
"search.result.other": "# matching documents",
|
||||||
|
"search.tokenizer": "[\s\-]+",
|
||||||
"source.link.title": "Go to repository",
|
"source.link.title": "Go to repository",
|
||||||
"toc.title": "Table of contents"
|
"toc.title": "Table of contents"
|
||||||
}[key] }}{% endmacro %}
|
}[key] }}{% endmacro %}
|
||||||
@ -398,6 +403,8 @@ section on [overriding partials][18] and the general guide on
|
|||||||
|
|
||||||
#### Site search
|
#### Site search
|
||||||
|
|
||||||
|
##### Language
|
||||||
|
|
||||||
Site search is implemented using [lunr.js][21], which includes stemmers for the
|
Site search is implemented using [lunr.js][21], which includes stemmers for the
|
||||||
English language by default, while stemmers for other languages are included
|
English language by default, while stemmers for other languages are included
|
||||||
with [lunr-languages][22], both of which are integrated with this theme. Support
|
with [lunr-languages][22], both of which are integrated with this theme. Support
|
||||||
|
File diff suppressed because one or more lines are too long
@ -150,7 +150,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script src="{{ base_url }}/assets/javascripts/application-9d8d07445e.js"></script>
|
<script src="{{ base_url }}/assets/javascripts/application-1d6ee6ee6c.js"></script>
|
||||||
{% set languages = lang.t("search.languages").split(",") %}
|
{% set languages = lang.t("search.languages").split(",") %}
|
||||||
{% if languages | length and languages[0] != "" %}
|
{% if languages | length and languages[0] != "" %}
|
||||||
{% set path = base_url + "/assets/javascripts/lunr" %}
|
{% set path = base_url + "/assets/javascripts/lunr" %}
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
"search.result.none": "No matching documents",
|
"search.result.none": "No matching documents",
|
||||||
"search.result.one": "1 matching document",
|
"search.result.one": "1 matching document",
|
||||||
"search.result.other": "# matching documents",
|
"search.result.other": "# matching documents",
|
||||||
"search.tokenizer": "",
|
"search.tokenizer": "[\s\-]+",
|
||||||
"source.link.title": "Go to repository",
|
"source.link.title": "Go to repository",
|
||||||
"toc.title": "Table of contents"
|
"toc.title": "Table of contents"
|
||||||
}[key] }}{% endmacro %}
|
}[key] }}{% endmacro %}
|
||||||
|
@ -23,6 +23,30 @@
|
|||||||
import escape from "escape-string-regexp"
|
import escape from "escape-string-regexp"
|
||||||
import lunr from "expose-loader?lunr!lunr"
|
import lunr from "expose-loader?lunr!lunr"
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* Functions
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Truncate a string after the given number of character
|
||||||
|
*
|
||||||
|
* This is not a reasonable approach, since the summaries kind of suck. It
|
||||||
|
* would be better to create something more intelligent, highlighting the
|
||||||
|
* search occurrences and making a better summary out of it.
|
||||||
|
*
|
||||||
|
* @param {string} string - String to be truncated
|
||||||
|
* @param {number} n - Number of characters
|
||||||
|
* @return {string} Truncated string
|
||||||
|
*/
|
||||||
|
const truncate = (string, n) => {
|
||||||
|
let i = n
|
||||||
|
if (string.length > i) {
|
||||||
|
while (string[i] !== " " && --i > 0);
|
||||||
|
return `${string.substring(0, i)}...`
|
||||||
|
}
|
||||||
|
return string
|
||||||
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
* Class
|
* Class
|
||||||
* ------------------------------------------------------------------------- */
|
* ------------------------------------------------------------------------- */
|
||||||
@ -42,6 +66,7 @@ export default class Result {
|
|||||||
* @property {Array<string>} lang_ - Search languages
|
* @property {Array<string>} lang_ - Search languages
|
||||||
* @property {Object} message_ - Search result messages
|
* @property {Object} message_ - Search result messages
|
||||||
* @property {Object} index_ - Search index
|
* @property {Object} index_ - Search index
|
||||||
|
* @property {Array<Function>} stack_ - Search result stack
|
||||||
* @property {string} value_ - Last input value
|
* @property {string} value_ - Last input value
|
||||||
*
|
*
|
||||||
* @param {(string|HTMLElement)} el - Selector or HTML element
|
* @param {(string|HTMLElement)} el - Selector or HTML element
|
||||||
@ -81,26 +106,6 @@ export default class Result {
|
|||||||
.map(lang => lang.trim())
|
.map(lang => lang.trim())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Truncate a string after the given number of character
|
|
||||||
*
|
|
||||||
* This is not a reasonable approach, since the summaries kind of suck. It
|
|
||||||
* would be better to create something more intelligent, highlighting the
|
|
||||||
* search occurrences and making a better summary out of it.
|
|
||||||
*
|
|
||||||
* @param {string} string - String to be truncated
|
|
||||||
* @param {number} n - Number of characters
|
|
||||||
* @return {string} Truncated string
|
|
||||||
*/
|
|
||||||
truncate_(string, n) {
|
|
||||||
let i = n
|
|
||||||
if (string.length > i) {
|
|
||||||
while (string[i] !== " " && --i > 0);
|
|
||||||
return `${string.substring(0, i)}...`
|
|
||||||
}
|
|
||||||
return string
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update search results
|
* Update search results
|
||||||
*
|
*
|
||||||
@ -147,7 +152,8 @@ export default class Result {
|
|||||||
const docs = this.docs_,
|
const docs = this.docs_,
|
||||||
lang = this.lang_
|
lang = this.lang_
|
||||||
|
|
||||||
/* Create index */
|
/* Create stack and index */
|
||||||
|
this.stack_ = []
|
||||||
this.index_ = lunr(function() {
|
this.index_ = lunr(function() {
|
||||||
|
|
||||||
/* Remove stemmer, as it cripples search experience */
|
/* Remove stemmer, as it cripples search experience */
|
||||||
@ -157,7 +163,7 @@ export default class Result {
|
|||||||
lunr.stopWordFilter
|
lunr.stopWordFilter
|
||||||
)
|
)
|
||||||
|
|
||||||
/* Set up stemmers for search languages */
|
/* Set up alternate search languages */
|
||||||
if (lang.length === 1) {
|
if (lang.length === 1) {
|
||||||
this.use(lunr[lang[0]])
|
this.use(lunr[lang[0]])
|
||||||
} else if (lang.length > 1) {
|
} else if (lang.length > 1) {
|
||||||
@ -172,6 +178,16 @@ export default class Result {
|
|||||||
/* Index documents */
|
/* Index documents */
|
||||||
docs.forEach(doc => this.add(doc))
|
docs.forEach(doc => this.add(doc))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/* Register event handler for lazy rendering */
|
||||||
|
const container = this.el_.parentNode
|
||||||
|
if (!(container instanceof HTMLElement))
|
||||||
|
throw new ReferenceError
|
||||||
|
container.addEventListener("scroll", () => {
|
||||||
|
while (this.stack_.length && container.scrollTop +
|
||||||
|
container.offsetHeight >= container.scrollHeight - 16)
|
||||||
|
this.stack_.splice(0, 10).forEach(render => render())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
/* eslint-enable no-invalid-this */
|
/* eslint-enable no-invalid-this */
|
||||||
|
|
||||||
@ -208,7 +224,7 @@ export default class Result {
|
|||||||
|
|
||||||
/* Append trailing wildcard to all terms for prefix querying */
|
/* Append trailing wildcard to all terms for prefix querying */
|
||||||
.query(query => {
|
.query(query => {
|
||||||
this.value_.split(" ")
|
this.value_.toLowerCase().split(" ")
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.forEach(term => {
|
.forEach(term => {
|
||||||
query.term(term, { wildcard: lunr.Query.wildcard.TRAILING })
|
query.term(term, { wildcard: lunr.Query.wildcard.TRAILING })
|
||||||
@ -236,12 +252,13 @@ export default class Result {
|
|||||||
const highlight = (_, separator, token) =>
|
const highlight = (_, separator, token) =>
|
||||||
`${separator}<em>${token}</em>`
|
`${separator}<em>${token}</em>`
|
||||||
|
|
||||||
/* Render results */
|
/* Reset stack and render results */
|
||||||
|
this.stack_ = []
|
||||||
result.forEach((items, ref) => {
|
result.forEach((items, ref) => {
|
||||||
const doc = this.docs_.get(ref)
|
const doc = this.docs_.get(ref)
|
||||||
|
|
||||||
/* Append search result */
|
/* Render article */
|
||||||
this.list_.appendChild(
|
const article = (
|
||||||
<li class="md-search-result__item">
|
<li class="md-search-result__item">
|
||||||
<a href={doc.location} title={doc.title}
|
<a href={doc.location} title={doc.title}
|
||||||
class="md-search-result__link">
|
class="md-search-result__link">
|
||||||
@ -256,29 +273,44 @@ export default class Result {
|
|||||||
</p> : {}}
|
</p> : {}}
|
||||||
</article>
|
</article>
|
||||||
</a>
|
</a>
|
||||||
{items.map(item => {
|
|
||||||
const section = this.docs_.get(item.ref)
|
|
||||||
return (
|
|
||||||
<a href={section.location} title={section.title}
|
|
||||||
class="md-search-result__link" data-md-rel="anchor">
|
|
||||||
<article class="md-search-result__article">
|
|
||||||
<h1 class="md-search-result__title">
|
|
||||||
{{ __html: section.title.replace(match, highlight) }}
|
|
||||||
</h1>
|
|
||||||
{section.text.length ?
|
|
||||||
<p class="md-search-result__teaser">
|
|
||||||
{{ __html: this.truncate_(
|
|
||||||
section.text.replace(match, highlight), 400)
|
|
||||||
}}
|
|
||||||
</p> : {}}
|
|
||||||
</article>
|
|
||||||
</a>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/* Render sections for article */
|
||||||
|
const sections = items.map(item => {
|
||||||
|
return () => {
|
||||||
|
const section = this.docs_.get(item.ref)
|
||||||
|
article.appendChild(
|
||||||
|
<a href={section.location} title={section.title}
|
||||||
|
class="md-search-result__link" data-md-rel="anchor">
|
||||||
|
<article class="md-search-result__article">
|
||||||
|
<h1 class="md-search-result__title">
|
||||||
|
{{ __html: section.title.replace(match, highlight) }}
|
||||||
|
</h1>
|
||||||
|
{section.text.length ?
|
||||||
|
<p class="md-search-result__teaser">
|
||||||
|
{{ __html: truncate(
|
||||||
|
section.text.replace(match, highlight), 400)
|
||||||
|
}}
|
||||||
|
</p> : {}}
|
||||||
|
</article>
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/* Push articles and section renderers onto stack */
|
||||||
|
this.stack_.push(() => this.list_.appendChild(article), ...sections)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/* Gradually add results as long as the height of the container grows */
|
||||||
|
const container = this.el_.parentNode
|
||||||
|
if (!(container instanceof HTMLElement))
|
||||||
|
throw new ReferenceError
|
||||||
|
while (this.stack_.length &&
|
||||||
|
container.offsetHeight >= container.scrollHeight - 16)
|
||||||
|
(this.stack_.shift())()
|
||||||
|
|
||||||
/* Bind click handlers for anchors */
|
/* Bind click handlers for anchors */
|
||||||
const anchors = this.list_.querySelectorAll("[data-md-rel=anchor]")
|
const anchors = this.list_.querySelectorAll("[data-md-rel=anchor]")
|
||||||
Array.prototype.forEach.call(anchors, anchor => {
|
Array.prototype.forEach.call(anchors, anchor => {
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
"search.result.none": "No matching documents",
|
"search.result.none": "No matching documents",
|
||||||
"search.result.one": "1 matching document",
|
"search.result.one": "1 matching document",
|
||||||
"search.result.other": "# matching documents",
|
"search.result.other": "# matching documents",
|
||||||
"search.tokenizer": "",
|
"search.tokenizer": "[\s\-]+",
|
||||||
"source.link.title": "Go to repository",
|
"source.link.title": "Go to repository",
|
||||||
"toc.title": "Table of contents"
|
"toc.title": "Table of contents"
|
||||||
}[key] }}{% endmacro %}
|
}[key] }}{% endmacro %}
|
||||||
|
Loading…
Reference in New Issue
Block a user