diff --git a/client/dcwebui.ts b/client/dcwebui.ts index 79f303c..296f9e9 100644 --- a/client/dcwebui.ts +++ b/client/dcwebui.ts @@ -5,11 +5,34 @@ import * as io from 'socket.io-client' "use strict"; +/** + * Display value when loading a saved password + */ var SENTINEL_PASSWORD = "************"; -var CHAT_SCROLLBACK_LIMIT = 200; // Once over 2x $limit, the first $limit will be trimmed off the list + +/** + * Number of lines of chat to keep in the scroll area. + * Once there are over 2x $limit lines, the first $limit lines will be trimmed off the list + */ +var CHAT_SCROLLBACK_LIMIT = 200; + +/** + * Our externally-accessible URL + */ var EXTERN_ROOT = window.location.protocol + "//" + window.location.host + "/"; -var nmdc_escape = function(str) { +// Help out the braindead minifier, use these functions instead +var document_getElementById = document.getElementById; +var document_getElementsByClassName = document.getElementsByClassName; +var document_createElement = document.createElement; +var document_addEventListener = document.addEventListener; + +/** + * Encode a string for NMDC + * + * @param str + */ +var nmdc_escape = function(str: string): string { return ( (''+str).length ? (''+str).replace(/&/g,'&').replace(/\|/g,'|').replace(/\$/g,'$') @@ -17,14 +40,24 @@ var nmdc_escape = function(str) { ); }; -var hesc = function(s) { +/** + * Encode a string for HTML + * + * @param s + */ +var hesc = function(s: string): string { var filter = { '&': '&', '<': '<', '>': '>', '"': '"', '\'': ''' }; return s.toString().replace(/[&<>'"]/g, function(s) { return filter[s]; }); }; -var fmtBytes = function(b) { +/** + * Format a number of bytes as a human-readable string + * + * @param b + */ +var fmtBytes = function(b: number): string { if (b == 0) { return '(nothing)'; } @@ -35,12 +68,21 @@ var fmtBytes = function(b) { return parseFloat((b / Math.pow(k, i)).toFixed(3)) + sizes[i]; }; - -var urldesc = function(s) { +/** + * Decode a string that was previously encoded in raw-url format. + * + * @param s + */ +var urldesc = function(s: string):string { return decodeURIComponent(s.replace(/\+/g, " ")); } -var linkify = function(str) { +/** + * Enhance an HTML string by automatically making links clickable, etc. + * + * @param str An HTML-safe string + */ +var linkify = function(str : string):string { // n.b. str is already hesced return (str .replace( @@ -54,17 +96,39 @@ var linkify = function(str) { ); }; -var sanitise = function(s) { +/** + * Convert a plain-text string into an enhanced, HTML-safe string. + * + * @param s + */ +var sanitise = function(s:string): string { return linkify(hesc(s)); }; -var textContent = function($el) { - if ($el.textContent) return $el.textContent; - if ($el.innerText) return $el.innerText; +/** + * Retrieve the plain-text content from an HTML element in a browser-compatible way. + * + * @param $el + */ +var textContent = function($el : HTMLElement) : string { + if ($el.textContent) { + return $el.textContent; + } + + if ($el.innerText) { + return $el.innerText; + } + return ""; }; -var negmod = function(l, r) { +/** + * Calculate the positive modulo of (l % r). + * + * @param l + * @param r + */ +var negmod = function(l:number, r:number):number { var ret = l % r; if (l < 0) { return ret + r; @@ -73,16 +137,27 @@ var negmod = function(l, r) { } }; -// @ref https://developer.mozilla.org/en/docs/Web/API/WindowBase64/Base64_encoding_and_decoding -var b64 = function(str) { +/** + * Encode a string to base64 in a UTF8-safe way. + * + * @ref https://developer.mozilla.org/en/docs/Web/API/WindowBase64/Base64_encoding_and_decoding + * @param str + */ +var b64 = function(str : string):string { return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) { return String.fromCharCode(parseInt('0x' + p1)); })).replace(/=/g, ''); } -// @ref https://gist.github.com/eligrey/1276030 -var appendInnerHTML = function($el, html) { - var child = document.createElement("span"); +/** + * Append content to an HTML element in a browser-compatible way. + * + * @ref https://gist.github.com/eligrey/1276030 + * @param $el + * @param html + */ +var appendInnerHTML = function($el: HTMLElement, html:string) { + var child = document_createElement("span"); child.innerHTML = html; var node; @@ -91,30 +166,48 @@ var appendInnerHTML = function($el, html) { } }; -// @ref http://stackoverflow.com/a/5598797 -function getOffsetLeft( elem ) { +/** + * Retrieve the left offset of a DOM element relative to the document. + * + * @ref http://stackoverflow.com/a/5598797 + * @param elem + */ +function getOffsetLeft( elem: HTMLElement ):number { var offsetLeft = 0; do { if (!isNaN(elem.offsetLeft)) { offsetLeft += elem.offsetLeft; } - } while (elem = elem.offsetParent); + } while (elem = elem.offsetParent); return offsetLeft; } -function getOffsetTop( elem ) { +/** + * Retrieve the top offset for a DOM element relative to the document. + * +* @ref http://stackoverflow.com/a/5598797 + * @param elem + */ +function getOffsetTop( elem: HTMLElement ):number { var offsetTop = 0; do { if (!isNaN(elem.offsetTop)) { offsetTop += elem.offsetTop; } - } while (elem = elem.offsetParent); + } while (elem = elem.offsetParent); return offsetTop; } /* */ -var date_format = function(d, format) { +/** + * Format a string in a date format, analgous to strftime(). + * + * @param d + * @param format Formatting string, supporting HisYmd character specifiers + * @return Plain text string + */ +var date_format = function(d:Date, format:string):string { var pad = function(s) { return (s < 10) ? '0'+s : ''+s ; }; @@ -123,15 +216,20 @@ var date_format = function(d, format) { ret = ret.replace(/H/g, pad(d.getHours())); ret = ret.replace(/i/g, pad(d.getMinutes())); ret = ret.replace(/s/g, pad(d.getSeconds())); - ret = ret.replace(/Y/g, d.getFullYear()); + ret = ret.replace(/Y/g, "" + d.getFullYear()); ret = ret.replace(/m/g, pad(d.getMonth() + 1)); ret = ret.replace(/d/g, pad(d.getDate())); return ret; }; -/* */ - -var notify = function(title, body, tab) { +/** + * Emit an HTML5 notification. + * + * @param title + * @param body + * @param tab + */ +var notify = function(title:string, body:string, tab:string) { if (!("Notification" in window)) { return; // not supported by browser } @@ -164,7 +262,7 @@ var notify = function(title, body, tab) { /* Tab writers */ var write = function(tab) { - var $tab = document.getElementById("inner-"+tab); + var $tab = document_getElementById("inner-"+tab); return { 'cls': function() { $tab.innerHTML = ''; @@ -240,11 +338,11 @@ var userlist = { 'add': function(u) { if (this.has(u)) return; - var userlists = document.getElementsByClassName("userlist"); + var userlists = document_getElementsByClassName("userlist"); for (var l = 0, e = userlists.length; l !== e; ++l) { var userlist = userlists[l]; - var to_add = document.createElement('li'); + var to_add = document_createElement('li'); to_add.className = "user-" + b64(u); to_add.innerHTML = hesc(u); @@ -269,7 +367,7 @@ var userlist = { return this; }, 'del': function(u) { - var userlists = document.getElementsByClassName("userlist"); + var userlists = document_getElementsByClassName("userlist"); for (var l = 0, e = userlists.length; l !== e; ++l) { if (! userlists[l].children) continue; var userlist = userlists[l]; @@ -286,7 +384,7 @@ var userlist = { return this; }, 'clear': function() { - var userlists = document.getElementsByClassName("userlist"); + var userlists = document_getElementsByClassName("userlist"); for (var i in userlists) { if (! userlists[i].children) continue; var userlist = userlists[i]; @@ -299,22 +397,22 @@ var userlist = { return this; }, 'names': function() { - var userlist = document.getElementsByClassName("userlist")[0].children; + var userlist = document_getElementsByClassName("userlist")[0].children; var ret = []; for (var i = 0, e = userlist.length; i < e; ++i) { - ret.push( textContent(userlist[i]) ); + ret.push( textContent((userlist[i])) ); } return ret; }, 'has': function(u) { - return document.getElementsByClassName("user-" + b64(u)).length !== 0; /* there are two - large and non-large */ + return document_getElementsByClassName("user-" + b64(u)).length !== 0; /* there are two - large and non-large */ }, 'count': function() { - return document.getElementsByClassName("userlist")[0].children.length; + return document_getElementsByClassName("userlist")[0].children.length; }, 'setInfo': function(nick, props) { var baseClass = "user-" + b64(nick); - var $el = document.getElementsByClassName("" + baseClass); + var $el = document_getElementsByClassName("" + baseClass); var prop_str = []; if (props.Description.length > 0) { prop_str.push(props.Description); @@ -343,7 +441,7 @@ var userlist = { }; var submit = function() { - var str = (document.getElementById("chatbox")).value; + var str = (document_getElementById("chatbox")).value; if (! str.length) return; if (hub_state === STATE_READY_FOR_LOGIN) { @@ -386,7 +484,7 @@ var submit = function() { write("tab-main").system("Invalid internal state."); } - (document.getElementById("chatbox")).value = ''; + (document_getElementById("chatbox")).value = ''; }; /* page visibility */ @@ -412,7 +510,7 @@ var pagevis_setup = function(fnActive, fnInactive) { pagevis_currently_visible = true } else { - document.addEventListener(vc, function() { + document_addEventListener(vc, function() { if (document[h]) { pagevis_currently_visible = false; fnInactive(); @@ -433,13 +531,13 @@ var pagevis_setup = function(fnActive, fnInactive) { */ var tab_set = function(tab) { - var tabs = document.getElementsByClassName("tabpane"); + var tabs = document_getElementsByClassName("tabpane"); for (var i in tabs) { try { ( tabs[i]).style.display = (tabs[i].id === tab ? 'block' : 'none'); } catch (e) {}; } - var tabitems = document.getElementsByClassName("tabitem"); + var tabitems = document_getElementsByClassName("tabitem"); for (var i in tabitems) { try { // Update UNREAD/SELECTED flags for the target @@ -467,13 +565,13 @@ var tab_set = function(tab) { updateTitle(); write(tab).scroll(); - document.getElementById("chatbox").focus(); + document_getElementById("chatbox").focus(); last_tab = tab; }; var tab_new = function(id, name) { - appendInnerHTML(document.getElementById("bar"), + appendInnerHTML(document_getElementById("bar"), '
'+ ''+ hesc(name)+ @@ -481,7 +579,7 @@ var tab_new = function(id, name) { '×'+ '
' ); - appendInnerHTML(document.getElementById("extratabs"), + appendInnerHTML(document_getElementById("extratabs"), ' ' @@ -494,10 +592,10 @@ var tab_free = function(id) { if (id === "tab-main") return; // remove tab item and body - var $el = document.getElementById("tabitem-"+id); + var $el = document_getElementById("tabitem-"+id); $el.parentNode.removeChild($el); - $el = document.getElementById(""+id); + $el = document_getElementById(""+id); $el.parentNode.removeChild($el); // clear from PM tabs @@ -532,7 +630,7 @@ var noprop = function(ev) { } var tab_addHandlers = function() { - var tabitems = document.getElementsByClassName("tabitem"); + var tabitems = document_getElementsByClassName("tabitem"); for (var i = 0; i < tabitems.length; i++) { if (! tabitems[i]) { continue; @@ -545,7 +643,7 @@ var tab_addHandlers = function() { }; } - var tabclosers = document.getElementsByClassName("tab-closer"); + var tabclosers = document_getElementsByClassName("tab-closer"); for (var i = 0; i < tabclosers.length; i++) { if (! tabclosers[i]) { continue; @@ -587,7 +685,7 @@ var tabcomplete_state = ''; var tabcompletion_start = function(direction) { - var cursor = (document.getElementById("chatbox")).value.replace(/^.*\s([^\s]+)$/, '$1'); + var cursor = (document_getElementById("chatbox")).value.replace(/^.*\s([^\s]+)$/, '$1'); if (tabcomplete_state === '') { // new tab completion @@ -623,7 +721,7 @@ var tabcompletion_start = function(direction) { // Replace in textbox - var $chatbox = (document.getElementById("chatbox")); + var $chatbox = (document_getElementById("chatbox")); var chatprefix = $chatbox.value.substr(0, $chatbox.value.length - cursor.length); $chatbox.value = chatprefix + targetName; @@ -639,11 +737,11 @@ var tabcompletion_inactive = function() { var MenuList = function(el) { this.el = el; - this.div = document.createElement("div"); + this.div = document_createElement("div"); this.div.classList.add("menu"); this.div.style.position = "absolute"; - this.ul = document.createElement("ul"); + this.ul = document_createElement("ul"); this.div.appendChild(this.ul); document.body.appendChild(this.div); @@ -655,7 +753,7 @@ MenuList.prototype.clear = function() { } }; MenuList.prototype.add = function(txt, cb) { - var li = document.createElement("li"); + var li = document_createElement("li"); li.innerHTML = txt; li.onclick = cb; this.ul.appendChild(li); @@ -676,7 +774,7 @@ MenuList.prototype.toggle = function() { /* */ -var menu = new MenuList(document.getElementById("menubutton")); +var menu = new MenuList(document_getElementById("menubutton")); menu.reset = function() { this.clear(); @@ -779,7 +877,7 @@ var toggle_joinparts = function(ev) { var updateTitle = function() { var prefix = ""; - var unrTabs = document.getElementsByClassName("unread"); + var unrTabs = document_getElementsByClassName("unread"); if (unrTabs.length === 1 && unrTabs[0].getAttribute('data-tab') == "tab-main") { prefix = "[" + mainchat_unread_count + " NEW] " } else if (unrTabs.length > 0) { @@ -885,7 +983,7 @@ var scrollback_move = function(delta) { } } - (document.getElementById("chatbox")).value = chat_scrollback[chat_scrollback_index]; + (document_getElementById("chatbox")).value = chat_scrollback[chat_scrollback_index]; }; /* */ @@ -906,7 +1004,7 @@ var persistence_get = function(key, fallback) { var transition = function(new_state) { hub_state = new_state; - var $chatbox = (document.getElementById("chatbox")); + var $chatbox = (document_getElementById("chatbox")); switch(new_state) { case STATE_DISCONNECTED: { @@ -939,8 +1037,8 @@ var tab_is_visible = function(tabref) { } var tab_mark_unread = function(tabref) { - if (document.getElementById("tabitem-"+tabref).className.indexOf('unread') === -1) { - document.getElementById("tabitem-"+tabref).className += " unread"; + if (document_getElementById("tabitem-"+tabref).className.indexOf('unread') === -1) { + document_getElementById("tabitem-"+tabref).className += " unread"; updateTitle(); } } @@ -957,21 +1055,21 @@ var contented_load = function() { var onceSDKLoaded = function() { contented.init("#inner-tab-main", function(items) { - var val = ( document.getElementById("chatbox")).value; + var val = ( document_getElementById("chatbox")).value; for (var i = 0; i < items.length; ++i) { if (val.length > 0) { val += " "; } val += contented.getPreviewURL(items[i]); } - ( document.getElementById("chatbox")).value = val; + ( document_getElementById("chatbox")).value = val; }); }; if (contented_loaded_sdk) { onceSDKLoaded(); } else { - var scriptElement = document.createElement('script'); + var scriptElement = document_createElement('script'); scriptElement.onload = function() { contented_loaded_sdk = true; onceSDKLoaded(); @@ -992,13 +1090,13 @@ window.onload = function() { // HTML event handlers - document.getElementById("form-none").onsubmit = function(ev) { + document_getElementById("form-none").onsubmit = function(ev) { submit(); return noprop(ev); // don't submit form }; - document.getElementById("chatbox").onkeydown = function(ev) { + document_getElementById("chatbox").onkeydown = function(ev) { if (ev.keyCode === 9 /* Tab */) { tabcompletion_start( ev.shiftKey ? -1 : 1 ); return noprop(ev); @@ -1026,7 +1124,7 @@ window.onload = function() { usermenu.hide(); }; - document.getElementById("menubutton").onclick = function(ev) { + document_getElementById("menubutton").onclick = function(ev) { menu.toggle(); return noprop(ev); }; @@ -1084,7 +1182,7 @@ window.onload = function() { if (pre_login.indexOf(":") !== -1) { pre_login = pre_login.substr(0, pre_login.indexOf(":")) + ":" + SENTINEL_PASSWORD; } - (document.getElementById("chatbox")).value = pre_login; + (document_getElementById("chatbox")).value = pre_login; if (have_cleared_once) { // re-log-in automatically