From 3b0303a4fcbb8b8fda258e11edb3fd3c5c09396e Mon Sep 17 00:00:00 2001 From: John Olheiser <42128690+jolheiser@users.noreply.github.com> Date: Wed, 13 Nov 2019 12:03:18 -0600 Subject: [PATCH] Implement documentation search (#8937) * Implement documentation search Signed-off-by: jolheiser Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> --- docs/.gitignore | 1 + docs/assets/js/search.js | 176 ++++++++++++++++++++++++++ docs/config.yaml | 6 + docs/content/doc/help.en-us.md | 4 +- docs/content/doc/help.fr-fr.md | 13 ++ docs/content/doc/help.zh-cn.md | 4 +- docs/content/doc/help.zh-tw.md | 13 ++ docs/content/doc/help/search.en-us.md | 25 ++++ docs/content/doc/help/search.fr-fr.md | 25 ++++ docs/content/doc/help/search.zh-cn.md | 25 ++++ docs/content/doc/help/search.zh-tw.md | 25 ++++ docs/layouts/_default/index.json | 5 + docs/layouts/doc/search.html | 44 +++++++ 13 files changed, 362 insertions(+), 4 deletions(-) create mode 100644 docs/assets/js/search.js create mode 100644 docs/content/doc/help.fr-fr.md create mode 100644 docs/content/doc/help.zh-tw.md create mode 100644 docs/content/doc/help/search.en-us.md create mode 100644 docs/content/doc/help/search.fr-fr.md create mode 100644 docs/content/doc/help/search.zh-cn.md create mode 100644 docs/content/doc/help/search.zh-tw.md create mode 100644 docs/layouts/_default/index.json create mode 100644 docs/layouts/doc/search.html diff --git a/docs/.gitignore b/docs/.gitignore index 55ec469a4..9cd1408bd 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 000000000..72d94c9ee --- /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 23d125733..039e2938f 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 5ad1dd7f1..635cb8931 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 000000000..ab0cedccf --- /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 6af7aa171..9465cd546 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 000000000..c9cd794d8 --- /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 000000000..93c154bde --- /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 000000000..3507e9efe --- /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 000000000..a51860aac --- /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 000000000..a51860aac --- /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 000000000..ae08324d8 --- /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 000000000..736fcaee1 --- /dev/null +++ b/docs/layouts/doc/search.html @@ -0,0 +1,44 @@ +{{ partial "header.html" . }} +{{ partial "navbar.html" . }} + +
+
+
+
+ {{ partial "menu" . }} +
+
+
+
+
+
+ +
+
+
+
+
+ + +
+
+
+
+
+ + + + +{{ $script := resources.Get "js/search.js" | minify | fingerprint -}} + +{{ partial "footer.html" . }}