You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
caddy-hugo2/frontend/templates/edit.html.tmpl

316 lines
7.5 KiB

<html>
<head>
<script src="/hugo/simplemde.js"></script>
<style type="text/css" media="screen">
#editor-wrapper {
position: absolute;
top: 50px;
right: 0;
bottom: 150px;
left: 40%;
}
#draft {
position: absolute;
top: 50px;
right: 60%;
bottom: 0;
left: 0;
}
#draft > iframe {
height: 100%;
width: 100%;
border: none;
}
a {
text-decoration: underline;
text-decoration-style: dotted;
cursor: pointer;
}
</style>
<link rel="stylesheet" href="/hugo/simplemde.css" />
<script src="/hugo/vue.js"></script>
<script src="/hugo/moment.js"></script>
<body>
<div id="container" >
<div id="header">
<div id="lastSaved">
<span v-if="sendQueue.length > 0 || Object.keys(needConfirmation).length > 0">last saved ${ lastSaved.from(now) }, saving</span>
<span v-else>saved</span>
<span v-if="connectionError">, ${connectionError}</span>
</div>
<div>
<a id="sideview-toggle-media">media</a>
<a id="sideview-toggle-draft">draft</a>
</div>
</div>
<div id="editor-wrapper">
<textarea id="editor">{{ .LoadContent }}</textarea>
</div>
<div id="draft"><iframe src="{{ .IframeSource }}">Loading draft...</iframe></div>
</div>
<script>
var iframe = document.querySelector("#draft > iframe");
document.onclick = function (event) {
var iframe = document.querySelector("#draft > iframe");
switch (event.target.id) {
case "sideview-toggle-media":
iframe.src = "/hugo/media";
break;
case "sideview-toggle-draft":
iframe.src = "{{ .IframeSource }}";
break;
}
}
var uiBindings = {
ltime: {{ .LTime }},
serverLtime: 0,
lastSaved: moment(),
now: moment(),
connectionError: null,
sendQueue: [],
sentRecently: [],
needConfirmation: {},
};
var app = new Vue({
el: "#container",
data: uiBindings,
delimiters: ["${", "}"],
});
function getLtime() {
uiBindings.ltime++
return uiBindings.ltime
}
function observeServer(l, confirmed) {
uiBindings.serverLtime = l;
if (confirmed && confirmed.length > 0) {
confirmed.forEach(function (e) {
delete uiBindings.needConfirmation[e];
})
}
observe(l);
}
function observe(l) {
if (l > uiBindings.ltime) {
uiBindings.now = moment();
uiBindings.lastSaved = moment();
uiBindings.ltime = l;
}
}
var selectedImage;
var editorElem = document.getElementById("editor");
var editor = new SimpleMDE({
element: editorElem,
forceSync: true,
insertTexts: {
image: ["{\{% thumb filename=\"", "#url#\" width=\"200\" %}}"]
},
imageURLFn: function () {
return selectedImage;
}
});
window.onmessage = function (evt) {
selectedImage = evt.data;
}
// Create WebSocket connection.
var socket = connect();
const sawChangesBumpsTo = 10;
var sentinelSrc = 'about:blank';
var oldSrc = '';
var sawChanges = -1;
window.setInterval(function () {
if (sawChanges >= 0) {
sawChanges--;
if (sawChanges == 0) {
if (iframe.contentWindow) {
iframe.contentWindow.location.reload();
}
}
}
uiBindings.now = moment();
if (uiBindings.connectionError) {
socket = connect();
} else if (uiBindings.sendQueue.length > 0) {
var ltime = getLtime();
// record lowest pending
// ltime at the time this message
// was serialized
var lowestPending = ltime;
for (c in uiBindings.needConfirmation) {
c = parseInt(c, 10);
if (lowestPending === 0 || c < lowestPending) {
lowestPending = c;
}
}
var msg = JSON.stringify({
"deltas": uiBindings.sendQueue,
"ltime": ltime,
"lowestPending": lowestPending,
});
uiBindings.sendQueue = [];
uiBindings.needConfirmation[ltime] = msg;
}
for (ltime in uiBindings.needConfirmation) {
var msg = uiBindings.needConfirmation[ltime];
socket.send(msg);
}
}, 500);
function connect() {
const socket = new WebSocket((location.protocol == "https:" ? 'wss://' : 'ws://') + location.host + location.pathname);
// Listen for messages
socket.addEventListener('message', function (event) {
var message = JSON.parse(event.data);
observeServer(message.ltime, message.confirmed);
var deltas = [];
deltas.push.apply(deltas, message.deltas);
deltas.forEach(function(aceDelta) {
var cmDelta = aceDeltaToCM(aceDelta)
var content = ""
var to = {
line: aceDelta.start.row,
ch: aceDelta.start.column,
}
if (aceDelta.action == "insert") {
content = aceDelta.lines.join("\n");
to = null;
}
editor.codemirror.doc.replaceRange(content, cmDelta.from, to, "dontreflect");
sawChanges = sawChangesBumpsTo;
})
});
socket.addEventListener('open', function () {
uiBindings.connectionError = null;
});
socket.addEventListener('close', function () {
if (!uiBindings.connectionError) {
getLtime();
uiBindings.connectionError = "server connection closed, reconnecting...";
}
});
socket.addEventListener('error', function (err) {
if (!uiBindings.connectionError) {
uiBindings.connectionError = err;
getLtime();
}
console.log(err);
});
return socket;
}
editor.codemirror.on("change", function (cm, cmDelta) {
if (cmDelta.origin == "dontreflect") {
return;
}
var aceDelta = cmDeltaToAce(cmDelta);
console.log(cmDelta, "=>", aceDelta)
sawChanges = sawChangesBumpsTo;
uiBindings.sendQueue.push.apply(uiBindings.sendQueue, aceDelta);
})
function cmDeltaToAce(cmDelta) {
var isRemove = (cmDelta.removed.length > 0 && cmDelta.removed[0].length > 0) || cmDelta.removed.length > 1;
var lines = isRemove ? cmDelta.removed : cmDelta.text;
var aceDelta = {
action: isRemove ? "remove" : "insert",
lines: lines,
start: {
row: cmDelta.from.line,
column: cmDelta.from.ch,
},
end: {
row: cmDelta.from.line + (isRemove ? lines.length - 1 : lines.length - 1 ),
column: lines[lines.length-1].length,
}
};
if (aceDelta.start.row == aceDelta.end.row) {
aceDelta.end.column += cmDelta.from.ch;
}
if (false && isRemove && aceDelta.start.row == aceDelta.end.row) {
var origStart = aceDelta.start;
aceDelta.start = aceDelta.end;
aceDelta.end = origStart;
aceDelta.start.column += cmDelta.from.ch;
}
if (isRemove && ((cmDelta.text.length > 0 && cmDelta.text[0].length > 0) || cmDelta.text.length > 1)) {
cmDelta.removed = [""];
var ret = [aceDelta];
ret.push.apply(ret, cmDeltaToAce(cmDelta));
return ret;
}
return [aceDelta];
}
function aceDeltaToCM(aceDelta) {
var cmDelta = {
text: [],
removed: [],
from: {
line: aceDelta.start.row,
ch: aceDelta.start.column,
},
to: {
// cm deltas are weird. to refers to the selection end, which
// with a simple blinking cursor with no selection, is always
// the same as from
line: aceDelta.start.row,
ch: aceDelta.start.column,
},
}
if (aceDelta.action == "remove") {
var origStart = aceDelta.start;
aceDelta.start = aceDelta.end;
aceDelta.end = origStart;
cmDelta.removed = aceDelta.lines
cmDelta.text = [""]
} else {
cmDelta.text = aceDelta.lines
cmDelta.removed = [""]
}
return cmDelta;
}
</script>
</body>
</html>