421 lines
12 KiB
HTML
421 lines
12 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<header>
|
|
<title>Notes</title>
|
|
<meta charset="utf-8">
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
|
|
|
<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">
|
|
|
|
<link rel="stylesheet" href="https://unpkg.com/turretcss/dist/turretcss.min.css" crossorigin="anonymous">
|
|
<!-- todo css
|
|
<link rel="stylesheet" href="https://cdn.concisecss.com/concise.min.css">
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/light.css">
|
|
<link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css">
|
|
-->
|
|
|
|
<style>
|
|
.EasyMDEContainer button {
|
|
color: black;
|
|
}
|
|
img {
|
|
max-width: 100%;
|
|
max-height: 100%;
|
|
}
|
|
.filetree {
|
|
max-width: 15em;
|
|
width: 15em;
|
|
min-width: 15em;
|
|
overflow-x: scroll;
|
|
white-space: nowrap;
|
|
}
|
|
.filesummary {
|
|
min-width: 10em;
|
|
}
|
|
.filedetails {
|
|
padding-inline-start: 2em;
|
|
}
|
|
.filetree > .filedetails,
|
|
.filetree > .filedetails > .filedetails {
|
|
padding-inline-start: 0em;
|
|
}
|
|
.fileleaf {
|
|
display: inline-flex;
|
|
flex-direction: row;
|
|
width: calc(100% - 1em);
|
|
}
|
|
.lr_fullscreen {
|
|
width: 90%;
|
|
max-width: 1024px;
|
|
margin-right: auto;
|
|
margin-left: auto;
|
|
}
|
|
.tb_fullscreen {
|
|
margin-top: 1em;
|
|
}
|
|
.columns {
|
|
display: flex;
|
|
flex-direction: row;
|
|
}
|
|
.rows {
|
|
width: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.thic_flex {
|
|
text-align: left;
|
|
flex-grow: 1;
|
|
}
|
|
.mia {
|
|
display: none;
|
|
}
|
|
.align_left {
|
|
text-align: left;
|
|
}
|
|
.tb_buffer {
|
|
margin-top: 1em;
|
|
margin-bottom: 1em;
|
|
}
|
|
.lr_buffer {
|
|
margin-left: 5em;
|
|
margin-right: 5em;
|
|
}
|
|
.monospace {
|
|
font-family: Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace;
|
|
}
|
|
.lil_btn {
|
|
width: initial;
|
|
display: inline-block;
|
|
}
|
|
input, label, textarea {
|
|
margin: initial;
|
|
}
|
|
</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 class="align_left" type="button" onclick="drawFile('${results[result]}');" value="${idsToFullTitle(results[result].split("/"))}"</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), "", "")
|
|
if (easyMDE.isPreviewActive()) {
|
|
easyMDE.togglePreview()
|
|
}
|
|
}
|
|
|
|
function enableMDE() {
|
|
document.getElementById("searchResults").className = "mia";
|
|
document.getElementById("article").className = "";
|
|
}
|
|
|
|
function disableMDE() {
|
|
document.getElementById("article").className = "mia";
|
|
document.getElementById("searchResults").className = "";
|
|
}
|
|
|
|
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 i = 0; i < pids.length; i++) {
|
|
const fullPid = pids.slice(0, i+1)
|
|
titlePath += ` <input type="button" class="lil_btn" value="${idsToTitle(fullPid)}" onclick="drawFile('${fullPid.join("/")}');"/> /`
|
|
}
|
|
|
|
enableMDE()
|
|
document.getElementById("titlePath").innerHTML = titlePath
|
|
document.getElementById("title").innerHTML = title
|
|
easyMDE.value(body)
|
|
easyMDE.meta = {
|
|
id: id,
|
|
}
|
|
if (!easyMDE.isPreviewActive()) {
|
|
easyMDE.togglePreview()
|
|
var previews = document.getElementsByClassName("preview")
|
|
}
|
|
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()
|
|
}
|
|
|
|
var lastTree = {}
|
|
|
|
function idsToTitle(original_ids) {
|
|
const fullTitle = idsToFullTitle(original_ids)
|
|
return fullTitle.slice(fullTitle.lastIndexOf("/")+1, fullTitle.length)
|
|
}
|
|
|
|
function idsToFullTitle(original_ids) {
|
|
var ids = original_ids.slice(0, original_ids.length)
|
|
var subtree = lastTree
|
|
var fullTitle = ""
|
|
while (ids && ids.length > 0) {
|
|
if (!subtree || !subtree["Branches"] || !subtree["Branches"][ids[0]])
|
|
break
|
|
subtree = subtree["Branches"][ids[0]]
|
|
if (subtree && subtree.Leaf && subtree.Leaf.Title)
|
|
fullTitle += "/" + subtree.Leaf.Title
|
|
ids = ids.slice(1, ids.length)
|
|
if (ids.length == 0) {
|
|
return fullTitle.slice(1, fullTitle.length)
|
|
}
|
|
}
|
|
return ids[ids.length-1]
|
|
}
|
|
|
|
function drawTree() {
|
|
function htmlifyBranch(id, branch) {
|
|
const maxTreeTitleLength = 35
|
|
var parent = `
|
|
<input class="thic_flex" type="button" value="${branch.Leaf.Title.substr(0, maxTreeTitleLength)}" onclick="drawFile('${id}');"/>
|
|
<input type="button" class="lil_btn" value="+" onclick="drawNewFile('${id}');"/>
|
|
`
|
|
if (id == "") {
|
|
parent = `
|
|
<span class="thic_flex"></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 class="filedetails" open>
|
|
<summary class="filesummary">
|
|
<div class="fileleaf">${parent}</div>
|
|
</summary>
|
|
${children}
|
|
</details>
|
|
`
|
|
}
|
|
http("GET", "/api/v0/tree", (body, status) => {
|
|
if (status != 200)
|
|
throw `bad status getting tree: ${status}: ${body}`
|
|
lastTree = JSON.parse(body)
|
|
document.getElementById("tree").innerHTML = htmlifyBranch("", lastTree)
|
|
})
|
|
}
|
|
|
|
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 class="lr_fullscreen tb_fullscreen" onload="init(); return false;">
|
|
<br>
|
|
<div class="rows">
|
|
<form class="columns" action="return false;">
|
|
<input class="thic_flex" type="text" id="searchbox" placeholder="search regexp"/>
|
|
<input class="info lil_btn" type="submit" value="search" onclick="searchFiles(); return false;"/>
|
|
</form>
|
|
<div class="columns thic_flex tb_buffer">
|
|
<div id="tree" class="filetree"></div>
|
|
<div class="thic_flex lr_fullscreen" style="margin-left: 1em; width: 5px;">
|
|
<article id="searchResults" class="mia">
|
|
</article>
|
|
<article id="article" class="mia">
|
|
<div>
|
|
<h1 class="columns">
|
|
<input class="button-info lil_btn" type="submit" class="lr_buffer" value="SAVE" onclick="pushFile(); return false;"/>
|
|
<span id="titlePath">
|
|
</span>
|
|
<span id="title" class="thic_flex" contenteditable></span>
|
|
<input class="button-error lil_btn" type="submit" value="DELETE" onclick="deleteFile(); return false;"/>
|
|
</h1>
|
|
</div>
|
|
<div id="saveFeedback" style="min-height: 1.2em; text-align: right;">
|
|
</div>
|
|
<!-- todo: each line no is an anchor -->
|
|
<div class="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: false,
|
|
uploadImage: true,
|
|
imageUploadEndpoint: "/api/v0/media", // POST wants {data: {filePath: "/..."}}
|
|
imagePathAbsolute: false,
|
|
renderingConfig: {
|
|
codeSyntaxHighlighting: true,
|
|
},
|
|
})
|
|
function logValue() {
|
|
console.log(easyMDE.value())
|
|
}
|
|
logValue()
|
|
</script>
|
|
</footer>
|
|
</html>
|