diff options
author | John Olheiser <42128690+jolheiser@users.noreply.github.com> | 2019-11-13 12:03:18 -0600 |
---|---|---|
committer | zeripath <art27@cantab.net> | 2019-11-13 18:03:18 +0000 |
commit | 3b0303a4fcbb8b8fda258e11edb3fd3c5c09396e (patch) | |
tree | 40bb579e03bc20b52fb2f6b82d078e325e0dd378 /docs | |
parent | afe50873a5d6b52177b0cd6bc9d2657faf82f311 (diff) | |
download | gitea-3b0303a4fcbb8b8fda258e11edb3fd3c5c09396e.tar.gz gitea-3b0303a4fcbb8b8fda258e11edb3fd3c5c09396e.zip |
Implement documentation search (#8937)
* Implement documentation search
Signed-off-by: jolheiser <john.olheiser@gmail.com>
Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com>
Diffstat (limited to 'docs')
-rw-r--r-- | docs/.gitignore | 1 | ||||
-rw-r--r-- | docs/assets/js/search.js | 176 | ||||
-rw-r--r-- | docs/config.yaml | 6 | ||||
-rw-r--r-- | docs/content/doc/help.en-us.md | 4 | ||||
-rw-r--r-- | docs/content/doc/help.fr-fr.md | 13 | ||||
-rw-r--r-- | docs/content/doc/help.zh-cn.md | 4 | ||||
-rw-r--r-- | docs/content/doc/help.zh-tw.md | 13 | ||||
-rw-r--r-- | docs/content/doc/help/search.en-us.md | 25 | ||||
-rw-r--r-- | docs/content/doc/help/search.fr-fr.md | 25 | ||||
-rw-r--r-- | docs/content/doc/help/search.zh-cn.md | 25 | ||||
-rw-r--r-- | docs/content/doc/help/search.zh-tw.md | 25 | ||||
-rw-r--r-- | docs/layouts/_default/index.json | 5 | ||||
-rw-r--r-- | docs/layouts/doc/search.html | 44 |
13 files changed, 362 insertions, 4 deletions
diff --git a/docs/.gitignore b/docs/.gitignore index 55ec469a42..9cd1408bd2 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1,3 +1,4 @@ public/ templates/swagger/v1_json.tmpl themes/ +resources/ diff --git a/docs/assets/js/search.js b/docs/assets/js/search.js new file mode 100644 index 0000000000..72d94c9ee2 --- /dev/null +++ b/docs/assets/js/search.js @@ -0,0 +1,176 @@ +function ready(fn) { + if (document.readyState != 'loading') { + fn(); + } else { + document.addEventListener('DOMContentLoaded', fn); + } +} + +ready(doSearch); + +const summaryInclude = 60; +const fuseOptions = { + shouldSort: true, + includeMatches: true, + matchAllTokens: true, + threshold: 0.0, // for parsing diacritics + tokenize: true, + location: 0, + distance: 100, + maxPatternLength: 32, + minMatchCharLength: 1, + keys: [{ + name: "title", + weight: 0.8 + }, + { + name: "contents", + weight: 0.5 + }, + { + name: "tags", + weight: 0.3 + }, + { + name: "categories", + weight: 0.3 + } + ] +}; + +function param(name) { + return decodeURIComponent((location.search.split(name + '=')[1] || '').split('&')[0]).replace(/\+/g, ' '); +} + +let searchQuery = param("s"); + +function doSearch() { + if (searchQuery) { + document.getElementById("search-query").value = searchQuery; + executeSearch(searchQuery); + } else { + const para = document.createElement("P"); + para.innerText = "Please enter a word or phrase above"; + document.getElementById("search-results").appendChild(para); + } +} + +function getJSON(url, fn) { + const request = new XMLHttpRequest(); + request.open('GET', url, true); + request.onload = function () { + if (request.status >= 200 && request.status < 400) { + const data = JSON.parse(request.responseText); + fn(data); + } else { + console.log("Target reached on " + url + " with error " + request.status); + } + }; + request.onerror = function () { + console.log("Connection error " + request.status); + }; + request.send(); +} + +function executeSearch(searchQuery) { + getJSON("/" + document.LANG + "/index.json", function (data) { + const pages = data; + const fuse = new Fuse(pages, fuseOptions); + const result = fuse.search(searchQuery); + console.log({ + "matches": result + }); + document.getElementById("search-results").innerHTML = ""; + if (result.length > 0) { + populateResults(result); + } else { + const para = document.createElement("P"); + para.innerText = "No matches found"; + document.getElementById("search-results").appendChild(para); + } + }); +} + +function populateResults(result) { + result.forEach(function (value, key) { + const content = value.item.contents; + let snippet = ""; + const snippetHighlights = []; + if (fuseOptions.tokenize) { + snippetHighlights.push(searchQuery); + value.matches.forEach(function (mvalue) { + if (mvalue.key === "tags" || mvalue.key === "categories") { + snippetHighlights.push(mvalue.value); + } else if (mvalue.key === "contents") { + const ind = content.toLowerCase().indexOf(searchQuery.toLowerCase()); + const start = ind - summaryInclude > 0 ? ind - summaryInclude : 0; + const end = ind + searchQuery.length + summaryInclude < content.length ? ind + searchQuery.length + summaryInclude : content.length; + snippet += content.substring(start, end); + if (ind > -1) { + snippetHighlights.push(content.substring(ind, ind + searchQuery.length)) + } else { + snippetHighlights.push(mvalue.value.substring(mvalue.indices[0][0], mvalue.indices[0][1] - mvalue.indices[0][0] + 1)); + } + } + }); + } + + if (snippet.length < 1) { + snippet += content.substring(0, summaryInclude * 2); + } + //pull template from hugo templarte definition + const templateDefinition = document.getElementById("search-result-template").innerHTML; + //replace values + const output = render(templateDefinition, { + key: key, + title: value.item.title, + link: value.item.permalink, + tags: value.item.tags, + categories: value.item.categories, + snippet: snippet + }); + document.getElementById("search-results").appendChild(htmlToElement(output)); + + snippetHighlights.forEach(function (snipvalue) { + new Mark(document.getElementById("summary-" + key)).mark(snipvalue); + }); + + }); +} + +function render(templateString, data) { + let conditionalMatches, copy; + const conditionalPattern = /\$\{\s*isset ([a-zA-Z]*) \s*\}(.*)\$\{\s*end\s*}/g; + //since loop below depends on re.lastInxdex, we use a copy to capture any manipulations whilst inside the loop + copy = templateString; + while ((conditionalMatches = conditionalPattern.exec(templateString)) !== null) { + if (data[conditionalMatches[1]]) { + //valid key, remove conditionals, leave content. + copy = copy.replace(conditionalMatches[0], conditionalMatches[2]); + } else { + //not valid, remove entire section + copy = copy.replace(conditionalMatches[0], ''); + } + } + templateString = copy; + //now any conditionals removed we can do simple substitution + let key, find, re; + for (key in data) { + find = '\\$\\{\\s*' + key + '\\s*\\}'; + re = new RegExp(find, 'g'); + templateString = templateString.replace(re, data[key]); + } + return templateString; +} + +/** + * By Mark Amery: https://stackoverflow.com/a/35385518 + * @param {String} HTML representing a single element + * @return {Element} + */ +function htmlToElement(html) { + const template = document.createElement('template'); + html = html.trim(); // Never return a text node of whitespace as the result + template.innerHTML = html; + return template.content.firstChild; +} diff --git a/docs/config.yaml b/docs/config.yaml index 23d1257337..039e2938fb 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -20,6 +20,12 @@ params: website: https://docs.gitea.io version: 1.9.5 +outputs: + home: + - HTML + - RSS + - JSON + menu: page: - name: Website diff --git a/docs/content/doc/help.en-us.md b/docs/content/doc/help.en-us.md index 5ad1dd7f1e..635cb8931e 100644 --- a/docs/content/doc/help.en-us.md +++ b/docs/content/doc/help.en-us.md @@ -2,12 +2,12 @@ date: "2017-01-20T15:00:00+08:00" title: "Help" slug: "help" -weight: 50 +weight: 5 toc: false draft: false menu: sidebar: name: "Help" - weight: 50 + weight: 5 identifier: "help" --- diff --git a/docs/content/doc/help.fr-fr.md b/docs/content/doc/help.fr-fr.md new file mode 100644 index 0000000000..ab0cedccfc --- /dev/null +++ b/docs/content/doc/help.fr-fr.md @@ -0,0 +1,13 @@ +--- +date: "2017-01-20T15:00:00+08:00" +title: "Aide" +slug: "help" +weight: 5 +toc: false +draft: false +menu: + sidebar: + name: "Aide" + weight: 5 + identifier: "help" +--- diff --git a/docs/content/doc/help.zh-cn.md b/docs/content/doc/help.zh-cn.md index 6af7aa1719..9465cd5464 100644 --- a/docs/content/doc/help.zh-cn.md +++ b/docs/content/doc/help.zh-cn.md @@ -2,12 +2,12 @@ date: "2017-01-20T15:00:00+08:00" title: "帮助" slug: "help" -weight: 50 +weight: 5 toc: false draft: false menu: sidebar: name: "帮助" - weight: 50 + weight: 5 identifier: "help" --- diff --git a/docs/content/doc/help.zh-tw.md b/docs/content/doc/help.zh-tw.md new file mode 100644 index 0000000000..c9cd794d81 --- /dev/null +++ b/docs/content/doc/help.zh-tw.md @@ -0,0 +1,13 @@ +--- +date: "2017-01-20T15:00:00+08:00" +title: "救命" +slug: "help" +weight: 5 +toc: false +draft: false +menu: + sidebar: + name: "救命" + weight: 5 + identifier: "help" +--- diff --git a/docs/content/doc/help/search.en-us.md b/docs/content/doc/help/search.en-us.md new file mode 100644 index 0000000000..93c154bde2 --- /dev/null +++ b/docs/content/doc/help/search.en-us.md @@ -0,0 +1,25 @@ +--- +date: "2019-11-12T16:00:00+02:00" +title: "Search" +slug: "search" +weight: 4 +toc: true +draft: false +menu: + sidebar: + parent: "help" + name: "Search" + weight: 4 + identifier: "search" +sitemap: + priority : 0.1 +layout: "search" +--- + + +This file exists solely to respond to /search URL with the related `search` layout template. + +No content shown here is rendered, all content is based in the template layouts/doc/search.html + +Setting a very low sitemap priority will tell search engines this is not important content. + diff --git a/docs/content/doc/help/search.fr-fr.md b/docs/content/doc/help/search.fr-fr.md new file mode 100644 index 0000000000..3507e9efe8 --- /dev/null +++ b/docs/content/doc/help/search.fr-fr.md @@ -0,0 +1,25 @@ +--- +date: "2019-11-12T16:00:00+02:00" +title: "Chercher" +slug: "search" +weight: 4 +toc: true +draft: false +menu: + sidebar: + parent: "help" + name: "Chercher" + weight: 4 + identifier: "search" +sitemap: + priority : 0.1 +layout: "search" +--- + + +This file exists solely to respond to /search URL with the related `search` layout template. + +No content shown here is rendered, all content is based in the template layouts/doc/search.html + +Setting a very low sitemap priority will tell search engines this is not important content. + diff --git a/docs/content/doc/help/search.zh-cn.md b/docs/content/doc/help/search.zh-cn.md new file mode 100644 index 0000000000..a51860aacd --- /dev/null +++ b/docs/content/doc/help/search.zh-cn.md @@ -0,0 +1,25 @@ +--- +date: "2019-11-12T16:00:00+02:00" +title: "搜索" +slug: "search" +weight: 4 +toc: true +draft: false +menu: + sidebar: + parent: "help" + name: "搜索" + weight: 4 + identifier: "search" +sitemap: + priority : 0.1 +layout: "search" +--- + + +This file exists solely to respond to /search URL with the related `search` layout template. + +No content shown here is rendered, all content is based in the template layouts/doc/search.html + +Setting a very low sitemap priority will tell search engines this is not important content. + diff --git a/docs/content/doc/help/search.zh-tw.md b/docs/content/doc/help/search.zh-tw.md new file mode 100644 index 0000000000..a51860aacd --- /dev/null +++ b/docs/content/doc/help/search.zh-tw.md @@ -0,0 +1,25 @@ +--- +date: "2019-11-12T16:00:00+02:00" +title: "搜索" +slug: "search" +weight: 4 +toc: true +draft: false +menu: + sidebar: + parent: "help" + name: "搜索" + weight: 4 + identifier: "search" +sitemap: + priority : 0.1 +layout: "search" +--- + + +This file exists solely to respond to /search URL with the related `search` layout template. + +No content shown here is rendered, all content is based in the template layouts/doc/search.html + +Setting a very low sitemap priority will tell search engines this is not important content. + diff --git a/docs/layouts/_default/index.json b/docs/layouts/_default/index.json new file mode 100644 index 0000000000..ae08324d8e --- /dev/null +++ b/docs/layouts/_default/index.json @@ -0,0 +1,5 @@ +{{- $.Scratch.Add "index" slice -}} +{{- range .Site.RegularPages -}} +{{- $.Scratch.Add "index" (dict "title" .Title "tags" .Params.tags "categories" .Params.categories "contents" .Plain "permalink" .Permalink) -}} +{{- end -}} +{{- $.Scratch.Get "index" | jsonify -}} diff --git a/docs/layouts/doc/search.html b/docs/layouts/doc/search.html new file mode 100644 index 0000000000..736fcaee10 --- /dev/null +++ b/docs/layouts/doc/search.html @@ -0,0 +1,44 @@ +{{ partial "header.html" . }} +{{ partial "navbar.html" . }} + +<section class="section"> + <div class="container is-centered page"> + <div class="columns"> + <div class="column is-one-quarter"> + {{ partial "menu" . }} + </div> + <div class="column"> + <div class=" content"> + <section class="resume-section p-3 p-lg-5 d-flex flex-column"> + <div class="my-auto" > + <form action="{{ "search" | absLangURL }}"> + <label>Search: + <input id="search-query" name="s"/> + </label> + </form> + <br/> + <div id="search-results"></div> + </div> + </section> + <!-- this template is sucked in by search.js and appended to the search-results div above. So editing here will adjust style --> + <script id="search-result-template" type="text/x-js-template"> + <div id="summary-${key}"> + <h4><a href="${link}">${title}</a></h4> + <p>${snippet}</p> + ${ isset tags }<p>Tags: ${tags}</p>${ end } + ${ isset categories }<p>Categories: ${categories}</p>${ end } + <hr/> + </div> + </script> + </div> + </div> + </div> + </div> +</section> + +<script src="https://cdnjs.cloudflare.com/ajax/libs/fuse.js/3.4.5/fuse.min.js"></script> +<script src="https://cdnjs.cloudflare.com/ajax/libs/mark.js/8.11.1/mark.min.js"></script> +<script>document.LANG = "{{ .Language.Lang }}";</script> +{{ $script := resources.Get "js/search.js" | minify | fingerprint -}} +<script src="{{ $script.Permalink }}" {{ printf "integrity=%q" $script.Data.Integrity | safeHTMLAttr }}></script> +{{ partial "footer.html" . }} |