var cache = {} var PendingMessages = {} var MessageContentCache = {} var RawFavs = {} var MaxImagePosts = 30 // todo: preload these. also, look into image sprites (no go on animating their sizes tho) // css clipping perhaps? Imgs = { "chatThumb": "/static/img/thumbs/smallheartfaved.gif", "chatThumbBig": "/static/img/thumbs/chatheartover.gif", "chatThumbOff": "/static/img/thumbs/smallheart.gif", "chatThumbDot": "/static/img/thumbs/smallheart.gif", "logThumb": "/static/img/thumbs/heartfaved.gif", "logThumbBig": "/static/img/thumbs/heartover.gif", "logThumbOff": "/static/img/thumbs/heart.gif" } Anim = { "chatThumbBig": {"width": "27px", "height": "27px", "right": "0px", "bottom": "2px"}, "chatThumbTiny": {"width": "8px", "height": "8px", "right": "8px", "bottom": "8px"}, "chatThumb": {"width": "16px", "height": "16px", "right": "4px", "bottom": "4px"}, "logThumb": {"width": "27px", "height": "27px", "marginRight": "0px", "marginTop": "0px"}, "logThumbBig": {"width": "64px", "height": "64px", "marginRight": "-20px", "marginTop": "-20px"} } // Utils /*Object.size = function(obj) { var size = 0, key; for (key in obj) { if (obj.hasOwnProperty(key)) size++; } return size; };*/ isEmptyObject = function(obj) { for (key in obj) { if (obj.hasOwnProperty(key)) return false; } return true } function isCSSPropertySupported(prop){ return prop in document.body.style } function escapeHtml(txt) { if (!txt) { return ""; } else { return $("").text(txt).html(); } } URLRegex = /((\b(http\:\/\/|https\:\/\/|ftp\:\/\/)|(www\.))+(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?)/gi; PicRegex = /\.(jpg|jpeg|png|gif|bmp|svg)$/i; function getImagesAsArray(text) { var imgs = [] var urls = text.match(URLRegex) if (urls === null) return imgs for (var i = 0; i"; break; case 'youtube': Youtube.startAnimation(); return "" + "" + ""; break; default: return "" + url + ""; } } Youtube = { "timer": 0, "startAnimation": function(){ if (!Youtube.timer) Youtube.timer = setTimeout(Youtube.animate, 1000) }, "animate": function(){ var thumbs = $(".youtube-thumb") thumbs.each(Youtube.nextThumb) if (thumbs.length == 0){ clearTimeout(Youtube.timer) Youtube.timer = 0 } else Youtube.timer = setTimeout(Youtube.animate, 1000); }, "nextThumb": function(){ var img = $(this); // yt thumb url is http://i.ytimg.com/vi/0123456789A/1.jpg var v = img.attr("src").substr(22,11) var num = img.attr("src").charAt(34); img.attr("src", (Youtube.nextThumbUrl(v, num))) }, "nextThumbUrl": function(v, num){ if (!num) num = 0; num = (parseInt(num) % 3) + 1 // cycle over 1,2,3 return "http://i.ytimg.com/vi/" + v + "/" + num + ".jpg" }, } function getUriType(uri){ if (PicRegex.test(uri.file.toLowerCase())) return "image"; if (parseDomain(uri.host) == "youtube.com" && 'v' in uri.queryKey || uri.anchor.indexOf('v') != -1) return "youtube"; return "link"; } function linkifyWithoutImage(text) { LastMsgContainsImage = false return text.replace(URLRegex, linkReplaceWithoutImage); } function linkReplaceWithoutImage(url){ var urlWithoutParams = url.replace(/\?.*$/i, ""); linkUrl = url.indexOf('http://') == 0 ? url : 'http://' + url; return "" + url + "" } // Message Handling var ImageMsgCount = 0 function removeOldMessages(){ // don't count posts that are all text if (LastMsgContainsImage) ImageMsgCount += 1; while (ImageMsgCount > MaxImagePosts) { var imgMsg = $(".contains-image:first") if (imgMsg.length) { imgMsg.prevAll().remove() // remove all text messages before the image message imgMsg.remove() } else break; ImageMsgCount -= 1; } } function buildMsgContent(content) { return linkify(escapeHtml(content)); } // todo: // isLoading doesn't get passed the right thing by $.map in addMessages function buildMessageDiv(msg, isLoading) { removeOldMessages() var nick = escapeHtml(msg.nick); var msgId = ('msg_id' in msg) ? 'id="message-' + msg.msg_id + '"' : ''; var loadingClass = isLoading ? ' loading' : ''; var containsImageClass = LastMsgContainsImage ? ' contains-image' : ''; return '
' + '' + nick + '' + ' ' + '' + buildMsgContent(msg.content) + '
'; } function buildUserDiv(user) { if (user.avatar) { return ''; } else { return ''; } } // Growl function buildGrowlDataAndPopDatShit(msg) { var nick = escapeHtml(msg.nick); nick = '' + nick + ':' var msg = buildMsgContent(msg.content) growl(nick, msg) } function growl(user, msg) { $.gritter.add({title: user, text: msg}); } function handleMsgError(resp) { var respText = resp.responseText ? resp.responseText.trim() : false; if (respText == 'MUST_LOGIN') { alert("Can't send message! Please login."); } else if (respText) { alert("Can't send message! " + respText); } else { alert("Can't send message!"); } } // Messages function invalidImageDomain(content) { var words = content.toLowerCase().split(' '); for (var i = 0; i < words.length; i++) { var w = words[i]; if (PicRegex.test(w)) { for (var j = 0; j < InvalidDomains.length; j++) { var d = InvalidDomains[j]; if (w.indexOf(d) != -1) { return d; } } } } } function submitMessage() { var content = $.trim($('#msgInput').val()); var invalidDomain = invalidImageDomain(content); if (invalidDomain) { $('#msgInput').blur(); // Remove focus to prevent FF alert loop alert("Sorry, cannot accept images from " + invalidDomain + ". Maybe host the image elsewhere?"); return; } $('#msgInput').val(''); if (content == '') { return; } if (content.length > 1337) { alert("POST TOO LONG DUDE!"); return; } // this shouldn't just be client side :V PendingMessages[content] = true; var msg = { 'nick': Nick, 'content': content }; var div = addNewMessage(msg, true); var onSuccess = function(json) { if (typeof pageTracker !== 'undefined') { pageTracker._trackEvent('Message', 'Submit', typeof Room !== 'undefined' ? Room : 'UnknownRoom'); } div.attr('id', 'message-' + json) .removeClass('loading').addClass('loaded'); }; var onError = function(resp, textStatus, errorThrown) { div.remove(); handleMsgError(resp); }; $.ajax({ type: 'POST', timeout: 5000, url: '/msg', data: { 'room': Room, 'content': content }, cache: false, dataType: 'json', success: onSuccess, error: onError }); } function ifEnter(fn) { return function(e) { if (e.keyCode == 13) { fn(); } }; } function addNewMessages(msgs) { var msgStr = $.map(msgs, buildMessageDiv).join(''); $('#messageList').append(msgStr); } function addNewMessage(msg, isLoading) { var msgStr = buildMessageDiv(msg, isLoading); var div = $(msgStr).appendTo('#messageList'); return div; } function setUserList(users) { $("#userList").html($.map(users, buildUserDiv).join('')); } function flattenUserJson(users) { var s = ""; $.map(users.sort(), function(user) { s += user.nick + user.avatar; }); return s; } function updateUI(msgs, users) { if (window['growlize'] && msgs && msgs.length > 0) { $.map(msgs, buildGrowlDataAndPopDatShit) } else if (msgs && msgs.length > 0) { addNewMessages(msgs); } if (users !== null) { var flattened = flattenUserJson(users); if (!('userlist' in cache) || flattened != cache.userlist) { $("#userList").html($.map(users.sort(sortUsersByAlpha), buildUserDiv).join('')); } cache.userlist = flattened } } function sortUsersByAlpha(a, b){ var nickA = a.nick.toLowerCase() var nickB = b.nick.toLowerCase() if (nickA > nickB) return 1 else if (nickA < nickB) return -1 return 0 } function isDuplicateMessage(m) { if (m.nick == Nick && m.content in PendingMessages) { delete PendingMessages[m.content]; return true; } else { return false; } } var CurrentTopic = null; function isSameTopic(curTopic, newTopic) { if (!!curTopic != !!newTopic) { return false; } else if (!curTopic) { return false; } // => !newTopic also else { return curTopic.topic == newTopic.topic && curTopic.deadline == newTopic.deadline && curTopic.maker == newTopic.maker; } } function updateTopic(newTopic) { if (isSameTopic(CurrentTopic, newTopic)) { return; } alert('new topic'); CurrentTopic = newTopic; $('#topic').text(topic.topic); } function refresh() { var onSuccess = function(json) { try { if (json.v && (typeof Version == 'undefined' || Version != json.v)) { location.reload(); } Timestamp = json.timestamp; $.map(json.messages, function(msg){ MessageContentCache[msg.msg_id.toString()] = msg.content }) var messages = $.grep( json.messages, function(m) { return !isDuplicateMessage(m) }); updateUI(messages, json.users); if (typeof UnseenMsgCounter !== 'undefined' && !HasFocus) { UnseenMsgCounter += messages.length; } if (json.topic) { updateTopic(json.topic); } } catch(e) { if (IsAdmin && window.console) { console.error(e); } } setTimeout(refresh, 1000); }; var onError = function(resp, textStatus, errorThrown) { var msg = $.trim(resp.responseText); if (msg == "UNKNOWN_ROOM") location.href = "http://dump.fm"; if (IsAdmin && window.console) { console.error(resp, textStatus, errorThrown); } setTimeout(refresh, 1000); }; $.ajax({ type: 'GET', timeout: 5000, url: '/refresh', data: { 'room': Room, 'since': Timestamp }, cache: false, dataType: 'json', success: onSuccess, error: onError }); } function initChat() { $('.oldmsg').each(function() { var dump = $(this); var content = dump.find(".content") MessageContentCache[dump.attr("id").substr(8)] = content.text() content.html(buildMsgContent(content.text())); }); $('#msgInput').keyup(ifEnter(submitMessage)); $('#msgSubmit').click(submitMessage); $('#palette-button').click(paletteToggle); messageList = $("#messageList")[0] initChatThumb() scrollToEnd() scrollWatcher() // see /static/webcam/webcam.js if ('webcam' in window) webcam.init() setTimeout(refresh, 1000); } function makePlainText() { var j = $(this); j.text(j.text()); } function activateProfileEditable() { var onSubmit = function(attr, newVal, oldVal) { newVal = $.trim(newVal); if (newVal == oldVal) { return oldVal }; $.ajax({ type: "POST", timeout: 5000, url: "/update-profile", data: { 'attr': attr, 'val': newVal } }); if (attr == 'avatar') { if (newVal != "") { var s = ''; $('#avatarPic').replaceWith(s).show(); } else { $('#avatarPic').hide(); } } return escapeHtml(newVal); }; if ($('#avatar-editing').length > 0) setupUploadAvatar('upload'); var textareaOpts = { 'default_text': 'Enter here!', 'callback': onSubmit, 'field_type': 'textarea', 'callbackShowErrors': false }; $('#contact.editable, #bio.editable') .editInPlace(textareaOpts) .each(makePlainText); } function enableProfileEdit() { $('img#contact').replaceWith('
'); $('img#bio').replaceWith('
'); $('#contact, #bio, #avatar').addClass('editable'); $('#avatar-editing').show(); var resetPage = function() { location.reload() }; $('#edit-toggle a').text('done editing').click(resetPage); activateProfileEditable(); } function initProfile() { $(".linkify").each(function() { var text = jQuery(this).text(); jQuery(this).html(linkifyWithoutImage(text)); }); $('#edit-toggle').click(enableProfileEdit); activateProfileEditable(); $('.logged-dump .content').each(function() { var t = $(this); t.html(buildMsgContent(t.text())); }); initLogThumb() }; function initLog() { $('.logged-dump .content').each(function() { var t = $(this); t.html(buildMsgContent(t.text())); }); initLogThumb(); } // jesus this logic is ugly function initLogThumb(){ $(".logged-dump .thumb").bind('mouseover mouseout', function(e) { var favorited = $(this).parents(".dump").hasClass("favorite") ? true : false; if (e.type == "mouseover") { if (favorited) { $(this).attr("src", Imgs.logThumbOff); } else { $(this).attr("src", Imgs.logThumbBig); $(this).stop().animate(Anim.logThumbBig, 'fast'); } } else { // mouseout if (favorited) { $(this).attr("src", Imgs.logThumb); $(this).stop().animate(Anim.logThumb, 'fast'); } else { $(this).attr("src", Imgs.logThumbOff); $(this).stop().animate(Anim.logThumb, 'fast'); } } }) } function initChatThumb(){ $(".chat-thumb").live('mouseover mouseout', function(e) { var favorited = $(this).parents(".dump").hasClass("favorite") ? true : false; if (e.type == "mouseover") { if (favorited) { $(this).attr("src", Imgs.chatThumbOff); } else { $(this).attr("src", Imgs.chatThumbBig); $(this).stop().animate(Anim.chatThumbBig, 'fast') } } else { // mouseout if (favorited) { $(this).attr("src", Imgs.chatThumb); $(this).stop().animate(Anim.chatThumb, 'fast'); } else { $(this).stop().animate(Anim.chatThumbTiny, 'fast', 'swing', function(){ $(this).attr("src", Imgs.chatThumbDot) $(this).animate(Anim.chatThumb, 0) }) } } }) } function paletteToChat(img){ var chatText = $("#msgInput").val() if (chatText.length && chatText[chatText.length - 1] != " ") chatText += " " chatText += $(img).attr("src") + " " $("#msgInput").val(chatText) $("#msgInput").focus().val($("#msgInput").val()) //http://stackoverflow.com/questions/1056359/ paletteHide() } paletteImageCache = false function paletteBuildImageThumbs(){ if (paletteImageCache) { var imgs = paletteImageCache } else { var imgs = [] var dupeFilter = {} for(fav in RawFavs){ var parsedImgs = getImagesAsArray(RawFavs[fav]) for (var i=0; i") } } function paletteShow(){ $("#palette").css("display", "block") if (isEmptyObject(RawFavs)) { $('#palette-thumbs').html('

This is where all the stuff you FAV goes!

To FAV a post click the little heart next to a users name.

Everything you fav gets saved to your profile.. Have fun!

'); } else { paletteBuildImageThumbs(); } } function paletteHide(){ $("#palette").css("display", "none") $("#palette-thumbs").html("") } function paletteToggle(){ if ($("#palette").css("display") == "none") paletteShow() else paletteHide() } function setupUpload(elementId, roomKey) { var onSubmit = function(file, ext) { if (!(ext && /^(jpg|png|jpeg|gif|bmp|svg)$/i.test(ext))) { alert('SORRY, NOT AN IMAGE DUDE... '); return false; } }; var onComplete = function(file, response) { var r = $.trim(response); g = r; if (r.match(/FILE_TOO_BIG/)) { var maxSize = r.split(" ")[1] / 1024; alert("Sorry. Your file is just too fucking big. " + maxSize + "KB or less please."); return; } else if (r.match(/FILE_NOT_IMAGE/)) { alert("What did you upload? Doesn't seem like an image. Sorry."); return; } else if (r.match(/INVALID_RESOLUTION/)) { var maxWidth = r.split(" ")[1]; var maxHeight = r.split(" ")[2]; alert("Sorry, the maximum image resolution is " + maxWidth + "x" + maxHeight); return; } else if (r != "OK") { alert(r); return; } if (typeof pageTracker !== 'undefined') { var r = typeof Room !== 'undefined' ? Room : 'UnknownRoom'; pageTracker._trackEvent('Message', 'Upload', r); } } new AjaxUpload(elementId, { action: '/upload/message', autoSubmit: true, name: 'image', data: { room: roomKey }, onSubmit: onSubmit, onComplete: onComplete }); } function setupUploadAvatar(elementId) { // NOTE: AjaxUpload responses aren't converted from JSON. var onSubmit = function(file, error) { $('#spinner').show(); }; var onComplete = function(file, resp) { $('#spinner').hide(); if (resp == 'INVALID_REQUEST') { location.reload(); } else if (resp == 'NOT_LOGGED_IN') { location.reload(); } else if (resp == 'INVALID_IMAGE') { alert("Sorry, dump.fm can't deal with your image. Pick another :("); return; } var s = ''; $('#avatarPic').replaceWith(s).show(); $('#avatar').text(resp); }; new AjaxUpload(elementId, { action: '/upload/avatar', autoSubmit: true, name: 'image', onSubmit: onSubmit, onComplete: onComplete }); } // scrolling stuff // this code keeps the div scrolled to the bottom, but will also let the user scroll up, without jumping down function isScrolledToBottom(){ var threshold = 15; var containerHeight = messageList.style.pixelHeight || messageList.offsetHeight var currentHeight = (messageList.scrollHeight > 0) ? messageList.scrollHeight : 0 var result = (currentHeight - messageList.scrollTop - containerHeight < threshold); return result; } function scrollIfPossible(){ if (lastScriptedScrolledPosition <= messageList.scrollTop || isScrolledToBottom()) scrollToEnd() } var lastScriptedScrolledPosition = 0 function scrollToEnd(){ messageList.scrollTop = messageList.scrollHeight lastScriptedScrolledPosition = messageList.scrollTop } function scrollWatcher(){ scrollIfPossible() setTimeout(scrollWatcher, 500) } // well fuck webkit for not supporting {text-decoration: blink} function blinkStart(){ blinkTimer = setInterval(function(){ $(".blink").removeClass("blink").addClass("blink-turning-off") $(".blink-off").removeClass("blink-off").addClass("blink") $(".blink-turning-off").removeClass("blink-turning-off").addClass("blink-off") },500); } function blinkStop(){ clearInterval(blinkTimer); } function initDirectory() { $(".linkify").each(function() { var text = jQuery(this).text(); jQuery(this).html(linkify(text)); }); } //big hand stuff // TODO: replace this with simple pointer-events thing. function initBigHand(id){ var cursorId = "#cursor-big" var cursor = $(cursorId)[0] // jquery's reported element sizes are not exactly the same as the browser's 'mouseover' target sizes // so we'll allow a few pixels extra var fudgeFactor = 2 $(id).addClass("no-cursor") // i have to do this weirdly bc putting the cursor image where the mouse cursor is causes problems with mouse events: // * it stops mousemove events on the image below the mouse cursor // * it fucks up mouseover/out and even mouseenter/leave events, as well as click // so i am doing this: // on mousing over the image: // make cursor visible // find image co-ords // bind a global mousemove func // bind cursor click event // unbind mouseover // mousemove func: // move image to mouse co-ords // if mouse co-ords are outside the image co-ords: // make cursor invisible // unbind mousemove func // unbind cursor click event var mousemove = function(e){ var y = e.pageY, x = e.pageX, coords = initBigHand.coords cursor.style.top = y + "px" cursor.style.left = x - 32 + "px" // 32: (4 pixels * 8 pixels per big pixel) to line up pointy finger with cursor if (y < coords.top || y > coords.bottom || x < coords.left || x > coords.right) { $(cursorId).addClass('invisible') $(cursorId).css({"top": 0, "left": 0 }) $(cursorId).unbind('click', cursorClick) $('logo7').unbind('mousemove', mousemove) $(id).mouseover(imageMouseOver) } } var cursorClick = function(){ $(id).click() } var imageMouseOver = function(){ //console.log("moused over...") initBigHand.coords = { "left": $(id).offset().left - fudgeFactor, "top": $(id).offset().top - fudgeFactor, "right": $(id).offset().left + $(id).width() + fudgeFactor, "bottom": $(id).offset().top + $(id).height() + fudgeFactor } $('body').mousemove(mousemove) $(cursorId).click(cursorClick) $(cursorId).removeClass('invisible') $(id).unbind('mouseover', imageMouseOver) } $(id).mouseover(imageMouseOver) } // grab message id etc from some element e that's inside a dump // (messages have something like id="message-0001" class="dump" ) function getMessageInfo(e){ var message = $(e).parents(".dump") var id = message.attr("id").substr(8) // cut "message-001" to "001" var nick = message.attr("nick") var link = "http://dump.fm/p/" + nick + "/" + id var content = message.find(".linkify") if (!content.length) content = message.find(".content") var rawContent = content.html() var img = content.find("img").attr("src") var via = "via " + nick + " on dump.fm" return {"nick": nick, "id": id, "link": encodeURIComponent(link), "content": rawContent, "img": encodeURIComponent(img), "via": encodeURIComponent(via)} } Share = { "openLink": function(url){ window.open(url, "_blank") }, "facebook": function(button){ var message = getMessageInfo(button) var url = "http://www.facebook.com/share.php?u=" + message.img + "&t=" + message.via Share.openLink(url) }, "tumblr": function(button){ var message = getMessageInfo(button) var url = "http://www.tumblr.com/share?v=3&u=" + message.img + "&t=" + message.via Share.openLink(url) }, "twitter": function(button){ var message = getMessageInfo(button) var url = "http://twitter.com/home?status=" + message.img + encodeURIComponent(" ") + message.via Share.openLink(url) }, "delicious": function(button){ var message = getMessageInfo(button) var url = "http://delicious.com/save?url=" + message.img + "&title=" + message.img + "¬es=" + message.via Share.openLink(url) } } Tag = { "favorite": function(button){ var message = getMessageInfo(button) var favorited = ($(button).parents(".dump").hasClass("favorite")) ? true : false if (favorited) { Tag.rm(message.id, "favorite") $(button).parents(".dump").removeClass("favorite") if (RawFavs[message.id]) { delete RawFavs[message.id] paletteImageCache = false } } else { Tag.add(message.id, "favorite") $(button).parents(".dump").addClass("favorite") if (RawFavs && MessageContentCache[message.id]) { // chat ui stuff if ($("#palette-button").css("display") == "none") paletteButtonShowAnim() RawFavs[message.id] = MessageContentCache[message.id] paletteImageCache = false } } }, "add": function(message_id, tag){ Tag.ajax("/cmd/tag/add", {"message_id": message_id, "tag": tag}) }, "rm": function(message_id, tag){ Tag.ajax("/cmd/tag/rm", {"message_id": message_id, "tag": tag}) }, "ajax": function(url, data) { $.ajax({ "type": 'POST', "timeout": 5000, "url": url, "data": data, "cache": false }); } } // uhhh todo: move preload stuff into js: // var nextImage = new Image(); // nextImage.src = "your-url/newImage.gif"; // mAcRoMeDiA sHiT function MM_swapImgRestore() { //v3.0 var i,x,a=document.MM_sr; for(i=0;a&&i0&&parent.frames.length) { d=parent.frames[n.substring(p+1)].document; n=n.substring(0,p);} if(!(x=d[n])&&d.all) x=d.all[n]; for (i=0;!x&&i, MIT License function parseUri (str) { var o = parseUri.options, m = o.parser[o.strictMode ? "strict" : "loose"].exec(str), uri = {}, i = 14; while (i--) uri[o.key[i]] = m[i] || ""; uri[o.q.name] = {}; uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) { if ($1) uri[o.q.name][$1] = $2; }); return uri; }; parseUri.options = { strictMode: false, key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], q: { name: "queryKey", parser: /(?:^|&)([^&=]*)=?([^&]*)/g }, parser: { strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/, loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ } }; // end parseUri function parseDomain(host){ return host.toLowerCase().replace(/^www\./, "") } // Away notification var UnseenMsgCounter = 0; var OrigTitle = $('title').text(); var HasFocus = true; function onFocus() { HasFocus = true; UnseenMsgCounter = 0; $('title').text(OrigTitle); } function onBlur() { HasFocus = false; } function titleUpdater() { if (HasFocus || UnseenMsgCounter == 0 || $('title').text() != OrigTitle) { $('title').text(OrigTitle); } else { var plural = UnseenMsgCounter > 1 ? 's' : ''; $('title').text(UnseenMsgCounter + ' new dump' + plural + '! | ' + OrigTitle); } setTimeout(titleUpdater, 2000); } function startTitleUpdater() { $(window).blur(onBlur); $(window).focus(onFocus); setTimeout(titleUpdater, 2000); }