minoteaur/src/client.js

97 lines
3.3 KiB
JavaScript

import m from "mithril"
const searchButton = document.querySelector("nav .search")
const mountpoint = document.createElement("div")
document.querySelector("main").insertBefore(mountpoint, document.querySelector(".header"))
const state = {
showingSearchDialog: false,
searchResults: [],
searchError: null,
searchQuery: ""
}
const lowercaseFirst = ([first, ...rest]) => first.toLowerCase() + rest.join("")
const uppercaseFirst = ([first, ...rest]) => first.toUpperCase() + rest.join("")
const pageToSlug = page => page.split(/[ _]/).map(lowercaseFirst).join("_")
const slugToPage = slug => slug.split(/[ _]/).map(uppercaseFirst).join(" ")
const urlForPage = (page, subpage) => {
let p = `/${encodeURIComponent(pageToSlug(page))}`
if (subpage) { p += "/" + subpage }
return p
}
const handleHTTPError = e => {
if (e.code === 0) { return }
let x = `Server error ${e.code}`
if (e.message) { x += " " + e.message }
alert(x)
}
const onsearch = ev => {
const query = ev.target.value
state.searchQuery = query
m.request({
url: "/api/search",
params: { q: query }
}).then(x => {
if (typeof x === "string") { // SQLite syntax error
console.log("ERR", x)
state.searchError = x
} else {
state.searchResults = x
state.searchError = null
}
}, e => handleHTTPError)
}
const currentPage = slugToPage(decodeURIComponent(/^\/([^/]+)/.exec(location.pathname)[1]).replace(/\+/g, " "))
const searchKeyHandler = ev => {
if (ev.keyCode === 13) { // enter key
// not very useful to just navigate to the same page
const otherResults = state.searchResults.filter(r => r.page !== currentPage)
if (otherResults[0]) { location.href = urlForPage(otherResults[0].page) }
}
}
const SearchDialog = {
view: () => m(".dialog.search", [
m("h1", "Search"),
m("input[type=search]", { placeholder: "Query", oninput: onsearch, onkeydown: searchKeyHandler, value: state.searchQuery, oncreate: ({ dom }) => dom.focus() }),
state.searchError && m(".error", state.searchError),
m("ul", state.searchResults.map(x => m("li", [
m(".flex-space", [ m("a.wikilink", { href: urlForPage(x.page) }, x.page), m("", x.rank.toFixed(3)) ]),
m("", x.snippet.map(s => s[0] ? m("span.highlight", s[1]) : s[1]))
])))
])
}
const App = {
view: () => m("", state.showingSearchDialog ? m(SearchDialog) : null)
}
searchButton.addEventListener("click", e => {
state.showingSearchDialog = !state.showingSearchDialog
e.preventDefault()
m.redraw()
})
document.body.addEventListener("keydown", e => {
if (e.target === document.body) { // maybe use alt instead? or right shift or something - this just detects unfocused keypresses
if (e.key === "e") {
location.pathname = urlForPage(currentPage, "edit")
} else if (e.key === "v") {
location.pathname = urlForPage(currentPage)
} else if (e.key === "r") {
location.pathname = urlForPage(currentPage, "revisions")
} else if (e.key === "/") {
state.showingSearchDialog = !state.showingSearchDialog
e.preventDefault()
m.redraw()
}
}
})
m.mount(mountpoint, App)