Commit 88afaf6a authored by Henrik Tramberend's avatar Henrik Tramberend

Cleanup my decker engine mess a little

- Remove the question overview slide in favour of the menu
- Tighten some of the code
- Remove debug output
parent 856559e3
export {contactEngine};
// TODO Make into a proper Reveal plugin
// TODO Make into a proper Reveal 4 plugin
const DEBUG = true;
const DEBUG_AUTH = false;
// Start with a 0.5 s retry interval. Back off exponentally.
var timeout = 500;
var timeout = 100; // ms
let engine = {
api: undefined,
deckId: undefined, // The unique deck identifier.
token: undefined,
slideId: undefined
}
// Contacts the engine API at base.
function contactEngine(base, deckId) {
engine.deckId = deckId || deckUrl();
async function contactEngine(base, publicUrl) {
// Try to import the API utility module.
import(base + "/decker-util.js")
.then(engine => {
prepareEngine(engine.buildApi(base), publicUrl);
.then(util => {
console.log("Decker engine contacted at: ", base);
engine.api = util.buildApi(base);
prepareEngine();
})
.catch(e => {
console.log("Can't contact decker engine:" + e);
setTimeout(() => contactEngine(base), (timeout *= 1.5));
console.log("Retrying ..." + e);
setTimeout(() => contactEngine(base, deckId), (timeout *= 2));
});
}
async function prepareEngine(api, publicUrl) {
var serverToken;
api
// Strips the document URI from everything that can not be part of the deck id.
function deckUrl() {
let url = new URL(window.location);
url.hash = "";
url.query = "";
url.username = "";
url.password = "";
return url.toString();
}
// Prepares the questions panel for operation.
function prepareEngine() {
engine.api
.getToken()
.then(token => {
serverToken = token;
// Globally set the server token.
engine.token = token;
// Build the panel, once Reval is ready.
if (Reveal.isReady()) {
buildInterface(api, serverToken, publicUrl);
//buildOverview(api, serverToken, publicUrl);
buildInterface();
} else {
Reveal.addEventListener("ready", _ => {
buildInterface(api, serverToken, publicUrl);
//buildOverview(api, serverToken, publicUrl);
buildInterface();
});
}
// Build the menu, once Reval and the menu are ready.
if (Reveal.isReady() && Reveal.hasPlugin('menu') && Reveal.getPlugin('menu').isInit()) {
buildMenu(api, serverToken, publicUrl);
buildMenu();
} else {
Reveal.addEventListener("menu-ready", _ => {
buildMenu(api, serverToken, publicUrl);
buildMenu();
});
}
})
.catch(e => {
// Nothing goes without a token
console.log("getToken() failed: " + e);
console.log("retrying ...");
setTimeout(() => prepareEngine(api), 1000);
console.log("API function getToken() failed: " + e);
throw (e);
});
}
function deckId() {
let url = new URL(window.location);
url.hash = "";
url.query = "";
url.username = "";
url.password = "";
return url.toString();
}
function buildInterface(api, initialToken, publicUrl) {
var serverToken = initialToken;
if (DEBUG) {
console.log("token:", initialToken);
console.log("publicUrl:", publicUrl);
}
// Builds the panel and sets up event handlers.
function buildInterface() {
let open = document.createElement("div");
let badge = document.createElement("div");
......@@ -192,27 +200,11 @@ function buildInterface(api, initialToken, publicUrl) {
document.body.appendChild(open);
document.body.appendChild(panel);
// Found on StackOverflow
const hashCode = s =>
s.split("").reduce((a, b) => ((a << 5) - a + b.charCodeAt(0)) | 0, 0);
let getContext = () => {
return {
deck: publicUrl || deckId(),
slide: Reveal.getCurrentSlide().id,
token: user.value
};
};
let updateIds = () => {
let context = getContext();
};
let initUser = () => {
let localToken = window.localStorage.getItem("token");
if (serverToken && serverToken.authorized) {
if (engine.token && engine.token.authorized) {
// Some higher power has authorized this user. Lock token in.
user.value = serverToken.authorized;
user.value = engine.token.authorized;
user.setAttribute("disabled", true);
check.classList.add("checked");
user.type = "password";
......@@ -226,7 +218,7 @@ function buildInterface(api, initialToken, publicUrl) {
check.classList.add("checked");
user.type = "password";
} else {
user.value = serverToken.random;
user.value = engine.token.random;
user.removeAttribute("disabled");
check.classList.remove("checked");
user.type = "text";
......@@ -234,12 +226,12 @@ function buildInterface(api, initialToken, publicUrl) {
};
let updateComments = () => {
let context = getContext();
api
let slideId = Reveal.getCurrentSlide().id;
engine.api
.getComments(
context.deck,
context.slide,
serverToken.admin || context.token
engine.deckId,
slideId,
engine.token.admin || user.value
)
.then(renderList)
.catch(console.log);
......@@ -251,13 +243,10 @@ function buildInterface(api, initialToken, publicUrl) {
};
let canDelete = comment => {
let context = getContext();
return serverToken.admin !== null || comment.author === context.token;
return engine.token.admin !== null || comment.author === user.value;
};
let renderList = list => {
let context = getContext();
counter.textContent = list.length;
counter.setAttribute("data-count", list.length);
badge.textContent = list.length;
......@@ -287,18 +276,17 @@ function buildInterface(api, initialToken, publicUrl) {
vote.appendChild(thumb.cloneNode(true));
}
vote.classList.add("vote");
if (comment.author !== context.token) {
if (comment.author !== user.value) {
vote.classList.add("canvote");
if (comment.didvote) {
vote.classList.add("didvote");
}
vote.addEventListener("click", _ => {
let context = getContext();
let vote = {
comment: comment.id,
voter: context.token
voter: user.value
};
api.voteComment(vote).then(updateComments);
engine.api.voteComment(vote).then(updateComments);
});
} else {
vote.classList.add("cantvote");
......@@ -317,22 +305,16 @@ function buildInterface(api, initialToken, publicUrl) {
let del = document.createElement("button");
del.appendChild(trash.cloneNode(true));
del.addEventListener("click", _ => {
let context = getContext();
console.log(comment);
console.log(context);
console.log(serverToken);
console.log(comment.id, serverToken.admin || context.token);
api
.deleteComment(comment.id, serverToken.admin || context.token)
engine.api
.deleteComment(comment.id, engine.token.admin || user.value)
.then(updateComments);
});
// Edit button
let mod = document.createElement("button");
mod.appendChild(edit.cloneNode(true));
mod.addEventListener("click", _ => {
let context = getContext();
api
.deleteComment(comment.id, serverToken.admin || context.token)
engine.api
.deleteComment(comment.id, engine.token.admin || user.value)
.then(updateComments);
text.value = comment.markdown;
text.focus();
......@@ -359,7 +341,7 @@ function buildInterface(api, initialToken, publicUrl) {
login.addEventListener("click", _ => {
if (login.classList.contains("admin")) {
serverToken.admin = null;
engine.token.admin = null;
username.value = "";
password.value = "";
login.classList.remove("admin");
......@@ -378,21 +360,21 @@ function buildInterface(api, initialToken, publicUrl) {
if (e.key !== "Enter") return;
if (login.classList.contains("admin")) {
serverToken.admin = null;
engine.token.admin = null;
username.value = "";
password.value = "";
login.classList.remove("admin");
credentials.classList.remove("visible");
updateComments();
} else {
api
engine.api
.getLogin({
login: username.value,
password: password.value,
deck: publicUrl || deckId()
deck: engine.deckId
})
.then(token => {
serverToken.admin = token.admin;
engine.token.admin = token.admin;
login.classList.add("admin");
username.value = "";
password.value = "";
......@@ -405,7 +387,7 @@ function buildInterface(api, initialToken, publicUrl) {
}
});
if (!(serverToken && serverToken.authorized)) {
if (!(engine.token.authorized)) {
user.addEventListener("keydown", e => {
if (e.key === "Enter") {
updateComments();
......@@ -434,9 +416,9 @@ function buildInterface(api, initialToken, publicUrl) {
text.addEventListener("keydown", e => {
if (e.key === "Enter" && e.shiftKey) {
let context = getContext();
api
.submitComment(context.deck, context.slide, context.token, text.value)
let slideId = Reveal.getCurrentSlide().id;
engine.api
.submitComment(engine.deckId, slideId, user.value, text.value)
.then(renderSubmit)
.catch(console.log);
e.stopPropagation();
......@@ -447,85 +429,13 @@ function buildInterface(api, initialToken, publicUrl) {
Reveal.addEventListener("slidechanged", _ => {
updateComments();
updateIds();
});
initUser();
updateComments();
updateIds();
}
function buildOverview(api, initialToken, publicUrl) {
var serverToken = initialToken;
let slides = document.querySelector("div.reveal div.slides");
let slide = document.createElement("section");
slide.setAttribute("id", "questions-overview")
slide.classList.add("slide", "level1", "questions", "overview");
let h1 = document.createElement("h1");
h1.textContent = "Questions Overview";
let scroll = document.createElement("div");
scroll.classList.add("scroll-y");
let table = document.createElement("table");
table.classList.add("questions");
scroll.appendChild(table)
slide.appendChild(h1);
slide.appendChild(scroll);
slides.appendChild(slide);
let updateList = list => {
console.log(list);
let tr = document.createElement("tr");
let th1 = document.createElement("th");
th1.textContent = "Slide";
let th2 = document.createElement("th");
th2.innerHTML = "<i class=\"far fa-thumbs-up\"></i>";
let th3 = document.createElement("th");
th3.textContent = "Question";
tr.appendChild(th1);
tr.appendChild(th2);
tr.appendChild(th3);
table.appendChild(tr);
for (let comment of list) {
let tr = document.createElement("tr");
let td1 = document.createElement("td");
let link = document.createElement("a");
link.setAttribute("href", `#${comment.slide}`);
link.textContent = comment.slide;
td1.appendChild(link);
let td2 = document.createElement("td");
td2.textContent = comment.votes;
let td3 = document.createElement("td");
td3.innerHTML = comment.html;
tr.appendChild(td1);
tr.appendChild(td2);
tr.appendChild(td3);
table.appendChild(tr);
}
};
api
.getComments(publicUrl || deckId())
.then(updateList)
.catch(console.log);
}
function buildMenu(api, initialToken, publicUrl) {
var serverToken = initialToken;
function buildMenu() {
let updateMenu = list => {
for (let comment of list) {
......@@ -547,8 +457,8 @@ function buildMenu(api, initialToken, publicUrl) {
}
};
api
.getComments(publicUrl || deckId())
engine.api
.getComments(engine.deckId)
.then(updateMenu)
.catch(console.log);
}
......@@ -302,8 +302,7 @@ $body$
$if(decker-engine.base-url)$
<script type="module" crossorigin>
import {contactEngine} from "./$decker-support-dir$/js/engine.js";
contactEngine("$decker-engine.base-url$", "$decker-engine.public-url$")
.then(() => Reveal.sync());
contactEngine("$decker-engine.base-url$", "$decker-engine.deck-id$")
</script>
$endif$
......
......@@ -3,7 +3,7 @@ author: Henrik Tramberend
controls: 0
decker-engine:
base-url: 'https://tramberend.beuth-hochschule.de/decker'
public-url: 'https://tramberend.beuth-hochschule.de/public/decker/test/decks/engine-deck.html'
deck-id: 'https://tramberend.beuth-hochschule.de/public/decker/test/decks/engine-deck.html'
help: 0
menu: 0
title: Decker Engine Test
......
......@@ -380,13 +380,13 @@ meta data.
``` {.yaml}
decker-engine:
public-url: 'https://tramberend.beuth-hochschule.de/public/decker/test/decks/engine-deck.html'
deck-id: 'https://tramberend.beuth-hochschule.de/public/decker/test/decks/engine-deck.html'
```
If `decker-engine.public-url` is specified, it overrides the actual deck
If `decker-engine.deck-id` is specified, it overrides the actual deck
URL as far as deck identification for decker engine is concerned. The
questions shown if the deck is served locally will be the questions
added where added to the published deck.
that where added to the published deck.
## Quizzes
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment