// started on main page load
function init() {
init_board();
}
var revision;
// helper for breaking feedback loop
var caretpos = 0;
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;
}
textAreaSetPos("board", pos);
revision = rev;
}
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 blob = new Blob([png], {type: 'image/png'});
var url = URL.createObjectURL(blob);
var img = document.getElementById("qrcode");
img.src = url;
showQRWindow();
}
function on_modify_ack(rev)
{
revision = rev;
}
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("cursorpos")[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 == "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 init_board() {
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;
}
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(); });
websocket.send("getfile" + get_id() + "");
};
websocket.onclose = function(e) {
alert("Server connection closed.");
};
websocket.onerror = function(e) {
alert("Error: Server connection closed.");
};
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("newid");
}
// local change done
function on_input()
{
var parser = new DOMParser();
var xmlDocument = parser.parseFromString("", "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);
var dataElement = xmlDocument.createElement("data");
dataElement.appendChild(document.createTextNode(document.getElementById("board").value));
requestElement.appendChild(dataElement);
websocket.send(new XMLSerializer().serializeToString(xmlDocument));
}
// for cursor position
function on_selectionchange(pos)
{
var parser = new DOMParser();
var xmlDocument = parser.parseFromString("", "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 dataElement = xmlDocument.createElement("pos");
dataElement.appendChild(document.createTextNode(pos));
requestElement.appendChild(dataElement);
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()
{
var parser = new DOMParser();
var xmlDocument = parser.parseFromString("", "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));
}