mirror of
https://github.com/MeiK2333/github-style.git
synced 2025-10-07 01:54:06 +00:00
search
This commit is contained in:
19
README.md
19
README.md
@@ -217,6 +217,25 @@ And it will show like this:
|
|||||||
<p>block content</p>
|
<p>block content</p>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
## Support local search
|
||||||
|
|
||||||
|
add to `config.toml`
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[params]
|
||||||
|
enableSearch = true
|
||||||
|
|
||||||
|
[outputs]
|
||||||
|
home = ["html", "json"]
|
||||||
|
|
||||||
|
[outputFormats.json]
|
||||||
|
mediaType = "application/json"
|
||||||
|
baseName = "index"
|
||||||
|
isPlainText = false
|
||||||
|
```
|
||||||
|
|
||||||
|
We can do local search now, it is implemented by `fuse.js`.
|
||||||
|
|
||||||
## deploy.sh example
|
## deploy.sh example
|
||||||
|
|
||||||
There are various way to deploy to github, here is a link to official [document](https://gohugo.io/hosting-and-deployment/hosting-on-github/).
|
There are various way to deploy to github, here is a link to official [document](https://gohugo.io/hosting-and-deployment/hosting-on-github/).
|
||||||
|
@@ -25,6 +25,7 @@ pygmentsUseClasses = true
|
|||||||
location = "China"
|
location = "China"
|
||||||
userStatusEmoji = "😀"
|
userStatusEmoji = "😀"
|
||||||
enableGitalk = true
|
enableGitalk = true
|
||||||
|
enableSearch = true
|
||||||
|
|
||||||
[params.gitalk]
|
[params.gitalk]
|
||||||
clientID = "Your client ID" # Your client ID
|
clientID = "Your client ID" # Your client ID
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
{{ partial "header" . }}
|
{{ partial "header" . }}
|
||||||
|
{{ partial "search.html" .}}
|
||||||
{{ block "content" . }}{{ end }}
|
{{ block "content" . }}{{ end }}
|
||||||
{{ partial "footer.html" . }}
|
{{ partial "footer.html" . }}
|
||||||
</body>
|
</body>
|
||||||
|
67
layouts/_default/index.json
Normal file
67
layouts/_default/index.json
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
{{- /* Copy from https://github.com/HugoBlox/hugo-blox-builder/blob/main/modules/blox-bootstrap/layouts/index.json. */ -}}
|
||||||
|
{{- /* Generate the search index. */ -}}
|
||||||
|
{{- $index := slice -}}
|
||||||
|
{{- $pages := site.RegularPages -}}
|
||||||
|
{{- /* Add the index page of multi-page content separately since it's not in RegularPages above. */ -}}
|
||||||
|
{{- $pages := $pages | union (where (where site.Pages "Kind" "section") "Type" "book") -}}
|
||||||
|
{{- /* Add author pages to index so their bios can be searched. Hide empty `/authors/` node. */ -}}
|
||||||
|
{{- $pages := $pages | union (where (where site.Pages "Section" "authors") "Params.superuser" "!=" nil) -}}
|
||||||
|
|
||||||
|
{{- range $pages -}}
|
||||||
|
{{- /* Do not index drafts or private pages. */ -}}
|
||||||
|
{{- if and (not .Draft) (not .Params.private) | and (ne .Params.searchable false) -}}
|
||||||
|
|
||||||
|
{{- /* Generate page description. */ -}}
|
||||||
|
{{- $desc := "" -}}
|
||||||
|
{{- if .Params.summary -}}
|
||||||
|
{{- $desc = .Params.summary -}}
|
||||||
|
{{- else if .Params.abstract -}}
|
||||||
|
{{- $desc = .Params.abstract -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{- $desc = .Summary -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- $authors := .Params.authors -}}
|
||||||
|
{{- $title := .Title}}
|
||||||
|
{{- $rel_permalink := .RelPermalink -}}
|
||||||
|
{{- $permalink := .Permalink -}}
|
||||||
|
|
||||||
|
{{/* Correct the title and URL for author profile pages. */}}
|
||||||
|
{{- if eq .Section "authors" -}}
|
||||||
|
{{- $username := path.Base .File.Dir -}}
|
||||||
|
{{- with site.GetPage (printf "/authors/%s" $username) -}}
|
||||||
|
{{- $permalink = .Permalink -}}
|
||||||
|
{{- $rel_permalink = .RelPermalink -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{/* Include a user's display name rather than username where possible. */}}
|
||||||
|
{{- if .Params.authors -}}
|
||||||
|
{{- $authorLen := len .Params.authors -}}
|
||||||
|
{{- if gt $authorLen 0 -}}
|
||||||
|
{{- $authors = slice -}}
|
||||||
|
{{- range $k, $v := .Params.authors -}}
|
||||||
|
{{- $person_page_path := (printf "/authors/%s" (urlize $v)) -}}
|
||||||
|
{{- $person_page := site.GetPage $person_page_path -}}
|
||||||
|
{{- if and $person_page $person_page.File -}}
|
||||||
|
{{- $authors = $authors | append $person_page.Title -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{- $authors = $authors | append ($v | plainify) -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- /* Add page to index. */ -}}
|
||||||
|
{{/* Exclude virtual pages which aren't backed by a file */}}
|
||||||
|
{{- if .File }}
|
||||||
|
{{/* exclude "content/readme.md". */}}
|
||||||
|
{{ $hidden_file := "/readme/" }}
|
||||||
|
{{ if ne $hidden_file $rel_permalink }}
|
||||||
|
{{- $index = $index | append (dict "objectID" .File.UniqueID "date" .Date.UTC.Unix "publishdate" .PublishDate "lastmod" .Lastmod.UTC.Unix "expirydate" .ExpiryDate.UTC.Unix "lang" .Lang "permalink" $permalink "relpermalink" $rel_permalink "title" $title "summary" (plainify $desc | htmlUnescape) "content" (.Plain | htmlUnescape | truncate 5000) "authors" $authors "kind" .Kind "type" .Type "section" .Section "tags" .Params.Tags "categories" .Params.Categories) -}}
|
||||||
|
{{ end }}
|
||||||
|
{{ end -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- $index | jsonify -}}
|
@@ -1,8 +1,6 @@
|
|||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<div class="application-main">
|
<div class="application-main">
|
||||||
<main>
|
{{ partial "user-profile.html" . }}
|
||||||
{{ partial "user-profile.html" . }}
|
{{ partial "posts.html" .}}
|
||||||
|
|
||||||
{{ partial "posts.html" .}}
|
|
||||||
</div>
|
</div>
|
||||||
{{end }}
|
{{ end }}
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
{{ partial "post.html" .}}
|
<div class="application-main">
|
||||||
{{ partial "gitalk.html" . }}
|
{{ partial "post.html" .}}
|
||||||
{{end }}
|
{{ partial "gitalk.html" . }}
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
@@ -19,6 +19,17 @@
|
|||||||
<div
|
<div
|
||||||
class="Header-search header-search flex-auto js-site-search position-relative flex-self-stretch flex-md-self-auto mb-3 mb-md-0 mr-0 mr-md-3 scoped-search site-scoped-search js-jump-to">
|
class="Header-search header-search flex-auto js-site-search position-relative flex-self-stretch flex-md-self-auto mb-3 mb-md-0 mr-0 mr-md-3 scoped-search site-scoped-search js-jump-to">
|
||||||
<div class="position-relative">
|
<div class="position-relative">
|
||||||
|
{{ if .Site.Params.enableSearch }}
|
||||||
|
<form target="_blank" id="search-form" action="" accept-charset="UTF-8" method="get"
|
||||||
|
autocomplete="off">
|
||||||
|
<label
|
||||||
|
class="Header-search-label form-control input-sm header-search-wrapper p-0 js-chromeless-input-container header-search-wrapper-jump-to position-relative d-flex flex-justify-between flex-items-center">
|
||||||
|
<input type="text"
|
||||||
|
class="Header-search-input form-control input-sm header-search-input jump-to-field js-jump-to-field js-site-search-focus js-site-search-field is-clearable"
|
||||||
|
name="q" value="" placeholder="Search" autocomplete="off">
|
||||||
|
</label>
|
||||||
|
</form>
|
||||||
|
{{ else }}
|
||||||
<form target="_blank" action="https://www.google.com/search" accept-charset="UTF-8" method="get"
|
<form target="_blank" action="https://www.google.com/search" accept-charset="UTF-8" method="get"
|
||||||
autocomplete="off">
|
autocomplete="off">
|
||||||
<label
|
<label
|
||||||
@@ -29,6 +40,7 @@
|
|||||||
<input type="hidden" name="q" value="site:{{ .Site.BaseURL }}">
|
<input type="hidden" name="q" value="site:{{ .Site.BaseURL }}">
|
||||||
</label>
|
</label>
|
||||||
</form>
|
</form>
|
||||||
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -47,3 +47,8 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
{{ if .Site.Params.enableSearch }}
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/fuse.js/dist/fuse.min.js"></script>
|
||||||
|
<script type="application/javascript" src='{{ "js/search.js" | absURL }}'></script>
|
||||||
|
{{ end }}
|
||||||
|
2
layouts/partials/search.html
Normal file
2
layouts/partials/search.html
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<div id="search-result" class="container-lg px-3 new-discussion-timeline" style="display: none;">
|
||||||
|
</div>
|
116
static/js/search.js
Normal file
116
static/js/search.js
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
(() => {
|
||||||
|
fetch('/index.json')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
const fuse = new Fuse(data, {
|
||||||
|
keys: ['title', 'content'],
|
||||||
|
shouldSort: true,
|
||||||
|
includeMatches: true,
|
||||||
|
minMatchCharLength: 2,
|
||||||
|
threshold: 0.0,
|
||||||
|
ignoreLocation: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
document.getElementById('search-form').addEventListener('submit', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const data = new FormData(e.target)
|
||||||
|
// data.entries() returns iterator, [...data.entries()] returns [["q", "input"]]
|
||||||
|
const input = [...data.entries()][0][1]
|
||||||
|
const results = fuse.search(input)
|
||||||
|
displayResults(input, results)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
function displayResults(input, results) {
|
||||||
|
const searchResults = document.getElementById('search-result');
|
||||||
|
searchResults.setAttribute('style', 'display: block;')
|
||||||
|
searchResults.nextElementSibling.setAttribute('style', 'display: none;')
|
||||||
|
let html = renderResultsCountHtml(results.length, input)
|
||||||
|
if (results.length > 0) {
|
||||||
|
let li = renderResultsItemHtml(results)
|
||||||
|
html += `<ul>${li}</ul>`
|
||||||
|
}
|
||||||
|
searchResults.innerHTML = html
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderResultsCountHtml(count, input) {
|
||||||
|
let html = `
|
||||||
|
<div class="TableObject border-gray-light py-3 mt-6">
|
||||||
|
<div class="user-repo-search-results-summary TableObject-item TableObject-item--primary v-align-top">
|
||||||
|
<strong>${count}</strong>
|
||||||
|
results
|
||||||
|
for "<strong>${input}</strong>"
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderResultsItemHtml(results) {
|
||||||
|
// modified from https://github.com/brunocechet/Fuse.js-with-highlight
|
||||||
|
var highlighter = function(resultItem){
|
||||||
|
resultItem.matches.forEach((matchItem) => {
|
||||||
|
var text = resultItem.item[matchItem.key];
|
||||||
|
var result = []
|
||||||
|
var matches = [].concat(matchItem.indices);
|
||||||
|
var pair = matches.shift()
|
||||||
|
|
||||||
|
for (var i = 0; i < text.length; i++) {
|
||||||
|
var char = text.charAt(i)
|
||||||
|
if (pair && i == pair[0]) {
|
||||||
|
result.push('<span style="color: red;">')
|
||||||
|
}
|
||||||
|
result.push(char)
|
||||||
|
if (pair && i == pair[1]) {
|
||||||
|
result.push('</span>')
|
||||||
|
pair = matches.shift()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resultItem.highlight = result.join('');
|
||||||
|
|
||||||
|
if(resultItem.children && resultItem.children.length > 0){
|
||||||
|
resultItem.children.forEach((child) => {
|
||||||
|
highlighter(child);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let html = ``
|
||||||
|
results.forEach(result => {
|
||||||
|
highlighter(result)
|
||||||
|
// truncated highlight content
|
||||||
|
let truncated = result.highlight.substring(0, 2000)
|
||||||
|
const reg = /(<span style="color: red;">[a-zA-Z0-9\u4e00-\u9fa5]+<\/span>)/g
|
||||||
|
let array = truncated.split(reg)
|
||||||
|
// drop unstable part
|
||||||
|
array.pop()
|
||||||
|
let content = ""
|
||||||
|
if (array.length > 0) {
|
||||||
|
content = array.join('')
|
||||||
|
} else {
|
||||||
|
// fallback to no highlighted truncated content
|
||||||
|
content = result.item.content.substring(0, 2000)
|
||||||
|
}
|
||||||
|
html += `
|
||||||
|
<li class="col-12 d-flex width-full py-4 border-top color-border-secondary public source">
|
||||||
|
<div class="col-12 d-inline-block">
|
||||||
|
<div class="d-inline-block mb-1">
|
||||||
|
<h3 class="wb-break-all">
|
||||||
|
<a href="${result.item.permalink}">${result.item.title}</a>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="col-12 d-inline-block text-gray mb-2 pr-4">
|
||||||
|
${content} ...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
return html
|
||||||
|
}
|
Reference in New Issue
Block a user