Compare commits
89 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1c7842c182 | |||
| 9e33e50986 | |||
| 7f618db70a | |||
| 7894355647 | |||
| af324441b7 | |||
| d862a3f703 | |||
| 59c118dc34 | |||
| 42a8298362 | |||
| c4e37bf47d | |||
| 95d56dbca2 | |||
| 761d0bfad5 | |||
| 1d3f16e6c6 | |||
| e5ceadb03a | |||
| 6999069fd7 | |||
| 5da61d5922 | |||
| d33ba5c085 | |||
| a28e5ce9b0 | |||
| 3448cb7eeb | |||
| ba941adfdd | |||
| 48d96f9efe | |||
| 1a1f361e60 | |||
| 3f1f575652 | |||
| e2af839101 | |||
| 659576c7e1 | |||
| 0c5279e254 | |||
| 99cf8ebe49 | |||
| 1189d6fe60 | |||
| 96c3e6ea7d | |||
| 1a64f79394 | |||
| 692537c6f0 | |||
| d2b1d23ba6 | |||
| bc80c94a6f | |||
| fd79d94a20 | |||
| d17ad14914 | |||
| 763f5030d1 | |||
| aa8d89d14b | |||
| d937491545 | |||
| 226e6751c6 | |||
| 9060b87e94 | |||
| 78b7af7fbc | |||
| ff075e2e7e | |||
| ef9057383d | |||
| 0951bf821e | |||
| a60e4bb0c2 | |||
| 7b5dbde7a6 | |||
| 3c2f0c7368 | |||
| 1cc6cd2b90 | |||
| 3f077f15f0 | |||
| ade4439f92 | |||
| edffaac74b | |||
| ceb7420e18 | |||
| 56ced01e97 | |||
| 3c1db1266e | |||
| 4a4e9e694d | |||
| c619be5917 | |||
| afd190b7cc | |||
| a927add8c5 | |||
| 0a5db6a015 | |||
| e15a6afceb | |||
| c19b0fe521 | |||
| 1eaabb099d | |||
| b26b3a695e | |||
| b30beac2b5 | |||
| 39e84f2744 | |||
| 7ceed88dfa | |||
| c7e40ab6c1 | |||
| 35994e81c6 | |||
| 2c94713ba0 | |||
| e9b3fedb17 | |||
| d0219cb16a | |||
| d0d52e931e | |||
| 0ec9ae30b4 | |||
| 490d20b807 | |||
| 299a1c12e1 | |||
| a9dc45f727 | |||
| 5f8544f031 | |||
| ea87419abc | |||
| d5e8e051e3 | |||
| 656125e790 | |||
| 0892666ead | |||
| 6fef5c37cb | |||
| 59abd3c50c | |||
| 610563b35c | |||
| f3ad1fba8a | |||
| 9268c4da4c | |||
| 9361e7ddec | |||
| ee59a4a773 | |||
| 2047762e62 | |||
| 16aa97b6e5 |
@@ -2,6 +2,7 @@ mode:regex
|
||||
|
||||
\.exe$
|
||||
^nmdc-webfrontend\.conf$
|
||||
^clientpack/
|
||||
|
||||
^_dist/
|
||||
^bindata\.go$
|
||||
6
.hgtags
Normal file
6
.hgtags
Normal file
@@ -0,0 +1,6 @@
|
||||
769fad81e3f8db8f7e5f5c164656a382a169d735 release-1.0.0
|
||||
9ed95938d809a8226aca529e34b655e6d8c8c379 release-1.0.1
|
||||
46fe533682419c8a519836ac95b5575053aa0fa8 release-1.0.2
|
||||
a2c92b262f339f82eb01c8d92dda252a27432255 release-1.1.0
|
||||
d14041daa7bbbd37ea2ff47aa978b9595af67ca3 release-1.1.1
|
||||
7278eb0d067d8ed2a653de6a1feeeb7f76fb9891 release-1.1.2
|
||||
21
Config.go
21
Config.go
@@ -2,22 +2,21 @@ package main
|
||||
|
||||
type Config struct {
|
||||
App struct {
|
||||
Name string
|
||||
Version string
|
||||
MotdHTML string
|
||||
Debug bool
|
||||
MotdHTML string `json:"motd"`
|
||||
}
|
||||
|
||||
Web struct {
|
||||
Port int
|
||||
BindTo string
|
||||
Extern string
|
||||
Title string
|
||||
Port int `json:"port"`
|
||||
BindTo string `json:"bind_to"`
|
||||
Title string `json:"title"`
|
||||
CustomFavicon bool `json:"custom_favicon"`
|
||||
|
||||
ExternalWebroot bool `json:"external_webroot,omitempty"`
|
||||
}
|
||||
|
||||
Hub struct {
|
||||
Address string
|
||||
Port int
|
||||
Tag string
|
||||
Address string `json:"address"`
|
||||
Port int `json:"port"`
|
||||
Tag string `json:"tag"`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,69 @@ Chat in real-time on your NMDC hub using a web browser. This project forks and d
|
||||
|
||||
Written in Golang
|
||||
|
||||
Tags: nmdc
|
||||
|
||||
=UPGRADING FROM DCWEBUI2=
|
||||
|
||||
- The configuration file format is identical, but please now ensure it's valid json instead of just a .js file. This means no assignment, use double-quoted strings, and no comments.
|
||||
- The configuration file content is identical between nmdc-webfrontend 1.0.0 and dcwebui2 1.3.0, but please now ensure it's valid JSON instead of arbitrary javascript. This means no assignment, use double-quoted strings, and no comments.
|
||||
- Future changes to the configuration file since nmdc-webfrontend 1.0.0 are backward compatible (see the changelog for more details).
|
||||
|
||||
=CHANGELOG=
|
||||
|
||||
2017-02-11 1.1.3
|
||||
- Feature: Display user IP address on hover, if available
|
||||
- Enhancement: Allow clicking on popup notifications
|
||||
- Enhancement: Only show 'popups enabled' notification when enabling for the first time, not persistent page load
|
||||
- Update libnmdc to 0.13
|
||||
- Fix a cosmetic issue with not displaying non-zero user share sizes
|
||||
|
||||
2017-02-06 1.1.2
|
||||
- Autodetect 'extern', no need to include it in config files
|
||||
- Enhancement: Remove redundant request, for a faster page load
|
||||
- Display server version number in log file and in response headers
|
||||
- Fix a cosmetic issue with spacing around user count in page title
|
||||
|
||||
2017-02-06 1.1.1
|
||||
- Fix an issue with malformed content in minified build
|
||||
|
||||
2017-02-06 1.1.0
|
||||
- Feature: Remember last username/password for login; remember last "show joins/parts" status
|
||||
- Feature: Display user details on hover (description, email, client tag, share size)
|
||||
- Feature: Optional desktop notifications for background PMs (not possible in incognito)
|
||||
- Feature: Automatically reconnect with the same username/password if connection was lost
|
||||
- Feature: Re-enter last message (Ctrl+Up, Ctrl+Down)
|
||||
- Feature: Set custom date/time format (Minutes, Seconds, Full), remembered for next session
|
||||
- Feature: Clickable magnet links
|
||||
- Feature: Display unread main-chat message count in the page title if the window is inactive
|
||||
- Feature: Add warning message when closing tab while still connected (optional preference, disabled by default, will be remembered).
|
||||
- Feature: Admin option to load a custom favicon (set `web.custom_favicon=true` and place a `favicon.ico` in the current directory)
|
||||
- Feature: Admin option to use external web resources (set `web.external_webroot=true` and use the /client/ directory)
|
||||
- Enhancement: Higher resolution favicon
|
||||
- Enhancement: Display operators in green in the user list
|
||||
- Enhancement: Enable spellcheck for text input once logged in
|
||||
- Enhancement: Prevent sending referrer to remote URLs
|
||||
- Enhancement: Display joins/parts and connection/disconnection messages in PM tabs
|
||||
- Enhancement: Support Shift+Tab to autocomplete backward
|
||||
- Enhancement: Support unread status for the main tab
|
||||
- Enhancement: Improve page load time via minification
|
||||
- Remove unused options from the config file
|
||||
- Update socket.io to 1.7.2
|
||||
- Update libnmdc to 0.12
|
||||
- Add margin between bottom of the text area and the text input box
|
||||
- Fix a cosmetic issue with collapsing consecutive spaces in posted messages
|
||||
- Fix a cosmetic issue with text size adjustment on mobile devices
|
||||
- Fix a cosmetic issue with clearing the screen on reconnection
|
||||
- Fix a cosmetic issue with not clearing the userlist on certain types of network error
|
||||
- Fix a cosmetic issue with closed PM tabs reappearing in some cases
|
||||
- Fix a cosmetic issue with marking all PM tabs as read when switching to a single one
|
||||
|
||||
2016-11-29 1.0.2
|
||||
- Rebuild with libnmdc 0.11
|
||||
- Fix an issue with not setting a version in the client tag
|
||||
|
||||
2016-10-08 1.0.1
|
||||
- Fix an issue with backward compatibility with `dcwebui2` configuration file format
|
||||
|
||||
2016-10-08 1.0.0
|
||||
- Port `dcwebui2` from Node.js (Javascript) to Go
|
||||
- Fix a cosmetic issue with not clearing userlist on disconnection
|
||||
|
||||
19
build.sh
19
build.sh
@@ -88,7 +88,7 @@ single_build() {
|
||||
# GOARCH/GOOS supplied in function env
|
||||
go build \
|
||||
-a \
|
||||
-ldflags '-s -w' \
|
||||
-ldflags "-s -w -X main.VERSION=nmdc-webfrontend/${version}" \
|
||||
-gcflags "-trimpath=${GOPATH}" \
|
||||
-asmflags "-trimpath=${GOPATH}" \
|
||||
-o "$(pathfix "${tmpdir}/${local_bin_name}")"
|
||||
@@ -158,13 +158,26 @@ main() {
|
||||
if [[ -f ./bindata.go ]] ; then
|
||||
rm ./bindata.go
|
||||
fi
|
||||
go-bindata -nomemcopy -prefix client client
|
||||
|
||||
php clientpack.php
|
||||
go-bindata -nomemcopy -prefix clientpack clientpack
|
||||
|
||||
GOARCH=amd64 GOOS=windows single_build "$version"
|
||||
GOARCH=386 GOOS=windows single_build "$version"
|
||||
GOARCH=amd64 GOOS=linux single_build "$version"
|
||||
GOARCH=386 GOOS=linux single_build "$version"
|
||||
|
||||
# Also make source tarball
|
||||
|
||||
local SOURCE_FILES=(
|
||||
client/
|
||||
build.sh
|
||||
clientpack.php
|
||||
Config.go
|
||||
main.go
|
||||
nmdc-webfrontend.conf.SAMPLE
|
||||
)
|
||||
XZ_OPT='-9' tar caf "_dist/$(get_package_name)-${version}-src.tar.xz" "${SOURCE_FILES[@]}" --owner=0 --group=0 >/dev/null
|
||||
|
||||
echo "[INFO] Build complete."
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/* dcwebui.css */
|
||||
|
||||
* {
|
||||
-webkit-text-size-adjust:100%
|
||||
-moz-text-size-adjust:100%
|
||||
-ms-text-size-adjust:100%
|
||||
-webkit-text-size-adjust:100%;
|
||||
-moz-text-size-adjust:100%;
|
||||
-ms-text-size-adjust:100%;
|
||||
}
|
||||
|
||||
html,body {
|
||||
@@ -180,13 +180,13 @@ html,body {
|
||||
overflow-y:auto;
|
||||
-webkit-transition: all 0.25s ease-in-out;
|
||||
|
||||
padding:0; /*4px;*/
|
||||
padding:0;
|
||||
margin:0;
|
||||
box-sizing:border-box;
|
||||
|
||||
position:absolute;
|
||||
top:4px;
|
||||
bottom:0;
|
||||
bottom:4px;
|
||||
left:4px;
|
||||
right:0;
|
||||
}
|
||||
@@ -300,6 +300,9 @@ html,body {
|
||||
|
||||
/* Text */
|
||||
|
||||
.tx-sys, .tx-user, .tx-chat {
|
||||
white-space:pre-wrap;
|
||||
}
|
||||
.tx-time {
|
||||
color:grey;
|
||||
}
|
||||
@@ -314,10 +317,6 @@ html,body {
|
||||
.tx-chat {
|
||||
color: black;
|
||||
}
|
||||
.tx-private {
|
||||
color: red;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* webkit scrollbars */
|
||||
|
||||
@@ -354,6 +353,11 @@ html,body {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.user-is-operator {
|
||||
color:darkgreen;
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
/* User sprite */
|
||||
|
||||
.ul-mini {
|
||||
|
||||
@@ -1,30 +1,35 @@
|
||||
/* dcwebui.js */
|
||||
//;(function() {
|
||||
//IIFEMODE:;(function() {
|
||||
|
||||
"use strict";
|
||||
|
||||
var $ = (document.querySelectorAll ?
|
||||
function(s) {
|
||||
var r = document.querySelectorAll(s);
|
||||
return (s[0] === '#' && r.length === 1) ? r[0] : r;
|
||||
} :
|
||||
function(s) {
|
||||
// i'm not writing a selector engine...
|
||||
if (! s.length) return [];
|
||||
if (s[0] === '#') {
|
||||
return document.getElementById(s.slice(1));
|
||||
} else if (s[0] === '.') {
|
||||
return document.getElementsByClassName(s.slice(1));
|
||||
} else {
|
||||
return document.getElementsByTagName(s);
|
||||
}
|
||||
var SENTINEL_PASSWORD = "************";
|
||||
var CHAT_SCROLLBACK_LIMIT = 50; // Once over 2x $limit, the first $limit will be trimmed off the list
|
||||
var EXTERN_ROOT = window.location.protocol + "//" + window.location.host + "/";
|
||||
|
||||
var $ = function(s) {
|
||||
// There used to be a querySelectorAll implementation, but, better that we don't have
|
||||
// potentially-incompatible implementations if this one does actually work.
|
||||
// i'm not writing a selector engine...
|
||||
if (! s.length) {
|
||||
return [];
|
||||
}
|
||||
);
|
||||
|
||||
if (s[0] === '#') {
|
||||
return document.getElementById(s.slice(1)); // single element
|
||||
} else if (s[0] === '.') {
|
||||
return document.getElementsByClassName(s.slice(1)); // multiple elements
|
||||
} else {
|
||||
return document.getElementsByTagName(s); // multiple elements
|
||||
}
|
||||
};
|
||||
|
||||
var nmdc_escape = function(str) {
|
||||
return (''+str).length ? (''+str).
|
||||
replace(/&/g,'&').replace(/\|/g,'|').replace(/\$/g,'$') :
|
||||
' ';
|
||||
return (
|
||||
(''+str).length
|
||||
? (''+str).replace(/&/g,'&').replace(/\|/g,'|').replace(/\$/g,'$')
|
||||
: ' '
|
||||
);
|
||||
};
|
||||
|
||||
var hesc = function(s) {
|
||||
@@ -34,22 +39,38 @@ var hesc = function(s) {
|
||||
return s.toString().replace(/[&<>'"]/g, function(s) { return filter[s]; });
|
||||
};
|
||||
|
||||
var nl2br = function(str) { // thanks php.js!
|
||||
return (str+'').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1<br>$2');
|
||||
var fmtBytes = function(b) {
|
||||
if (b == 0) {
|
||||
return '(nothing)';
|
||||
}
|
||||
|
||||
var k = 1024;
|
||||
var sizes = [' B', ' KiB', ' MiB', ' GiB', ' TiB'];
|
||||
var i = Math.floor(Math.log(b) / Math.log(k));
|
||||
return parseFloat((b / Math.pow(k, i)).toFixed(3)) + sizes[i];
|
||||
};
|
||||
|
||||
|
||||
var urldesc = function(s) {
|
||||
return decodeURIComponent(s.replace(/\+/g, " "));
|
||||
}
|
||||
|
||||
var linkify = function(str) {
|
||||
return str.replace(
|
||||
/(https?:\/\/[^\s<]+)/g, "<a target='_blank' href=\"$1\">$1</a>"
|
||||
// n.b. str is already hesced
|
||||
return (str
|
||||
.replace(
|
||||
/(https?:\/\/[^\s<]+)/g,
|
||||
"<a target='_blank' rel=\"noreferrer\" href=\"$1\">$1</a>"
|
||||
)
|
||||
.replace(
|
||||
/magnet:\?.+dn=([^\< ]+)/g,
|
||||
function(match, m1) { return "<a href=\"" + match + "\">[MAGNET] " + urldesc(m1) + "</a>"; }
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
var sanitise = function(s) {
|
||||
return linkify(nl2br(hesc(s)));
|
||||
};
|
||||
|
||||
var toggle = function($el) {
|
||||
$el.style.display = ($el.style.display === "block") ? "none" : "block";
|
||||
return linkify(hesc(s));
|
||||
};
|
||||
|
||||
var textContent = function($el) {
|
||||
@@ -58,7 +79,23 @@ var textContent = function($el) {
|
||||
return "";
|
||||
};
|
||||
|
||||
// https://gist.github.com/eligrey/1276030
|
||||
var negmod = function(l, r) {
|
||||
var ret = l % r;
|
||||
if (l < 0) {
|
||||
return ret + r;
|
||||
} else {
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
// @ref https://developer.mozilla.org/en/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
|
||||
var b64 = function(str) {
|
||||
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) {
|
||||
return String.fromCharCode('0x' + p1);
|
||||
})).replace(/=/g, '');
|
||||
}
|
||||
|
||||
// @ref https://gist.github.com/eligrey/1276030
|
||||
var appendInnerHTML = function($el, html) {
|
||||
var child = document.createElement("span");
|
||||
child.innerHTML = html;
|
||||
@@ -69,7 +106,7 @@ var appendInnerHTML = function($el, html) {
|
||||
}
|
||||
};
|
||||
|
||||
// http://stackoverflow.com/a/5598797
|
||||
// @ref http://stackoverflow.com/a/5598797
|
||||
function getOffsetLeft( elem ) {
|
||||
var offsetLeft = 0;
|
||||
do {
|
||||
@@ -90,6 +127,55 @@ function getOffsetTop( elem ) {
|
||||
return offsetTop;
|
||||
}
|
||||
|
||||
/* */
|
||||
|
||||
var date_format = function(d, format) {
|
||||
var pad = function(s) {
|
||||
return (s < 10) ? '0'+s : ''+s ;
|
||||
};
|
||||
|
||||
var ret = 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(/m/g, pad(d.getMonth() + 1));
|
||||
ret = ret.replace(/d/g, pad(d.getDate()));
|
||||
return ret;
|
||||
};
|
||||
|
||||
/* */
|
||||
|
||||
var notify = function(title, body, tab) {
|
||||
if (!("Notification" in window)) {
|
||||
return; // not supported by browser
|
||||
}
|
||||
|
||||
switch (window.Notification.permission) {
|
||||
case "granted": {
|
||||
var n = new Notification(title, {
|
||||
body: body,
|
||||
icon: EXTERN_ROOT + "/favicon.ico"
|
||||
});
|
||||
n.onclick = function() {
|
||||
parent.focus(); // recent chrome
|
||||
window.focus(); // older browsers
|
||||
tab_set(tab);
|
||||
this.close();
|
||||
};
|
||||
} break;
|
||||
|
||||
case "denied": return;
|
||||
|
||||
default: {
|
||||
// Clarify permission and retry
|
||||
Notification.requestPermission(function(permission) {
|
||||
notify(title, body);
|
||||
});
|
||||
} break;
|
||||
}
|
||||
};
|
||||
|
||||
/* Tab writers */
|
||||
|
||||
var write = function(tab) {
|
||||
@@ -111,15 +197,7 @@ var write = function(tab) {
|
||||
return this.raw('<span class="'+c+'">'+s+'</span>');
|
||||
},
|
||||
'time': function() {
|
||||
var d = new Date();
|
||||
var pad = function(s) {
|
||||
return (s < 10) ? '0'+s : ''+s ;
|
||||
};
|
||||
return this.raw(
|
||||
'<span class="tx-time">['+
|
||||
pad(d.getHours()) + ":" + pad(d.getMinutes())+
|
||||
"]</span> "
|
||||
);
|
||||
return this.raw('<span class="tx-time">['+ date_format(new Date(), timestamp_formats[timestamp_format_index])+"]</span> ");
|
||||
},
|
||||
'system': function(s) {
|
||||
return this.time().c('tx-sys', sanitise(s)).raw('<br/>');
|
||||
@@ -131,17 +209,16 @@ var write = function(tab) {
|
||||
return this.time().
|
||||
pubnick(u).raw(' ').
|
||||
c('tx-chat', sanitise(s)).raw('<br/>');
|
||||
},
|
||||
'priv': function(f, t, s) {
|
||||
return this.time().
|
||||
c('tx-user', '<'+hesc(f)+' -> '+hesc(t)+'>').
|
||||
raw(' ').
|
||||
c('tx-private', sanitise(s)).
|
||||
raw('<br/>');
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var write_system_message_in_all_pm_tabs = function(system_message) {
|
||||
for (var k in pm_tabs) {
|
||||
writerFor(k).system(system_message);
|
||||
}
|
||||
};
|
||||
|
||||
/* Userlist */
|
||||
|
||||
var switchToPM = function(u) {
|
||||
@@ -171,8 +248,7 @@ var userMenu = function(u, ev) {
|
||||
usermenu.show();
|
||||
|
||||
//
|
||||
ev.preventDefault();
|
||||
return false;
|
||||
return noprop(ev);
|
||||
};
|
||||
|
||||
var userlist = {
|
||||
@@ -184,6 +260,7 @@ var userlist = {
|
||||
var userlist = userlists[l];
|
||||
|
||||
var to_add = document.createElement('li');
|
||||
to_add.className = "user-" + b64(u);
|
||||
to_add.innerHTML = hesc(u);
|
||||
|
||||
to_add.onclick = function() { switchToPM(u); };
|
||||
@@ -245,16 +322,38 @@ var userlist = {
|
||||
return ret;
|
||||
},
|
||||
'has': function(u) {
|
||||
var userlist = $(".userlist")[0].children;
|
||||
for (var i = 0, e = userlist.length; i < e; ++i) {
|
||||
if (textContent(userlist[i]) === u) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return $(".user-" + b64(u)).length !== 0; /* there are two - large and non-large */
|
||||
},
|
||||
'count': function() {
|
||||
return $(".userlist")[0].children.length;
|
||||
},
|
||||
'setInfo': function(nick, props) {
|
||||
var baseClass = "user-" + b64(nick);
|
||||
var $el = $("." + baseClass);
|
||||
var prop_str = [];
|
||||
if (props.Description.length > 0) {
|
||||
prop_str.push(props.Description);
|
||||
}
|
||||
if (props.Email.length > 0) {
|
||||
prop_str.push(props.Email);
|
||||
}
|
||||
if (props.ClientTag.length > 0) {
|
||||
prop_str.push(props.ClientTag + " " + props.ClientVersion);
|
||||
}
|
||||
if (props.IPAddress.length > 0) {
|
||||
prop_str.push(props.IPAddress);
|
||||
}
|
||||
prop_str.push("Sharing " + fmtBytes(props.ShareSize));
|
||||
|
||||
for (var i = 0; i < $el.length; ++i) {
|
||||
$el[i].title = prop_str.join("\n");
|
||||
|
||||
if (props.IsOperator) {
|
||||
$el[i].className = baseClass + " user-is-operator";
|
||||
} else {
|
||||
$el[i].className = baseClass; // remove op flag
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -262,17 +361,29 @@ var submit = function() {
|
||||
var str = $("#chatbox").value;
|
||||
if (! str.length) return;
|
||||
|
||||
if (hub_state === 0) {
|
||||
hub_state = 1;
|
||||
if (hub_state === STATE_READY_FOR_LOGIN) {
|
||||
transition(1); // disables #chatbox
|
||||
|
||||
var name_parts = str.split(":", 2);
|
||||
hub_last_nick = name_parts[0];
|
||||
|
||||
sock.emit('hello', {'nick' : hub_last_nick, 'pass' : name_parts.length >= 2 ? name_parts[1] : ''});
|
||||
$("#chatbox").disabled = true;
|
||||
hub_last_nick = str.split(":", 2)[0];
|
||||
|
||||
var hub_pass = "";
|
||||
if (str.length > hub_last_nick.length) {
|
||||
hub_pass = str.substr(hub_last_nick.length + 1);
|
||||
}
|
||||
if (hub_pass === SENTINEL_PASSWORD) {
|
||||
// Probably not a real password. Attempt to load a better one from the saved state
|
||||
var cache = persistence_get("login", "");
|
||||
if (cache.indexOf(":") != -1) {
|
||||
hub_pass = cache.substr(cache.indexOf(":") + 1);
|
||||
}
|
||||
}
|
||||
|
||||
persistence_set("login", hub_pass.length > 0 ? hub_last_nick+":"+hub_pass : hub_last_nick);
|
||||
|
||||
sock.emit('hello', {'nick' : hub_last_nick, 'pass' : hub_pass});
|
||||
write("tab-main").system("Connecting...");
|
||||
|
||||
} else if (hub_state === 2) {
|
||||
} else if (hub_state === STATE_ACTIVE) {
|
||||
if (pm_target !== false) {
|
||||
sock.emit('priv', {'user': pm_target, 'message': str});
|
||||
writerFor(pm_target).pub(hub_last_nick, str );
|
||||
@@ -280,6 +391,12 @@ var submit = function() {
|
||||
sock.emit('pub', {'message' : str});
|
||||
}
|
||||
|
||||
chat_scrollback.push( str );
|
||||
chat_scrollback_index = -1;
|
||||
if (chat_scrollback.length > (2*CHAT_SCROLLBACK_LIMIT)) {
|
||||
chat_scrollback = chat_scrollback.slice(CHAT_SCROLLBACK_LIMIT);
|
||||
}
|
||||
|
||||
} else {
|
||||
write("tab-main").system("Invalid internal state.");
|
||||
}
|
||||
@@ -287,6 +404,41 @@ var submit = function() {
|
||||
$("#chatbox").value = '';
|
||||
};
|
||||
|
||||
/* page visibility */
|
||||
|
||||
var pagevis_currently_visible = true;
|
||||
|
||||
var pagevis_setup = function(fnActive, fnInactive) {
|
||||
|
||||
var h, vc;
|
||||
if (typeof document.hidden !== "undefined") {
|
||||
h = "hidden";
|
||||
vc = "visibilitychange";
|
||||
} else if (typeof document.msHidden !== "undefined") {
|
||||
h = "msHidden";
|
||||
vc = "msvisibilitychange";
|
||||
} else if (typeof document.webkitHidden !== "undefined") {
|
||||
h = "webkitHidden";
|
||||
vc = "webkitvisibilitychange";
|
||||
}
|
||||
|
||||
if (typeof document[h] === "undefined") {
|
||||
// Browser doesn't support Page Visibility API, so behave as if the page is always visible
|
||||
pagevis_currently_visible = true
|
||||
|
||||
} else {
|
||||
document.addEventListener(vc, function() {
|
||||
if (document[h]) {
|
||||
pagevis_currently_visible = false;
|
||||
fnInactive();
|
||||
} else {
|
||||
pagevis_currently_visible = true;
|
||||
fnActive();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* tabs */
|
||||
|
||||
/**
|
||||
@@ -305,7 +457,13 @@ var tab_set = function(tab) {
|
||||
var tabitems = $(".tabitem");
|
||||
for (var i in tabitems) {
|
||||
try {
|
||||
tabitems[i].className = "tabitem" + (tabitems[i].getAttribute('data-tab') === tab ? ' selected' : '');
|
||||
// Update UNREAD/SELECTED flags for the target
|
||||
var was_unread = (tabitems[i].className.indexOf("unread") !== -1);
|
||||
var is_target = (tabitems[i].getAttribute('data-tab') === tab);
|
||||
|
||||
var is_still_unread = (was_unread && !is_target);
|
||||
|
||||
tabitems[i].className = "tabitem" + (is_target ? ' selected' : '') + (is_still_unread ? ' unread' : '');
|
||||
} catch (e) {};
|
||||
}
|
||||
|
||||
@@ -317,11 +475,16 @@ var tab_set = function(tab) {
|
||||
}
|
||||
}
|
||||
|
||||
if (tab == "tab-main" && pagevis_currently_visible) {
|
||||
mainchat_unread_count = 0;
|
||||
}
|
||||
|
||||
updateTitle();
|
||||
|
||||
write(tab).scroll();
|
||||
$("#chatbox").focus();
|
||||
last_tab = tab;
|
||||
|
||||
};
|
||||
|
||||
var tab_new = function(id, name) {
|
||||
@@ -355,7 +518,8 @@ var tab_free = function(id) {
|
||||
// clear from PM tabs
|
||||
for (var i in pm_tabs) {
|
||||
if (pm_tabs[i] === id) {
|
||||
pm_tabs[i] = false;
|
||||
// pm_tabs[i] = false;
|
||||
delete(pm_tabs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,6 +532,20 @@ var tab_free = function(id) {
|
||||
}
|
||||
};
|
||||
|
||||
var noprop = function(ev) {
|
||||
if (ev.preventDefault) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
if (ev.stopPropagation) {
|
||||
ev.stopPropagation();
|
||||
} else {
|
||||
ev.cancelBubble = true; // oldIE
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var tab_addHandlers = function() {
|
||||
var tabitems = $(".tabitem");
|
||||
for (var i = 0; i < tabitems.length; i++) {
|
||||
@@ -376,13 +554,7 @@ var tab_addHandlers = function() {
|
||||
tabitems[i].onclick = function(ev) {
|
||||
tab_set( this.getAttribute('data-tab') );
|
||||
|
||||
// 360nobubble
|
||||
if (ev.stopPropagation) {
|
||||
ev.stopPropagation();
|
||||
} else {
|
||||
ev.cancelBubble = true; // oldIE
|
||||
}
|
||||
return false;
|
||||
return noprop(ev);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -393,28 +565,30 @@ var tab_addHandlers = function() {
|
||||
tabclosers[i].onclick = function(ev) {
|
||||
tab_free( this.getAttribute('data-tab') );
|
||||
|
||||
// 360nobubble
|
||||
if (ev.stopPropagation) {
|
||||
ev.stopPropagation();
|
||||
} else {
|
||||
ev.cancelBubble = true; // oldIE
|
||||
}
|
||||
return false;
|
||||
return noprop(ev);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/* */
|
||||
|
||||
var writerFor = function(username) {
|
||||
var tabid = "";
|
||||
var maybeWriterFor = function(username) {
|
||||
if (! username in pm_tabs || ! pm_tabs[username]) {
|
||||
tabid = tab_new(next_tabid++, username);
|
||||
pm_tabs[username] = tabid;
|
||||
} else {
|
||||
tabid = pm_tabs[username];
|
||||
return null;
|
||||
}
|
||||
|
||||
return write(pm_tabs[username]);
|
||||
};
|
||||
|
||||
var writerFor = function(username) {
|
||||
var ret = maybeWriterFor(username);
|
||||
if (ret !== null) {
|
||||
return ret; // found
|
||||
}
|
||||
|
||||
// create new
|
||||
var tabid = tab_new(next_tabid++, username);
|
||||
pm_tabs[username] = tabid;
|
||||
return write(tabid);
|
||||
};
|
||||
|
||||
@@ -422,7 +596,7 @@ var writerFor = function(username) {
|
||||
|
||||
var tabcomplete_state = '';
|
||||
|
||||
var tabcompletion_start = function() {
|
||||
var tabcompletion_start = function(direction) {
|
||||
|
||||
var cursor = $("#chatbox").value.replace(/^.*\s([^\s]+)$/, '$1');
|
||||
|
||||
@@ -456,7 +630,7 @@ var tabcompletion_start = function() {
|
||||
}
|
||||
}
|
||||
|
||||
var targetName = match[(i + 1) % match.length];
|
||||
var targetName = match[negmod(i + direction, match.length)];
|
||||
|
||||
// Replace in textbox
|
||||
|
||||
@@ -516,7 +690,10 @@ var menu = new MenuList($("#menubutton"));
|
||||
|
||||
menu.reset = function() {
|
||||
this.clear();
|
||||
this.add(joinparts_getstr(), toggle_joinparts);
|
||||
this.add(joinparts_getstr(), toggle_joinparts);
|
||||
this.add(desktop_notifications_fmtstr(), desktop_notifications_toggle);
|
||||
this.add(warnonclose_fmtstr(), warnonclose_toggle);
|
||||
this.add(timestamp_display(), timestamp_toggle);
|
||||
};
|
||||
|
||||
var usermenu = new MenuList(document.body);
|
||||
@@ -597,22 +774,28 @@ var joinparts_getstr = function() {
|
||||
var toggle_joinparts = function(ev) {
|
||||
var $el = ev.target || ev.srcElement;
|
||||
show_joins = ! show_joins;
|
||||
persistence_set("show_joins", show_joins);
|
||||
$el.innerHTML = joinparts_getstr();
|
||||
};
|
||||
|
||||
/* */
|
||||
|
||||
var updateTitle = function() {
|
||||
document.title =
|
||||
($(".unread").length ? "[NEW PM] " : "") +
|
||||
"("+userlist.count()+") "+
|
||||
hub_hubname;
|
||||
var prefix = "";
|
||||
var unrTabs = $(".unread");
|
||||
if (unrTabs.length === 1 && unrTabs[0].getAttribute('data-tab') == "tab-main") {
|
||||
prefix = "[" + mainchat_unread_count + " NEW] "
|
||||
} else if (unrTabs.length > 0) {
|
||||
prefix = "[NEW PM] "
|
||||
}
|
||||
|
||||
document.title = prefix + hub_hubname + " ("+userlist.count()+")"
|
||||
};
|
||||
|
||||
var sock = {};
|
||||
var hub_state = 0; // [disconnected, sent-nick, connected]
|
||||
var hub_last_nick = '';
|
||||
var hub_hubname = 'DCWebUI';
|
||||
var hub_hubname = "Loading...";
|
||||
|
||||
var pm_tabs = {}; // nick => tabid
|
||||
var next_tabid = 1;
|
||||
@@ -622,30 +805,174 @@ var last_tab = "tab-main";
|
||||
|
||||
var show_joins = false;
|
||||
|
||||
var have_cleared_once = false;
|
||||
|
||||
var STATE_DISCONNECTED = -1;
|
||||
var STATE_READY_FOR_LOGIN = 0;
|
||||
var STATE_CONNECTING = 1;
|
||||
var STATE_ACTIVE = 2;
|
||||
|
||||
var chat_scrollback = [];
|
||||
var chat_scrollback_index = -1;
|
||||
|
||||
var timestamp_formats = [ "H:i", "H:i:s", "Y-m-d H:i:s" ];
|
||||
var timestamp_names = [ "Minutes", "Seconds", "Full" ];
|
||||
var timestamp_format_index = 0;
|
||||
|
||||
var mainchat_unread_count = 0;
|
||||
|
||||
var should_warn_on_close = false;
|
||||
|
||||
var timestamp_display = function() {
|
||||
return "Timestamp format: " + timestamp_names[timestamp_format_index];
|
||||
};
|
||||
|
||||
var timestamp_toggle = function(ev) {
|
||||
var $el = ev.target || ev.srcElement;
|
||||
timestamp_format_index = (timestamp_format_index + 1) % timestamp_formats.length;
|
||||
persistence_set("timestamps", timestamp_format_index);
|
||||
$el.innerHTML = timestamp_display();
|
||||
};
|
||||
|
||||
var warnonclose_fmtstr = function() {
|
||||
return (should_warn_on_close ? "☑" : "☐") + " Confirm closing window";
|
||||
};
|
||||
|
||||
var warnonclose_toggle = function(ev) {
|
||||
var $el = ev.target || ev.srcElement;
|
||||
should_warn_on_close = !should_warn_on_close;
|
||||
persistence_set("warnonclose", should_warn_on_close);
|
||||
$el.innerHTML = warnonclose_fmtstr();
|
||||
};
|
||||
|
||||
var desktop_notifications_enabled = false;
|
||||
|
||||
var desktop_notifications_fmtstr = function() {
|
||||
return (desktop_notifications_enabled ? "☑" : "☐") + " Show desktop popups";
|
||||
};
|
||||
|
||||
var desktop_notifications_toggle = function(ev) {
|
||||
var $el = ev.target || ev.srcElement;
|
||||
desktop_notifications_enabled = !desktop_notifications_enabled;
|
||||
persistence_set("notifications", desktop_notifications_enabled);
|
||||
|
||||
if (desktop_notifications_enabled) {
|
||||
notify(hub_hubname, "Desktop popups enabled", "tab-main");
|
||||
}
|
||||
|
||||
persistence_set("popups", desktop_notifications_enabled);
|
||||
|
||||
$el.innerHTML = desktop_notifications_fmtstr();
|
||||
};
|
||||
|
||||
var scrollback_move = function(delta) {
|
||||
if (chat_scrollback.length === 0) {
|
||||
return; // no effect
|
||||
}
|
||||
|
||||
if (chat_scrollback_index === -1) {
|
||||
chat_scrollback_index = chat_scrollback.length - 1; // always starts at most recent message
|
||||
} else {
|
||||
chat_scrollback_index += delta;
|
||||
|
||||
// clamp
|
||||
if (chat_scrollback_index === -1) {
|
||||
chat_scrollback_index = 0;
|
||||
} else if (chat_scrollback_index === chat_scrollback.length) {
|
||||
chat_scrollback_index = chat_scrollback.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
$("#chatbox").value = chat_scrollback[chat_scrollback_index];
|
||||
};
|
||||
|
||||
/* */
|
||||
|
||||
var persistence_set = function(key, value) {
|
||||
if (window.localStorage) {
|
||||
window.localStorage[key] = JSON.stringify(value);
|
||||
}
|
||||
};
|
||||
|
||||
var persistence_get = function(key, fallback) {
|
||||
try {
|
||||
return JSON.parse( window.localStorage[key] );
|
||||
} catch (ex) {
|
||||
return fallback;
|
||||
}
|
||||
};
|
||||
|
||||
var transition = function(new_state) {
|
||||
hub_state = new_state;
|
||||
|
||||
switch(new_state) {
|
||||
case STATE_DISCONNECTED: {
|
||||
userlist.clear();
|
||||
$("#chatbox").disabled = true;
|
||||
$("#chatbox").value = ''; // clear
|
||||
} break;
|
||||
|
||||
case STATE_READY_FOR_LOGIN: {
|
||||
userlist.clear();
|
||||
$("#chatbox").spellcheck = false;
|
||||
$("#chatbox").disabled = false;
|
||||
$("#chatbox").value = ''; // clear
|
||||
} break;
|
||||
|
||||
case STATE_CONNECTING: {
|
||||
$("#chatbox").disabled = true;
|
||||
} break;
|
||||
|
||||
case STATE_ACTIVE: {
|
||||
write("tab-main").system("Now talking on "+hub_hubname);
|
||||
$("#chatbox").disabled = false;
|
||||
$("#chatbox").spellcheck = true;
|
||||
} break;
|
||||
}
|
||||
};
|
||||
|
||||
var tab_is_visible = function(tabref) {
|
||||
return (last_tab === tabref && pagevis_currently_visible);
|
||||
}
|
||||
|
||||
var tab_mark_unread = function(tabref) {
|
||||
if ($("#tabitem-"+tabref).className.indexOf('unread') === -1) {
|
||||
$("#tabitem-"+tabref).className += " unread";
|
||||
updateTitle();
|
||||
}
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
write("tab-main").system("Communicating with server...");
|
||||
|
||||
document.title = DCWEBUI_CONF.title;
|
||||
show_joins = persistence_get("show_joins", false);
|
||||
|
||||
document.title = hub_hubname; // "Loading...";
|
||||
|
||||
// HTML event handlers
|
||||
|
||||
$("#form-none").onsubmit = function(ev) {
|
||||
submit();
|
||||
|
||||
// don't submit form
|
||||
ev.preventDefault();
|
||||
return false;
|
||||
return noprop(ev); // don't submit form
|
||||
};
|
||||
|
||||
$("#chatbox").onkeydown = function(ev) {
|
||||
if (ev.keyCode === 9) {
|
||||
// tab
|
||||
tabcompletion_start();
|
||||
ev.preventDefault();
|
||||
return false;
|
||||
if (ev.keyCode === 9 /* Tab */) {
|
||||
tabcompletion_start( ev.shiftKey ? -1 : 1 );
|
||||
return noprop(ev);
|
||||
|
||||
} else if (ev.keyCode == 38 /* ArrowUp */ && ev.ctrlKey) {
|
||||
scrollback_move(-1);
|
||||
return noprop(ev);
|
||||
|
||||
} else if (ev.keyCode == 40 /* ArrowDown */ && ev.ctrlKey) {
|
||||
scrollback_move(1);
|
||||
return noprop(ev);
|
||||
|
||||
} else {
|
||||
tabcompletion_inactive();
|
||||
chat_scrollback_index = -1; // clear
|
||||
}
|
||||
};
|
||||
|
||||
@@ -660,9 +987,7 @@ window.onload = function() {
|
||||
|
||||
$("#menubutton").onclick = function(ev) {
|
||||
menu.toggle();
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
return false;
|
||||
return noprop(ev);
|
||||
};
|
||||
|
||||
window.onclick = function() {
|
||||
@@ -670,10 +995,38 @@ window.onload = function() {
|
||||
usermenu.hide();
|
||||
};
|
||||
|
||||
window.onbeforeunload = function (e) {
|
||||
if (should_warn_on_close && hub_state === STATE_ACTIVE) {
|
||||
// n.b. recent Firefox / Chrome don't display the custom message here.
|
||||
var confirmationMessage = "Still connected to the hub \"" + hub_hubname + "\".\nAre you sure you want to close?";
|
||||
e.returnValue = confirmationMessage;
|
||||
return confirmationMessage;
|
||||
}
|
||||
// else: ignore
|
||||
};
|
||||
|
||||
timestamp_format_index = persistence_get("timestamps", 0);
|
||||
|
||||
should_warn_on_close = persistence_get("warnonclose", false);
|
||||
|
||||
desktop_notifications_enabled = persistence_get("popups", false);
|
||||
|
||||
menu.reset();
|
||||
|
||||
tab_addHandlers();
|
||||
|
||||
pagevis_setup(
|
||||
function() {
|
||||
// We've just become active
|
||||
// If the foreground is a PM window with UNREAD, mark it as read
|
||||
tab_set(last_tab);
|
||||
},
|
||||
function() {
|
||||
// We've just become inactive
|
||||
// TODO: marker lines
|
||||
}
|
||||
);
|
||||
|
||||
// Hacks for WiiU user-agent
|
||||
|
||||
if (navigator && navigator.userAgent && !!navigator.userAgent.match("Nintendo WiiU")) {
|
||||
@@ -682,14 +1035,29 @@ window.onload = function() {
|
||||
|
||||
// Socket event handlers
|
||||
|
||||
sock = io.connect(DCWEBUI_CONF.extern);
|
||||
sock = io.connect(EXTERN_ROOT);
|
||||
sock.on('cls', function() {
|
||||
write("tab-main").cls();
|
||||
userlist.clear();
|
||||
hub_state = 0;
|
||||
transition(STATE_READY_FOR_LOGIN);
|
||||
|
||||
var pre_login = persistence_get("login", "");
|
||||
if (pre_login.indexOf(":") !== -1) {
|
||||
pre_login = pre_login.substr(0, pre_login.indexOf(":")) + ":" + SENTINEL_PASSWORD;
|
||||
}
|
||||
$("#chatbox").value = pre_login;
|
||||
|
||||
if (have_cleared_once) {
|
||||
// re-log-in automatically
|
||||
write("tab-main").system("Automatically reconnecting as \"" + pre_login + "\"...");
|
||||
submit();
|
||||
}
|
||||
|
||||
if (!have_cleared_once) {
|
||||
write("tab-main").cls();
|
||||
userlist.clear();
|
||||
have_cleared_once = true; // don't re-clear, keep history
|
||||
}
|
||||
});
|
||||
sock.on('hubname', function(s) {
|
||||
write("tab-main").system("Now talking on "+s);
|
||||
hub_hubname = s;
|
||||
updateTitle();
|
||||
});
|
||||
@@ -701,25 +1069,40 @@ window.onload = function() {
|
||||
});
|
||||
sock.on('pub', function(data) {
|
||||
write("tab-main").pub( data.user, data.message);
|
||||
});
|
||||
sock.on('priv', function(data) {
|
||||
writerFor(data.user).pub( data.user, data.message);
|
||||
if (last_tab !== pm_tabs[data.user] &&
|
||||
($("#tabitem-"+pm_tabs[data.user]).className.indexOf('unread') === -1)
|
||||
) {
|
||||
$("#tabitem-"+pm_tabs[data.user]).className += " unread";
|
||||
|
||||
if (! tab_is_visible("tab-main")) {
|
||||
tab_mark_unread("tab-main");
|
||||
mainchat_unread_count += 1;
|
||||
updateTitle();
|
||||
}
|
||||
});
|
||||
sock.on('priv', function(data) {
|
||||
writerFor(data.user).pub(data.user, data.message);
|
||||
if (! tab_is_visible(pm_tabs[data.user])) {
|
||||
// Got PM, but tab isn't focused
|
||||
|
||||
tab_mark_unread( pm_tabs[data.user] );
|
||||
|
||||
if (desktop_notifications_enabled) {
|
||||
notify("Message from " + data.user, data.message, pm_tabs[data.user]);
|
||||
}
|
||||
}
|
||||
});
|
||||
sock.on('hello', function() {
|
||||
hub_state = 2;
|
||||
$("#chatbox").disabled = false;
|
||||
transition(STATE_ACTIVE);
|
||||
write_system_message_in_all_pm_tabs("Reconnected.");
|
||||
});
|
||||
sock.on('part', function(u) {
|
||||
userlist.del(u.user);
|
||||
if (show_joins) {
|
||||
write("tab-main").system("*** Parts: "+u.user); // sanitised
|
||||
}
|
||||
|
||||
var targetTab = maybeWriterFor(u.user);
|
||||
if (targetTab !== null) {
|
||||
targetTab.system("*** Parts: "+u.user);
|
||||
}
|
||||
|
||||
updateTitle();
|
||||
});
|
||||
sock.on('join', function(u) {
|
||||
@@ -727,21 +1110,33 @@ window.onload = function() {
|
||||
if (show_joins) {
|
||||
write("tab-main").system("*** Joins: "+u.user);
|
||||
}
|
||||
|
||||
var targetTab = maybeWriterFor(u.user);
|
||||
if (targetTab !== null) {
|
||||
targetTab.system("*** Joins: "+u.user);
|
||||
}
|
||||
|
||||
updateTitle();
|
||||
});
|
||||
sock.on('info', function(u) {
|
||||
try {
|
||||
var props = JSON.parse(u.message);
|
||||
userlist.setInfo(u.user, props);
|
||||
} catch (ex) {}
|
||||
});
|
||||
sock.on('close', function() {
|
||||
hub_state = 0;
|
||||
userlist.clear();
|
||||
transition(STATE_DISCONNECTED);
|
||||
write("tab-main").system("Connection closed by remote host.");
|
||||
write_system_message_in_all_pm_tabs("Disconnected.");
|
||||
});
|
||||
sock.on('disconnect', function() {
|
||||
hub_state = 0;
|
||||
userlist.clear();
|
||||
transition(STATE_DISCONNECTED);
|
||||
write("tab-main").system("Lost connection to the server.");
|
||||
write_system_message_in_all_pm_tabs("Disconnected.");
|
||||
});
|
||||
sock.on('usercommand', function(data) {
|
||||
process_usercommand(data);
|
||||
});
|
||||
};
|
||||
|
||||
//})();
|
||||
//IIFEMODE:})();
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 17 KiB |
@@ -12,7 +12,7 @@
|
||||
<body>
|
||||
<div class="tabbar placement-top" id="bar">
|
||||
<div class="menubutton" id="menubutton">☰</div>
|
||||
<div class="tabitem selected" data-tab="tab-main">
|
||||
<div class="tabitem selected" data-tab="tab-main" id="tabitem-tab-main">
|
||||
<span class="tab-label">
|
||||
Main
|
||||
</span>
|
||||
@@ -52,8 +52,7 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="/socket.io-1.4.5.js"></script>
|
||||
<script type="text/javascript" src="/conf"></script>
|
||||
<script type="text/javascript" src="/socket.io-1.7.2.js"></script>
|
||||
<script type="text/javascript" src="/dcwebui.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
4
client/socket.io-1.7.2.js
Normal file
4
client/socket.io-1.7.2.js
Normal file
File diff suppressed because one or more lines are too long
56
clientpack.php
Normal file
56
clientpack.php
Normal file
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/php
|
||||
<?php
|
||||
|
||||
# Dependencies:
|
||||
# - PHP
|
||||
# - Uglifyjs (`npm install -g uglifyjs`)
|
||||
# - Lessc (`npm install -g less`)
|
||||
# - Lessc minifier (`npm install -g less-plugin-clean-css`)
|
||||
# - HTML minifier (`npm install -g html-minifier`)
|
||||
|
||||
echo "Compressing/minifying web resources...\n";
|
||||
|
||||
if (is_dir('clientpack')) {
|
||||
`rm -r clientpack`;
|
||||
}
|
||||
|
||||
`cp -r client clientpack`;
|
||||
|
||||
// Toggle IIFE on
|
||||
|
||||
`sed -i -re 's~//IIFEMODE:~~g' clientpack/dcwebui.js`;
|
||||
|
||||
// Minify JS
|
||||
|
||||
`uglifyjs clientpack/dcwebui.js -o clientpack/dcwebui.min.js -c -m`;
|
||||
|
||||
// Minify CSS
|
||||
|
||||
`lessc --clean-css clientpack/dcwebui.css clientpack/dcwebui.min.css`;
|
||||
|
||||
// Embed css into HTML file
|
||||
|
||||
$html_content = file_get_contents('clientpack/index.htm');
|
||||
|
||||
$html_content = preg_replace_callback('~<link[^>]+dcwebui.css[^>]*>~', function() { return '<style type="text/css">'.file_get_contents('clientpack/dcwebui.min.css').'</style>'; }, $html_content);
|
||||
|
||||
// Embed JS into HTML file
|
||||
|
||||
$html_content = preg_replace_callback('~<script[^>]+dcwebui.js[^>]*>~', function() { return '<script type="text/javascript">'.file_get_contents('clientpack/dcwebui.min.js').'</script>'; }, $html_content);
|
||||
|
||||
// Embed socketio into HTML file
|
||||
|
||||
define('SIO_NAME', 'socket.io-1.7.2.js');
|
||||
$html_content = preg_replace_callback('~<script[^>]+'.SIO_NAME.'[^>]*>~', function() { return '<script type="text/javascript">'.file_get_contents('clientpack/'.SIO_NAME).'</script>'; }, $html_content);
|
||||
|
||||
// Minify the combined file
|
||||
|
||||
file_put_contents('clientpack/index.htm', $html_content);
|
||||
|
||||
`html-minifier --collapse-whitespace -o clientpack/index.min.htm clientpack/index.htm`;
|
||||
|
||||
// Clean up files
|
||||
|
||||
`rm clientpack/{index.htm,dcwebui{.min,}.js,dcwebui{.min,}.css}`;
|
||||
unlink('clientpack/'.SIO_NAME);
|
||||
rename('clientpack/index.min.htm', 'clientpack/index.htm');
|
||||
99
main.go
99
main.go
@@ -8,15 +8,11 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"code.ivysaur.me/libnmdc"
|
||||
"github.com/googollee/go-socket.io"
|
||||
|
||||
"libnmdc"
|
||||
)
|
||||
|
||||
type ActiveConnection struct {
|
||||
s *socketio.Socket
|
||||
h *libnmdc.HubConnection
|
||||
}
|
||||
var VERSION string = `nmdc-webfrontend/devel-unreleased`
|
||||
|
||||
type App struct {
|
||||
cfg *Config
|
||||
@@ -49,6 +45,7 @@ func (this *App) HubWorker(Nick, Pass string, so socketio.Socket, done chan stru
|
||||
|
||||
selfUser := libnmdc.NewUserInfo(Nick)
|
||||
selfUser.ClientTag = this.cfg.Hub.Tag
|
||||
selfUser.ClientVersion = libnmdc.DEFAULT_CLIENT_VERSION
|
||||
|
||||
hco := libnmdc.HubConnectionOptions{
|
||||
Address: libnmdc.HubAddress(fmt.Sprintf("%s:%d", this.cfg.Hub.Address, this.cfg.Hub.Port)),
|
||||
@@ -81,6 +78,29 @@ func (this *App) HubWorker(Nick, Pass string, so socketio.Socket, done chan stru
|
||||
|
||||
// Loop hub connection
|
||||
|
||||
serveUserInfo := func(nick string) {
|
||||
props := ""
|
||||
hub.Users(func(users *map[string]libnmdc.UserInfo) error {
|
||||
uinfo, ok := (*users)[nick]
|
||||
if !ok {
|
||||
return nil // just skip
|
||||
}
|
||||
|
||||
bProps, err := json.Marshal(uinfo)
|
||||
if err != nil {
|
||||
return nil // just skip
|
||||
}
|
||||
|
||||
props = string(bProps)
|
||||
return nil
|
||||
})
|
||||
|
||||
// 'Message' is a json-encoded param with user properties
|
||||
if len(props) > 0 {
|
||||
so.Emit("info", UserMessageStruct{User: nick, Message: props})
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
|
||||
@@ -100,19 +120,24 @@ func (this *App) HubWorker(Nick, Pass string, so socketio.Socket, done chan stru
|
||||
case libnmdc.EVENT_PRIVATE:
|
||||
so.Emit("priv", UserMessageStruct{User: hev.Nick, Message: hev.Message})
|
||||
|
||||
case libnmdc.EVENT_USER_UPDATED_INFO:
|
||||
serveUserInfo(hev.Nick)
|
||||
|
||||
case libnmdc.EVENT_USER_JOINED:
|
||||
so.Emit("join", UserMessageStruct{User: hev.Nick})
|
||||
serveUserInfo(hev.Nick)
|
||||
|
||||
case libnmdc.EVENT_USER_PART:
|
||||
so.Emit("part", UserMessageStruct{User: hev.Nick})
|
||||
|
||||
case libnmdc.EVENT_CONNECTION_STATE_CHANGED:
|
||||
if hev.StateChange == libnmdc.CONNECTIONSTATE_CONNECTED {
|
||||
log.Printf("[%s] Connected to hub\n", so.Id())
|
||||
so.Emit("hello")
|
||||
} else if hev.StateChange == libnmdc.CONNECTIONSTATE_DISCONNECTED {
|
||||
so.Emit("close")
|
||||
} else {
|
||||
so.Emit("sys", hev.StateChange.Format())
|
||||
so.Emit("sys", hev.StateChange.String())
|
||||
}
|
||||
|
||||
case libnmdc.EVENT_HUBNAME_CHANGED:
|
||||
@@ -126,11 +151,6 @@ func (this *App) HubWorker(Nick, Pass string, so socketio.Socket, done chan stru
|
||||
"raw": hev.UserCommand.Command,
|
||||
})
|
||||
|
||||
default:
|
||||
if this.cfg.App.Debug {
|
||||
log.Printf("[%s] %v\n", so.Id(), hev)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case <-done:
|
||||
@@ -146,6 +166,7 @@ func (this *App) SocketIOServer(so socketio.Socket) {
|
||||
log.Printf("[%s] Client connected", so.Id())
|
||||
|
||||
so.Emit("cls")
|
||||
so.Emit("hubname", this.cfg.Web.Title)
|
||||
so.Emit("raw", this.cfg.App.MotdHTML+"<br>")
|
||||
so.Emit("sys", "Enter a name to connect as (or name:pass for a registered nick)")
|
||||
|
||||
@@ -163,22 +184,8 @@ func (this *App) SocketIOServer(so socketio.Socket) {
|
||||
|
||||
}
|
||||
|
||||
func (this *App) ConfigRequestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
confStruct := struct {
|
||||
Extern string `json:"extern"`
|
||||
Title string `json:"title"`
|
||||
}{
|
||||
Extern: this.cfg.Web.Extern,
|
||||
Title: this.cfg.Web.Title,
|
||||
}
|
||||
confBytes, _ := json.Marshal(confStruct)
|
||||
|
||||
//
|
||||
|
||||
w.Header().Set("Content-Type", "text/javascript")
|
||||
w.WriteHeader(200)
|
||||
fmt.Fprintf(w, "var DCWEBUI_CONF = %s;\n", string(confBytes))
|
||||
func (this *App) customFaviconHandler(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeFile(w, r, "favicon.ico")
|
||||
}
|
||||
|
||||
func (this *App) StaticRequestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -195,9 +202,9 @@ func (this *App) StaticRequestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
knownContentTypes := map[string]string{
|
||||
".htm": "text/html",
|
||||
".css": "text/css",
|
||||
".js": "application/javascript",
|
||||
".png": "image/png",
|
||||
".ico": "image/x-icon",
|
||||
// No CSS/JS since they're embedded in the HTML
|
||||
}
|
||||
|
||||
foundMime := false
|
||||
@@ -219,6 +226,9 @@ func (this *App) StaticRequestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (this *App) RunServer() {
|
||||
|
||||
// Inner mux {{
|
||||
innerMux := http.NewServeMux()
|
||||
|
||||
// Socket.io handler
|
||||
server, err := socketio.NewServer(nil)
|
||||
if err != nil {
|
||||
@@ -228,22 +238,39 @@ func (this *App) RunServer() {
|
||||
server.On("error", func(so socketio.Socket, err error) {
|
||||
log.Println("error:", err)
|
||||
})
|
||||
http.Handle("/socket.io/", server)
|
||||
innerMux.Handle("/socket.io/", server)
|
||||
|
||||
// Configuration handler
|
||||
http.HandleFunc("/conf", this.ConfigRequestHandler)
|
||||
// Custom favicon handler
|
||||
if this.cfg.Web.CustomFavicon {
|
||||
innerMux.HandleFunc("/favicon.ico", this.customFaviconHandler)
|
||||
}
|
||||
|
||||
// Other files: asset handler
|
||||
http.HandleFunc("/", this.StaticRequestHandler)
|
||||
// Asset handler
|
||||
if this.cfg.Web.ExternalWebroot {
|
||||
innerMux.Handle("/", http.FileServer(http.Dir("client")))
|
||||
} else {
|
||||
innerMux.HandleFunc("/", this.StaticRequestHandler)
|
||||
}
|
||||
// }}
|
||||
|
||||
// Wrapper mux {{
|
||||
outerMux := http.NewServeMux()
|
||||
outerMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Server", VERSION)
|
||||
innerMux.ServeHTTP(w, r)
|
||||
})
|
||||
// }}
|
||||
|
||||
// Listen and serve
|
||||
bindAddr := fmt.Sprintf("%s:%d", this.cfg.Web.BindTo, this.cfg.Web.Port)
|
||||
log.Printf("Serving at %s...", bindAddr)
|
||||
log.Fatal(http.ListenAndServe(bindAddr, nil))
|
||||
log.Fatal(http.ListenAndServe(bindAddr, outerMux))
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
log.Println(VERSION)
|
||||
|
||||
a, err := NewApp("nmdc-webfrontend.conf")
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
{
|
||||
|
||||
"app": {
|
||||
"name" : "DCWebUI2",
|
||||
"version": "1.3.0",
|
||||
"motd" : "Welcome!<br>",
|
||||
"debug" : true
|
||||
"motd": "Welcome!<br>"
|
||||
},
|
||||
|
||||
"web": {
|
||||
"port" : 8082,
|
||||
"port": 8082,
|
||||
"bind_to": "127.0.0.1",
|
||||
"extern" : "http://127.0.0.1:8082",
|
||||
"title" : "DCWebUI"
|
||||
"title": "NMDC Web Frontend",
|
||||
"custom_favicon": false
|
||||
},
|
||||
|
||||
"hub": {
|
||||
"address": "127.0.0.1",
|
||||
"port" : 411,
|
||||
"tag" : "nmdc-webfrontend"
|
||||
"port": 411,
|
||||
"tag": "nmdc-webfrontend"
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user