This commit is contained in:
parent
03501280ec
commit
eb6dd6f2ee
5
editor/package-lock.json
generated
5
editor/package-lock.json
generated
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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';
|
||||||
|
|
||||||
|
@ -28,7 +31,7 @@ function Indicator(element, timeout) {
|
||||||
done() {
|
done() {
|
||||||
timeoutId = setTimeout(() => {
|
timeoutId = setTimeout(() => {
|
||||||
element.classList.add('hidden')
|
element.classList.add('hidden')
|
||||||
}, timeout*1000);
|
}, timeout * 1000);
|
||||||
},
|
},
|
||||||
|
|
||||||
setText(text) {
|
setText(text) {
|
||||||
|
@ -53,19 +56,49 @@ function addIndicator(editor, indicator) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let holder = document.getElementById('editor');
|
let holder = document.getElementById('editor');
|
||||||
let editor = listEditor(holder, JSON.parse(holder.dataset.input));
|
if (holder) {
|
||||||
|
let editor = listEditor(holder, JSON.parse(holder.dataset.input));
|
||||||
|
|
||||||
editor.on('change', function () {
|
editor.on('change', function () {
|
||||||
let element = document.getElementById('editor');
|
let element = document.getElementById('editor');
|
||||||
let indicator = Indicator(document.getElementById('save-indicator'), 2);
|
let indicator = Indicator(document.getElementById('save-indicator'), 2);
|
||||||
let saveUrl = element.dataset.saveurl;
|
let saveUrl = element.dataset.saveurl;
|
||||||
let page = element.dataset.page;
|
let page = element.dataset.page;
|
||||||
|
|
||||||
indicator.setText('has changes...');
|
indicator.setText('has changes...');
|
||||||
addIndicator(
|
addIndicator(
|
||||||
addSaver(editor, saveUrl, page, () => indicator.setText('saving...')),
|
addSaver(editor, saveUrl, page, () => indicator.setText('saving...')),
|
||||||
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
26
editor/src/search.js
Normal 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;
|
4
file.go
4
file.go
|
@ -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
45
main.go
|
@ -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)
|
||||||
|
|
|
@ -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>
|
|
||||||
|
|
|
@ -5,44 +5,81 @@
|
||||||
<meta name="viewport"
|
<meta name="viewport"
|
||||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css" />
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css"/>
|
||||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" />
|
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"/>
|
||||||
<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>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user