diff options
| -rw-r--r-- | src/site.clj | 43 | ||||
| -rwxr-xr-x | static/pichat.js | 71 |
2 files changed, 80 insertions, 34 deletions
diff --git a/src/site.clj b/src/site.clj index f2e5626..cce5da1 100644 --- a/src/site.clj +++ b/src/site.clj @@ -8,9 +8,30 @@ (defstruct user-struct :nick :last-seen) (defstruct message-struct :nick :content :timestamp) + (def users (ref {})) (def messages (ref [])) +(def run-flusher true) +(def flusher-sleep-ms 4000) +(def user-timeout-ms 30000) + +(defn swap [f] + (fn [& more] (apply f (reverse more)))) + +(def flusher (agent nil)) + +(defn flush! [x] + (when run-flusher + (send-off *agent* #'flush!)) + (dosync + (let [now (System/currentTimeMillis) + alive? (fn [[n u]] (> (u :last-seen) (- now user-timeout-ms)))] + (ref-set users + (into {} (filter alive? @users))))) + (. Thread (sleep flusher-sleep-ms)) + x) + (defn resp-error [message] {:status 400 :headers {} :body message}) @@ -19,14 +40,14 @@ (defn join-success [nick] (alter users assoc nick (struct user-struct nick (System/currentTimeMillis))) - (let [users (keys @users) - messages (take 20 @messages) + (let [users (sort (keys @users)) + messages (reverse (take 40 @messages)) data {"users" users "messages" messages}] [(session-assoc :nick nick) (resp-success data)])) (defn try-join [params] - (let [nick (params :nick)] + (let [nick (escape-html (params :nick))] (dosync (if (contains? @users nick) (resp-error "NICK_TAKEN") @@ -37,17 +58,18 @@ (defn refresh [nick] (dosync - (let [last-seen (get-in @users [nick :last-seen])] - (alter users assoc-in [nick :last-seen] (System/currentTimeMillis)) - (resp-success {"messages" (new-messages last-seen)})))) - -(defn swap [f] - (fn [& more] (apply f (reverse more)))) + (if (contains? @users nick) + (let [last-seen (get-in @users [nick :last-seen]) + user-list (sort (keys @users))] + (alter users assoc-in [nick :last-seen] (System/currentTimeMillis)) + (resp-success {"messages" (new-messages last-seen) + "users" user-list})) + (resp-error "UNKNOWN_USER")))) (defn msg [session params] (dosync (let [nick (session :nick) - content (params :content) + content (escape-html (params :content)) msg (struct message-struct nick content (System/currentTimeMillis))] (if (contains? @users nick) (do (alter messages (swap cons) msg) @@ -70,3 +92,4 @@ (run-server {:port 8080} "/*" (servlet pichat)) +(send-off flusher flush!)
\ No newline at end of file diff --git a/static/pichat.js b/static/pichat.js index 99bf1b5..aeef1bd 100755 --- a/static/pichat.js +++ b/static/pichat.js @@ -51,28 +51,52 @@ function join() { }); } +function escapeHtml(txt) { + return $("<span>").text(txt).html(); +} + function buildUserDiv(user) { - return '<div>' + user + '</div>'; + return '<div>' + escapeHtml(user) + '</div>'; +} + +// http://rickyrosario.com/blog/converting-a-url-into-a-link-in-javascript-linkify-function +function linkify(text){ + if (text) { + text = text.replace( + /((https?\:\/\/)|(www\.))(\S+)(\w{2,4})(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/gi, + function(url){ + var full_url = url; + if (!full_url.match('^https?:\/\/')) { + full_url = 'http://' + full_url; + } + return '<a href="' + full_url + '" target="_blank">' + url + '</a>'; + } + ); + } + return text; } // http://snippets.dzone.com/posts/show/6995 var URLRegex = /((http\:\/\/|https\:\/\/|ftp\:\/\/)|(www\.))+(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/i; +var PicRegex = /\.(jpg|jpeg|png|gif|bmp)$/i; function buildMessageDiv(msg) { - var match = URLRegex.exec(msg.content) - if (match) { - return '<div class="msgDev"><b>' + msg.nick + ': </b>' - + '<a href="' + match[0] + '" target="_blank">' - + '<img height="150" width="150" src="' - + match[0] + '" /></a></div>'; - } else { - return '<div class="msgDiv"><b>' + msg.nick + ": </b>" - + msg.content + "</div>"; - } + function buildContent(content) { + var match = URLRegex.exec(content) + if (match && PicRegex.test(match[0])) { + return '<a href="' + match[0] + '" target="_blank">' + + '<img height="150" width="150" src="' + + match[0] + '" /></a>'; + } else { + return linkify(escapeHtml(msg.content)); + } + } + return '<div class="msgDev"><b>' + escapeHtml(msg.nick) + ': </b>' + + buildContent(msg.content) + '</div>'; } function buildChatInterface(users, messages) { - var userList = '<div id="userlist">' + var userList = '<div id="userList">' + $.map(users, buildUserDiv).join('') + '</div>'; var messageList = '<div id="messagePane">' + '<div id="messageList">' @@ -133,21 +157,20 @@ function scrollToBottom(div) { function refresh() { var onSuccess = function(json) { - if (json.messages.length == 0) { - return; - } - - var shouldScroll = isScrolledToBottom($('#messageList')[0]); + if (json.messages.length > 0) { + var shouldScroll = isScrolledToBottom($('#messageList')[0]); - // Ignore our own messages - var filterFunc = function(m) { return m.nick != Nick }; - var msgStr = $.map($.grep(json.messages, filterFunc), - buildMessageDiv).join(''); - $('#messageList').append(msgStr); + // Ignore our own messages + var filterFunc = function(m) { return m.nick != Nick }; + var msgStr = $.map($.grep(json.messages, filterFunc), + buildMessageDiv).join(''); + $('#messageList').append(msgStr); - if (shouldScroll) { - scrollToBottom($('#messageList')[0]); + if (shouldScroll) { + scrollToBottom($('#messageList')[0]); + } } + $("#userList").html($.map(json.users, buildUserDiv).join('')); }; var onError = function(resp, textStatus, errorThrown) {}; |
