Add search to wiki
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Peter Stuifzand 2020-05-17 15:09:18 +02:00
parent 03501280ec
commit eb6dd6f2ee
8 changed files with 202 additions and 27 deletions

View File

@ -4220,6 +4220,11 @@
"integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=",
"dev": true "dev": true
}, },
"mustache": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/mustache/-/mustache-4.0.1.tgz",
"integrity": "sha512-yL5VE97+OXn4+Er3THSmTdCFCtx5hHWzrolvH+JObZnUYwuaG7XV+Ch4fR2cIrcYI0tFHxS7iyFYl14bW8y2sA=="
},
"nan": { "nan": {
"version": "2.14.0", "version": "2.14.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",

View File

@ -11,6 +11,8 @@
"axios": "^0.19.0", "axios": "^0.19.0",
"bulma": "^0.7.5", "bulma": "^0.7.5",
"css-loader": "^3.2.0", "css-loader": "^3.2.0",
"lunr": "^2.3.8",
"mustache": "^4.0.1",
"node-sass": "^4.12.0", "node-sass": "^4.12.0",
"sass-loader": "^7.3.1", "sass-loader": "^7.3.1",
"style-loader": "^1.0.0", "style-loader": "^1.0.0",

View File

@ -1,6 +1,9 @@
import listEditor from 'wiki-list-editor'; import listEditor from 'wiki-list-editor';
import axios from 'axios'; import axios from 'axios';
import qs from 'querystring' import qs from 'querystring'
import $ from 'jquery';
import search from './search';
import Mustache from 'mustache';
import './styles.scss'; import './styles.scss';
@ -53,8 +56,8 @@ function addIndicator(editor, indicator) {
} }
} }
let holder = document.getElementById('editor'); let holder = document.getElementById('editor');
if (holder) {
let editor = listEditor(holder, JSON.parse(holder.dataset.input)); let editor = listEditor(holder, JSON.parse(holder.dataset.input));
editor.on('change', function () { editor.on('change', function () {
@ -69,3 +72,33 @@ editor.on('change', function () {
indicator indicator
).save() ).save()
}) })
}
let timeout = null;
let searchInput = document.getElementById('search-input');
search(searchInput).then(searcher => {
searchInput.addEventListener('keyup', function (e) {
clearTimeout(timeout);
if (e.key === 'Escape') {
$(searchInput).val('');
$('#autocomplete').hide();
return;
}
timeout = setTimeout(function () {
let query = $(searchInput).val()
if (query === '') {
let autocomplete = document.getElementById('autocomplete');
$(autocomplete).hide()
return
}
$('#autocomplete').show()
let result = searcher.idx.search(query)
var template = document.getElementById('result-template').innerHTML;
var rendered = Mustache.render(template, {results: result}, {}, ['[[', ']]']);
let autocomplete = document.getElementById('autocomplete');
autocomplete.innerHTML = rendered;
}, 500)
})
})

26
editor/src/search.js Normal file
View File

@ -0,0 +1,26 @@
import lunr from 'lunr';
import $ from 'jquery';
function search(element) {
return new Promise(function (resolve, reject) {
$.get('/documents.json', function (documents) {
let idx = lunr(function () {
this.ref('url')
this.field('title')
this.field('body')
let lunridx = this
$.each(documents, function (index, doc) {
lunridx.add(doc)
})
})
resolve({
element: element,
idx: idx
})
})
})
}
export default search;

View File

@ -28,6 +28,7 @@ func NewFilePages(dirname string) PagesRepository {
func (fp *FilePages) Get(title string) Page { func (fp *FilePages) Get(title string) Page {
name := strings.Replace(title, " ", "_", -1) name := strings.Replace(title, " ", "_", -1)
title = strings.Replace(title, "_", " ", -1)
refs, err := loadBackrefs(fp, name) refs, err := loadBackrefs(fp, name)
if err != nil { if err != nil {
refs = nil refs = nil
@ -279,6 +280,9 @@ func (fp *FilePages) AllPages() ([]Page, error) {
var pages []Page var pages []Page
for _, file := range files { for _, file := range files {
if file.Name()[0] == '.' {
continue
}
if file.Name() == "backrefs.json" { if file.Name() == "backrefs.json" {
continue continue
} }

45
main.go
View File

@ -590,6 +590,51 @@ func main() {
mp = NewFilePages("data") mp = NewFilePages("data")
http.Handle("/auth/", &authHandler{}) http.Handle("/auth/", &authHandler{})
http.HandleFunc("/documents.json", func(w http.ResponseWriter, r *http.Request) {
type Document struct {
Title string `json:"title"`
Body string `json:"body"`
URL string `json:"url"`
}
var results []Document
pages, err := mp.(*FilePages).AllPages()
if err != nil {
http.Error(w, err.Error(), 500)
return
}
for _, page := range pages {
content := page.Content
var listItems []struct {
Id int
Indented int
Text string
}
err = json.NewDecoder(strings.NewReader(content)).Decode(&listItems)
if err == nil {
pageText := ""
for _, item := range listItems {
pageText += strings.Repeat(" ", item.Indented) + "* " + item.Text + "\n"
}
content = pageText
}
results = append(results, Document{
Title: page.Title,
Body: content,
URL: page.Name,
})
}
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(&results)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
})
http.HandleFunc("/fetchLink", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/fetchLink", func(w http.ResponseWriter, r *http.Request) {
link := r.URL.Query().Get("url") link := r.URL.Query().Get("url")
u, err := url.Parse(link) u, err := url.Parse(link)

View File

@ -1,2 +1 @@
<div id="editor" data-input="{{ .Data }}" data-saveurl="/save/" data-page="{{ .Page }}" save-type="{{ .ContentType }}"></div> <div id="editor" data-input="{{ .Data }}" data-saveurl="/save/" data-page="{{ .Page }}" save-type="{{ .ContentType }}"></div>
<script src="/public/index.bundle.js"></script>

View File

@ -10,39 +10,76 @@
<title>{{ .Title }} - Wiki</title> <title>{{ .Title }} - Wiki</title>
{{ block "content_head" . }} {{ end }} {{ block "content_head" . }} {{ end }}
<style> <style>
#autocomplete {
z-index: 1;
width: 217px;
overflow-x: hidden;
overflow-y: auto;
height: 300px;
position: absolute;
background: white;
border: 1px solid #ccc;
padding: 24px;
}
.monospace { .monospace {
font-family: "Fira Code Retina", monospace; font-family: "Fira Code Retina", monospace;
white-space: pre-wrap; white-space: pre-wrap;
} }
.lighter { .lighter {
color: #ccc; color: #ccc;
} }
del { del {
text-decoration: none; text-decoration: none;
} }
ins { ins {
text-decoration: none; text-decoration: none;
} }
.checklist { .checklist {
margin-bottom: 1em; margin-bottom: 1em;
} }
.checklist--item { .checklist--item {
display: flex; display: flex;
} }
.checklist--item-text { .checklist--item-text {
align-self: center; align-self: center;
} }
</style> </style>
<style> <style>
@import url('https://rsms.me/inter/inter.css'); @import url('https://rsms.me/inter/inter.css');
html { font-family: 'Inter', sans-serif; }
body { font-family: 'Inter', sans-serif; } html {
input.input-line { font-family: 'Inter', sans-serif; } font-family: 'Inter', sans-serif;
@supports (font-variation-settings: normal) {
html { font-family: 'Inter var', sans-serif; }
body { font-family: 'Inter var', sans-serif; }
input.input-line { font-family: 'Inter var', sans-serif; }
} }
body {
font-family: 'Inter', sans-serif;
}
input.input-line {
font-family: 'Inter', sans-serif;
}
@supports (font-variation-settings: normal) {
html {
font-family: 'Inter var', sans-serif;
}
body {
font-family: 'Inter var', sans-serif;
}
input.input-line {
font-family: 'Inter var', sans-serif;
}
}
.list-item { .list-item {
padding: 3px; padding: 3px;
display: flex; display: flex;
@ -77,6 +114,7 @@
.selected { .selected {
background: lightblue; background: lightblue;
} }
.editor.selected { .editor.selected {
background: none; background: none;
} }
@ -135,7 +173,8 @@
Wiki Wiki
</a> </a>
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample"> <a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false"
data-target="navbarBasicExample">
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
@ -149,13 +188,35 @@
</div> </div>
</nav> </nav>
<div class="level">
<div class="level-left"></div>
<div class="level-right">
<div class="level-item">
<div class="field">
<p class="control">
<input class="search input" id="search-input" type="text" placeholder="Find a page">
</p>
<div id="autocomplete" class="hidden"></div>
</div>
</div>
</div>
</div>
<section class="section"> <section class="section">
{{ template "content" . }} {{ template "content" . }}
</section> </section>
<div id="save-indicator" class="hidden"></div> <div id="save-indicator" class="hidden"></div>
</div> </div>
{{ block "footer_scripts" . }}{{ end }} {{ block "footer_scripts" . }}
{{ end }}
<script src="/public/index.bundle.js"></script>
<div id="result-template" class="hidden">
<ul>
[[#results]]
<li><a href="/[[ref]]">[[ref]]</a></li>
[[/results]]
</ul>
</div>
</body> </body>
</html> </html>