summaryrefslogtreecommitdiffhomepage
path: root/html/webchat.js
diff options
context:
space:
mode:
Diffstat (limited to 'html/webchat.js')
-rw-r--r--html/webchat.js351
1 files changed, 351 insertions, 0 deletions
diff --git a/html/webchat.js b/html/webchat.js
new file mode 100644
index 0000000..379c757
--- /dev/null
+++ b/html/webchat.js
@@ -0,0 +1,351 @@
+// started on main page load
+function init() {
+ init_board();
+}
+
+var revision;
+var modify_in_progress = 0;
+var baseline = ""; // data contents relating to revision, acknowledged by server
+var baseline_candidate = ""; // will become baseline, after ack by server
+
+// helper for breaking feedback loop
+var caretpos = 0;
+
+function set_status(message)
+{
+ if (message == "") {
+ document.getElementById("status").textContent = message;
+ document.getElementById("status").style.display = 'inline';
+ } else {
+ document.getElementById("status").textContent = "";
+ document.getElementById("status").style.display = 'none';
+ }
+}
+
+function showQRWindow()
+{
+ document.getElementById("qrwindow").style.display = 'block';
+}
+
+function hideQRWindow()
+{
+ document.getElementById("qrwindow").style.display = 'none';
+}
+
+var websocket;
+
+//
+// Callbacks for websocket data of different types
+//
+
+function on_getfile(data, rev, pos)
+{
+ var board = document.getElementById("board");
+ if (board.value != data) {
+ board.value = data;
+ }
+ revision = rev;
+ baseline = data;
+ textAreaSetPos("board", pos);
+}
+
+function on_getdiff(diff, rev, pos)
+{
+ if (rev != revision + 1)
+ console.log("Revision skipped on diff receive: " + rev + " after " + revision);
+
+ var board = document.getElementById("board");
+
+ var old_version_ptr = allocateUTF8(board.value);
+ var diff_ptr = allocateUTF8(new XMLSerializer().serializeToString(diff));
+ var new_version_ptr = Module._diff_apply(old_version_ptr, diff_ptr);
+ var data = UTF8ToString(new_version_ptr);
+ board.value = data;
+ _free(old_version_ptr);
+ _free(new_version_ptr);
+ _free(diff_ptr);
+
+ revision = rev;
+ baseline = data;
+ textAreaSetPos("board", pos);
+}
+
+function on_getpos(pos)
+{
+ textAreaSetPos("board", pos);
+}
+
+function on_newid(id)
+{
+ var new_location = document.location.origin + document.location.pathname + '?id=' + id;
+ window.location.href = new_location;
+}
+
+function on_qrcode(png)
+{
+ var url = "data:image/png;base64," + png;
+ var img = document.getElementById("qrcode");
+ img.src = url;
+ showQRWindow();
+}
+
+function on_version(version)
+{
+ document.getElementById("version").textContent = version;
+}
+
+function on_pdf(pdf)
+{
+ var a = document.getElementById("download-a");
+ a.href = "data:application/pdf;base64," + pdf;
+ a.download = get_id() + ".pdf"
+ a.click();
+}
+
+function on_modify_ack(rev)
+{
+ if (rev != revision + 1)
+ console.log("Revision skipped on published local change: " + rev + " after " + revision);
+
+ revision = rev;
+ baseline = baseline_candidate;
+ modify_in_progress = 0;
+}
+
+function on_message(e) {
+ var parser = new DOMParser();
+ var xmlDocument = parser.parseFromString(e.data, "text/xml");
+
+ var type = xmlDocument.getElementsByTagName("type")[0].textContent;
+
+ if (type == "getfile") {
+ on_getfile(xmlDocument.getElementsByTagName("data")[0].textContent,
+ parseInt(xmlDocument.getElementsByTagName("revision")[0].textContent),
+ parseInt(xmlDocument.getElementsByTagName("pos")[0].textContent));
+ } else if (type == "getdiff") {
+ on_getdiff(xmlDocument.getElementsByTagName("diff")[0],
+ parseInt(xmlDocument.getElementsByTagName("revision")[0].textContent),
+ parseInt(xmlDocument.getElementsByTagName("pos")[0].textContent));
+ } else if (type == "getpos") {
+ on_getpos(parseInt(xmlDocument.getElementsByTagName("pos")[0].textContent));
+ } else if (type == "modify") {
+ on_modify_ack(parseInt(xmlDocument.getElementsByTagName("revision")[0].textContent));
+ } else if (type == "newid") {
+ on_newid(xmlDocument.getElementsByTagName("id")[0].textContent);
+ } else if (type == "qrcode") {
+ on_qrcode(xmlDocument.getElementsByTagName("png")[0].textContent);
+ } else if (type == "version") {
+ on_version(xmlDocument.getElementsByTagName("version")[0].textContent);
+ } else if (type == "pdf") {
+ on_pdf(xmlDocument.getElementsByTagName("pdf")[0].textContent);
+ } else if (type == "error") {
+ alert(xmlDocument.getElementsByTagName("message")[0].textContent);
+ } else {
+ alert("Unhandled message type: " + e.data + "|" + type);
+ }
+}
+
+function handleSelection() {
+ const activeElement = document.activeElement
+
+ if (activeElement && activeElement.id === 'board') {
+ if (caretpos != activeElement.selectionStart) {
+ on_selectionchange(activeElement.selectionStart);
+ caretpos = activeElement.selectionStart;
+ }
+ }
+}
+
+function connect_websocket() {
+ document.getElementById("reconnect").style.display = 'none';
+ set_status("Connecting...");
+ var newlocation = location.origin + location.pathname;
+ newlocation = newlocation.replace(/^http/, 'ws');
+ if (newlocation.slice(-1) != "/")
+ newlocation += "/";
+ newlocation += "websocket";
+
+ websocket = new WebSocket(newlocation);
+
+ websocket.onmessage = function(e) { on_message(e); };
+
+ websocket.onopen = function(e) {
+ const searchParams = (new URL(document.location)).searchParams;
+ if (!searchParams.has('id')) {
+ redirect_to_new_page();
+ return;
+ }
+
+ websocket.send("<request><command>getversion</command></request>");
+ websocket.send("<request><command>getfile</command><id>" + get_id() + "</id></request>");
+ set_status(""); // ok
+ document.getElementById("board").focus();
+ };
+
+ websocket.onclose = function(e) {
+ alert("Server connection closed.");
+ document.getElementById("reconnect").style.display = 'inline';
+ document.getElementById("reconnect").focus();
+ };
+
+ websocket.onerror = function(e) {
+ alert("Error: Server connection closed.");
+ document.getElementById("reconnect").style.display = 'inline';
+ document.getElementById("reconnect").focus();
+ };
+}
+
+// button in html
+function on_reconnect_click() {
+ connect_websocket();
+}
+
+function init_board() {
+ set_status("Loading...");
+ Module.onRuntimeInitialized = () => {
+ connect_websocket();
+ };
+
+ var board = document.getElementById("board");
+ board.addEventListener("input", function() {on_input(); });
+ // Need this workaround (different from direct on_selectionchange) for Chrome.
+ // Otherwise, callback will not be called on Chrome.
+ document.addEventListener("selectionchange", handleSelection);
+ //board.addEventListener("selectionchange", function() {on_selectionchange(); });
+
+ document.getElementById("qrwindow").onclick = function() {
+ hideQRWindow();
+ }
+
+ document.onkeydown = function(evt) {
+ if (evt.key == "Escape") {
+ hideQRWindow();
+ }
+ }
+
+ document.getElementById("board").focus();
+}
+
+function get_id()
+{
+ const searchParams = (new URL(document.location)).searchParams;
+ return searchParams.get('id');
+}
+
+// from html
+function on_new_page()
+{
+ redirect_to_new_page();
+}
+
+function redirect_to_new_page()
+{
+ websocket.send("<request><command>newid</command></request>");
+}
+
+// local change done
+function on_input()
+{
+ if (modify_in_progress == 1) {
+ console.log("Deferring on_input handler by 100ms");
+ setTimeout(function(){on_input();}, 100); // re-try after 100ms
+ return;
+ }
+ modify_in_progress = 1;
+
+ var parser = new DOMParser();
+ var xmlDocument = parser.parseFromString("<request></request>", "text/xml");
+
+ var requestElement = xmlDocument.getElementsByTagName("request")[0];
+
+ var commandElement = xmlDocument.createElement("command");
+ commandElement.appendChild(document.createTextNode("modify"));
+ requestElement.appendChild(commandElement);
+
+ var idElement = xmlDocument.createElement("id");
+ idElement.appendChild(document.createTextNode(get_id()));
+ requestElement.appendChild(idElement);
+
+ baseline_candidate = document.getElementById("board").value;
+
+ if (baseline == baseline_candidate) {
+ modify_in_progress = 0;
+ return;
+ }
+
+ var revisionElement = xmlDocument.createElement("baserev");
+ revisionElement.appendChild(document.createTextNode(revision));
+ requestElement.appendChild(revisionElement);
+
+ var old_version = allocateUTF8(baseline);
+ var new_version = allocateUTF8(baseline_candidate);
+ var diff = Module._diff_create(old_version, new_version);
+ var diffDocument = parser.parseFromString(UTF8ToString(diff), "text/xml");
+ _free(old_version);
+ _free(new_version);
+ _free(diff);
+ requestElement.appendChild(xmlDocument.importNode(diffDocument.getElementsByTagName("diff")[0], true));
+
+ var posElement = xmlDocument.createElement("pos");
+ posElement.appendChild(document.createTextNode(document.getElementById("board").selectionStart));
+ requestElement.appendChild(posElement);
+
+ websocket.send(new XMLSerializer().serializeToString(xmlDocument));
+}
+
+// for cursor position
+function on_selectionchange(pos)
+{
+ var parser = new DOMParser();
+ var xmlDocument = parser.parseFromString("<request></request>", "text/xml");
+
+ var requestElement = xmlDocument.getElementsByTagName("request")[0];
+
+ var commandElement = xmlDocument.createElement("command");
+ commandElement.appendChild(document.createTextNode("cursorpos"));
+ requestElement.appendChild(commandElement);
+
+ var idElement = xmlDocument.createElement("id");
+ idElement.appendChild(document.createTextNode(get_id()));
+ requestElement.appendChild(idElement);
+
+ var posElement = xmlDocument.createElement("pos");
+ posElement.appendChild(document.createTextNode(pos));
+ requestElement.appendChild(posElement);
+
+ websocket.send(new XMLSerializer().serializeToString(xmlDocument));
+}
+
+function textAreaSetPos(id, pos)
+{
+ if (document.getElementById(id).selectionStart != pos) {
+ document.getElementById(id).selectionStart = pos;
+ document.getElementById(id).selectionEnd = pos;
+ caretpos = pos;
+ }
+}
+
+// HTML button
+function on_qrcode_click()
+{
+ var parser = new DOMParser();
+ var xmlDocument = parser.parseFromString("<request></request>", "text/xml");
+
+ var requestElement = xmlDocument.getElementsByTagName("request")[0];
+
+ var commandElement = xmlDocument.createElement("command");
+ commandElement.appendChild(document.createTextNode("qrcode"));
+ requestElement.appendChild(commandElement);
+
+ var idElement = xmlDocument.createElement("url");
+ idElement.appendChild(document.createTextNode(document.location));
+ requestElement.appendChild(idElement);
+
+ websocket.send(new XMLSerializer().serializeToString(xmlDocument));
+}
+
+function on_pdf_click()
+{
+ websocket.send("<request><command>pdf</command><id>" + get_id() + "</id></request>");
+}
+