notea-de-me/spike/review/reinvent/ezmded/ui/index.html

338 lines
10 KiB
HTML

<!DOCTYPE html>
<html>
<header>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.css">
<script src="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.js"></script>
<script src="https://cdn.jsdelivr.net/highlight.js/latest/highlight.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/highlight.js/latest/styles/github.min.css">
<!-- todo css
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/light.css">
-->
<style>
#tree {
max-width: 15em;
width: 15em;
min-width: 15em;
overflow-x: scroll;
}
#tree > details summary {
min-width: 10em;
}
#tree details {
padding-inline-start: 1em;
}
details {
margin-top: .35em;
margin-bottom: .35em;
}
#tree summary > div {
display: inline-flex;
flex-direction: row;
width: calc(100% - 1em);
}
#tree summary > div > a {
flex-grow: 1;
}
#tree summary > div > input:first-child {
width: 100%;
text-align: left;
}
</style>
<script>
function init() {
drawTree()
setInterval(drawTree, 100000)
navigateToQueryParams()
}
function navigateToQueryParams() {
var queryF = getParameterByName("f")
var queryQ = getParameterByName("q")
console.log("init query f:", queryF, "q:", queryQ)
if (queryF && queryF.length > 0) {
drawFile(queryF)
} else if (queryQ && queryQ.length > 0) {
searchFilesFor(queryQ)
}
}
function getParameterByName(name, url = window.location.href) {
name = name.replace(/[\[\]]/g, '\\$&');
var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, ' '));
}
function deleteFile() {
if (!confirm("would you like to delete this file?"))
return
const id = easyMDE.meta.id
if (!id || id.length == 0)
return
http("DELETE", "/api/v0/files/" + id, (body, status) => {
if (status != 200) {
alert(`failed to delete file ${id}: ${status}: ${body}`)
throw `failed to delete file ${id}: ${status}: ${body}`
}
drawTree()
var pid = id.slice(0, id.lastIndexOf("/"))
if (pid) {
drawFile(pid)
} else {
disableMDE()
}
})
}
function searchFiles() {
const q = document.getElementById("searchbox").value
searchFilesFor(q)
}
function searchFilesFor(q) {
http("GET", "/api/v0/search?q=" + q, (body, status) => {
var results = JSON.parse(body)
console.log(q, results)
results.sort()
var innerHTML = "<ul>"
for (var result in results)
innerHTML += `<li><input type="button" onclick="drawFile('${results[result]}');" value="${results[result]}"</li>`
innerHTML += "</ul>"
if (!results || results.length == 0)
innerHTML = "no results"
disableMDE()
navigateToQuery("q", q)
document.getElementById("searchResults").innerHTML = innerHTML
})
}
var saveFeedbackInterval = null
function pushFile() {
const title = document.getElementById("title").innerHTML ? document.getElementById("title").innerHTML : ""
const body = easyMDE.value() ? easyMDE.value() : ""
const id = easyMDE.meta.id ? easyMDE.meta.id : ""
headers = {}
if (title)
headers["Title"] = title
http("PUT", "/api/v0/files/" + id, (body, status) => {
if (status != 200) {
alert(`failed to push file ${id}: ${status}: ${body}`)
throw `failed to push file ${id}: ${status}: ${body}`
}
drawTree()
drawFile(id)
document.getElementById("saveFeedback").innerHTML = "success!"
if (saveFeedbackInterval) {
clearInterval(saveFeedbackInterval)
}
saveFeedbackInterval = setTimeout(() => {document.getElementById("saveFeedback").innerHTML = ""}, 5000)
}, body, headers)
}
function drawFile(id) {
if (!id || id.length < 1) {
return
}
http("GET", "/api/v0/files/"+id, (body, status, headers) => {
if (status != 200) {
throw `ERR loading file: ${status}: ${body}`
}
const title = headers("title") ? headers("title") : ""
setMDE(id, title, body)
})
}
function drawNewFile(pid) {
setMDE(pid + "/" + crypto.randomUUID().substr(0, 5), "", "")
}
function enableMDE() {
document.getElementById("searchResults").style.display = "none";
document.getElementById("article").style.display = "";
}
function disableMDE() {
document.getElementById("article").style.display = "none";
document.getElementById("searchResults").style.display = "";
}
function setMDE(id, title, body) {
if (id[0] == "/")
id = id.slice(1, id.length)
if (id[id.length-1] == "/")
id = id.slice(0, id.length-1)
var pids = id.split("/")
pids = pids.slice(0, pids.length-1)
var titlePath = "/"
for (var pid in pids) {
titlePath += ` <input type="button" value="${pids[pid]}" onclick="drawFile('${pids.slice(0, pid+1).join("/")}');"/> /`
}
enableMDE()
document.getElementById("titlePath").innerHTML = titlePath
document.getElementById("title").innerHTML = title
easyMDE.value(body)
easyMDE.meta = {
id: id,
}
navigateToQuery("f", id)
}
var lastNavigateToQuery = new Date()
function navigateToQuery(k, v) {
if (new Date() - lastNavigateToQuery < .1)
return
lastNavigateToQuery = new Date()
const url = new URL(window.location)
url.searchParams.set(k, v)
var hash = "#?"
const it = url.searchParams.entries()
let result = it.next()
while (!result.done) {
hash = hash + result.value[0] + "=" + result.value[1] + "&"
result = it.next()
}
window.location.hash = hash
}
window.onhashchange = () => {
navigateToQueryParams()
}
function drawTree() {
function htmlifyBranch(id, branch) {
var parent = `
<input type="button" value="${branch.Leaf.Title.substr(0, 15)}" onclick="drawFile('${id}');"/>
<input type="button" value="+" onclick="drawNewFile('${id}');"/>
`
if (id == "") {
parent = `
<span style="flex-grow:1"></span>
<input type="button" value="+" onclick="drawNewFile('${id}');"/>
`
}
var children = []
for (var child in branch.Branches)
children.push({title: branch.Branches[child].Leaf.Title, content: htmlifyBranch(id + "/" + child, branch.Branches[child])})
children = children.map((e) => `<!--${e.title}-->${e.content}`)
children.sort();
children = children.join("\n")
return `
<details open>
<summary>
<div>${parent}</div>
</summary>
${children}
</details>
`
}
http("GET", "/api/v0/tree", (body, status) => {
if (status != 200)
throw `bad status getting tree: ${status}: ${body}`
const tree = JSON.parse(body)
document.getElementById("tree").innerHTML = htmlifyBranch("", tree)
})
}
function http(method, remote, callback, body, headers) {
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == XMLHttpRequest.DONE) {
callback(xmlhttp.responseText, xmlhttp.status, (key) => xmlhttp.getResponseHeader(key))
}
};
xmlhttp.open(method, remote, true);
if (typeof body == "undefined") {
body = null
}
if (headers) {
for (var key in headers)
xmlhttp.setRequestHeader(key, headers[key])
}
xmlhttp.send(body);
}
</script>
</header>
<body style="width: 90%; max-width: 1024px; margin: auto;" onload="init(); return false;">
<div style="width: 100%; display: flex; flex-direction: column;">
<form action="return false;" style="margin: 1em; display: flex; flex-direction: row;">
<input type="text" id="searchbox" style="flex-grow: 1;" placeholder="search regexp"/>
<input type="submit" value="search" onclick="searchFiles(); return false;"/>
</form>
<div style="width: 100%; display: flex; flex-direction: row; flex-grow: 1;">
<div id="tree"></div>
<div style="flex-grow: 1; margin-left: 1em;">
<article id="searchResults" style="display: none">
</article>
<article id="article" style="display: none">
<div>
<h1 style="display: flex; flex-direction: row;">
<input type="submit" value="DELETE" onclick="deleteFile(); return false;" style="margin-left: 5%; margin-right: 5%"/>
<span id="titlePath">
</span>
<span id="title" contenteditable style="flex-grow: 1;"></span>
<input type="submit" value="SAVE" onclick="pushFile(); return false;"/>
</h1>
</div>
<div id="saveFeedback" style="min-height: 1.2em; text-align: right;">
</div>
<!-- todo: each line no is an anchor -->
<div style="font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace;">
<textarea id="my-text-area"></textarea>
</font>
</article>
</div>
</div>
</div>
</body>
<footer>
<script>
const easyMDE = new EasyMDE({
autoDownloadFontAwesome: true,
autofocus: true,
autosave: {
enabled: false,
},
element: document.getElementById('my-text-area'),
forceSync: true,
indentWithTabs: false,
initialValue: "# initial\nvalue",
showIcons: ["code", "table"],
spellChecker: false,
sideBySideFullscreen: false,
tabSize: 3,
previewImagesInEditor: true,
insertTexts: {
image: ["![](", ")"],
link: ["[](", ")"],
},
lineNumbers: true,
lineWrapping: true,
uploadImage: true,
imageUploadEndpoint: "/api/v0/media", // POST wants {data: {filePath: "/..."}}
imagePathAbsolute: false,
renderingConfig: {
codeSyntaxHighlighting: true,
},
})
easyMDE.togglePreview()
/* todo */
setTimeout(() => {
var previews = document.getElementsByClassName("preview")
for (var i in previews) {
previews[i].classList.add("active")
}
}, 250)
function logValue() {
console.log(easyMDE.value())
}
logValue()
</script>
</footer>
</html>