diff options
Diffstat (limited to 'static/resizetest/dump.fm.all.files/pichat.js')
| -rwxr-xr-x | static/resizetest/dump.fm.all.files/pichat.js | 1704 |
1 files changed, 1704 insertions, 0 deletions
diff --git a/static/resizetest/dump.fm.all.files/pichat.js b/static/resizetest/dump.fm.all.files/pichat.js new file mode 100755 index 0000000..6da51b0 --- /dev/null +++ b/static/resizetest/dump.fm.all.files/pichat.js @@ -0,0 +1,1704 @@ +// http://plugins.jquery.com/files/jquery.cookie.js.txt +jQuery.cookie=function(name,value,options){if(typeof value!='undefined'){options=options||{};if(value===null){value='';options.expires=-1;} +var expires='';if(options.expires&&(typeof options.expires=='number'||options.expires.toUTCString)){var date;if(typeof options.expires=='number'){date=new Date();date.setTime(date.getTime()+(options.expires*24*60*60*1000));}else{date=options.expires;} +expires='; expires='+date.toUTCString();} +var path=options.path?'; path='+(options.path):'';var domain=options.domain?'; domain='+(options.domain):'';var secure=options.secure?'; secure':'';document.cookie=[name,'=',encodeURIComponent(value),expires,path,domain,secure].join('');}else{var cookieValue=null;if(document.cookie&&document.cookie!=''){var cookies=document.cookie.split(';');for(var i=0;i<cookies.length;i++){var cookie=jQuery.trim(cookies[i]);if(cookie.substring(0,name.length+1)==(name+'=')){cookieValue=decodeURIComponent(cookie.substring(name.length+1));break;}}} +return cookieValue;}}; + +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": "54px", "height": "54px", "right": "0px", "bottom": "2px"}, + "chatThumbTiny": {"width": "16px", "height": "16px", "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": "-2px", "marginTop": "-2px"} +} + +// Utils + +isEmptyObject = function(obj) { + for (key in obj) { + if (obj.hasOwnProperty(key)) return false; + } + return true +} + +String.prototype.trim = function(){ return this.replace(/^\s+|\s+$/g,'') } + +function isCSSPropertySupported(prop){ return prop in document.body.style } + +function track(group, name) { + if (typeof pageTracker !== 'undefined') { + pageTracker._trackEvent(group, name, + typeof Nick !== 'undefined' ? Nick : 'anon'); + } +} + +var Preferences = { + "Domain": '.dump.fm', + + "getProperty": function(prop, defaultValue) { + var value = $.cookie(prop); + return (value !== null) ? value : defaultValue; + }, + + "setProperty": function(prop, val) { + $.cookie(prop, val, { domain: Preferences.Domain, path: '/' }); + }, + + "delProperty": function(prop) { + $.cookie(prop, null, { domain: Preferences.Domain, path: '/' }); + } +}; + +function escapeHtml(txt) { + if (!txt) { return ""; } +// txt = annoyingCaps(txt) + return $("<span>").text(txt).html() +} + +var Log = { + "Levels": ['info', 'warn', 'error'], + "AjaxSubmitLevels": ['warn', 'error'], + "AjaxSubmitPath": "/logerror", + + "SupplementalInfo": function() { + return { 'user': UserInfo && UserInfo.nick }; + }, + + "ajaxSubmit": function(level, component, msg) { + var info = Log.SupplementalInfo(); + var data = { 'level': level, 'component': component, 'msg': msg }; + $.extend(info, data); + + $.ajax({type: 'POST', + timeout: 5000, + url: Log.AjaxSubmitPath, + data: info + }); + }, + + "initialize": function() { + $.each(Log.Levels, function(i, level) { + Log[level] = function(component, msg) { + if (window.console && window.console[level]) + window.console[level](args); + if (Log.AjaxSubmitLevels.indexOf(level) != -1) + Log.ajaxSubmit(level, args); + }; + }); + } +}; + +Log.initialize(); + + +URLRegex = /((\b(http\:\/\/|https\:\/\/|ftp\:\/\/)|(www\.))+(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?)/gi; +PicRegex = /\.(jpg|jpeg|png|gif|bmp|svg|fid)$/i; + +function getImagesAsArray(text) { + var imgs = [] + var urls = text.match(URLRegex) + if (urls === null) return imgs + for (var i = 0; i<urls.length; i++){ + var url = urls[i] + var urlWithoutParams = url.replace(/\?.*$/i, ""); + if (PicRegex.test(urlWithoutParams)) + imgs.push(url) + } + return imgs +} + +function linkify(text) { + LastMsgContainsImage = false + text = text.replace(URLRegex, linkReplace); + return text +} + +function annoyingCaps(text){ + var chunks = text.split(" ") + for(var i=0; i<chunks.length; i++){ + var chunk = chunks[i] + if (!chunk.length || chunk.substr(0,4) == 'http') continue; + var letters=chunk.split("") + for(var j = 0; j<letters.length; j++){ + if (j % 2) letters[j] = letters[j].toUpperCase() + else letters[j] = letters[j].toLowerCase() + } + chunks[i] = letters.join("") + } + return chunks.join(" ") +} + + +function imgClickHandler() { + // Ugly hack. Don't open new links in chat, only in logs. + // Ugly hack mkII: ensure middle-click opens images in new tab + // c.f. http://code.google.com/p/chromium/issues/detail?id=255 + if ($.browser.webkit && event.button != 0) { + event.stopPropagation(); + } else { + return $('#chatrap').length == 0; + } +} + +// durty hack to use a global to check this... but otherwise i'd have to rewrite the String.replace function? :/ +var LastMsgContainsImage = false +function linkReplace(url) { + var lowerurl = url.toLowerCase(); + if (lowerurl.indexOf('http://') == 0 || lowerurl.indexOf('https://') == 0 || lowerurl.indexOf('ftp://') == 0) + linkUrl = url; + else + linkUrl = 'http://' + url; + + var uri = parseUri(url) + var type = getUriType(uri) + + if (type == 'image') { + LastMsgContainsImage = true; + return "<a target='_blank' href='" + linkUrl + "' class='img-wrapper' onclick='return imgClickHandler()'><img src='" + linkUrl + "'></a>"; + } else if (type == 'youtube') { + Youtube.startAnimation(); + return "<a target='_blank' class='youtube' href='" + linkUrl + "'>" + + "<img class='youtube-thumb' width='130' height='97' src='"+Youtube.nextThumbUrl(uri.queryKey.v)+"'>" + + "<img class='youtube-controls' src='/static/img/youtube.controls.png'></a>" + } else if (type == 'midi' || type == 'wav') { + return '<embed src="'+linkUrl+'" loop="false" autostart="false" volume="80" width="150" height="20" style="vertical-align:bottom"> <a href="'+linkUrl+'">'+uri.file+'</a>' + } else + return "<a target='_blank' href='" + linkUrl + "'>" + url + "</a>"; + +} + +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"; + + if (uri.path.substr(-4) == ".mid" || uri.path.substr(-5) == ".midi") + return "midi" + + if (uri.path.substr(-4) == ".wav") + return "wav" + + + 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 "<a target='_blank' href='" + linkUrl + "'>" + url + "</a>" +} + +// 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; + } +} + +var TextEnabled = Preferences.getProperty("chat.textEnabled", "true") == "true"; + +function setTextEnable() { + if ($(this).attr('checked')) { + TextEnabled = true; + Preferences.setProperty("chat.textEnabled", "true"); + track('UI', 'TextEnabled'); + $('.dump').not('.contains-image').show(); + } else { + TextEnabled = false; + Preferences.setProperty("chat.textEnabled", "false"); + track('UI', 'TextDisabled'); + $('.dump').not('.contains-image').hide() + } +}; + +function buildMsgContent(content) { + if (content.substr(0,6) == "<safe>") + return content.substr(6,content.length - 13) + else return linkify(escapeHtml(content)); +} + +// todo: +// isLoading doesn't get passed the right thing by $.map in addMessages +function buildMessageDiv(msg, isLoading) { + var nick = escapeHtml(msg.nick); + removeOldMessages(); + + var builtContent = buildMsgContent(msg.content); + + var msgId = ('msg_id' in msg) ? 'id="message-' + msg.msg_id + '"' : ''; + var loadingClass = isLoading ? ' loading' : ''; + var containsImageClass = LastMsgContainsImage ? ' contains-image' : ''; + var displayStyle = (TextEnabled || LastMsgContainsImage) ? '' : ' style="display: none"'; + + return '<div class="msgDiv dump ' + loadingClass + containsImageClass + '" ' + msgId + displayStyle + '>' + + '<span class="nick"><b><a href="http://dump.fm/' + nick + ' ">' + nick + '</a></b>' + + ' <img src="'+Imgs.chatThumbDot+'" class="chat-thumb" onclick="Tag.favorite(this)"> ' + + '</span>' + + '<span class="content">' + builtContent + '</span>' + + '</div>'; +} + +function buildUserDiv(user) { + if (user.avatar) { + return '<div class="username">' + + '<a href="http://dump.fm/' + escapeHtml(user.nick) + '" target="_blank">' + + '<img src="' + user.avatar + '" width="50" height="50">' + + escapeHtml(user.nick) + '</a></div>'; + } else { + return '<div class="username">' + + '<a href="/' + escapeHtml(user.nick) + '" target="_blank">' + + '<img src="/static/img/noinfo.png" width="50" height="50">' + + escapeHtml(user.nick) + '</a></div>'; + } +} + +// Favs + +function buildFav(f) { + var h = '<div class="fav-note">' + + '<img src="/static/img/thumbs/chatheartover.gif">' + + '<a href="http://dump.fm/' + f.from + '">' + f.from + '</a>' + + ' <span>just faved you!</span>' + + '</div>'; + return $(h); +} + +function removeFavAndHideBox() { + $(this).remove(); + if ($('#favbox').children().length == 0) + $('#favbox').hide(); +} + +function showFav(f) { + $('#favbox').show(); + buildFav(f).appendTo('#favbox').animate( + {"opacity": 0}, + {"duration": 9000, + "easing": "easeInExpo", + "complete": removeFavAndHideBox + }) + +} + + +function updateFavs(fs) { + if (fs.length == 0) + return; + $('#favbox').show(); + $(fs).each(function(i, f) { showFav(f) }); +} + + +// Growl + +function buildGrowlDataAndPopDatShit(msg) { + var nick = escapeHtml(msg.nick); + nick = '<a href="http://dump.fm/' + nick + ' " style="color:pink">' + nick + '</a>:' + 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 > 2468) { + 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: 15000, + 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, favs) { + 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 + } + updateFavs(favs); +} + +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; + } +} + +function refresh() { + var onSuccess = function(json) { + try { + 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, json.favs); + if (!Away.HasFocus) + Away.UnseenMsgCounter += messages.length; + } 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, 4000); + }; + + $.ajax({ + type: 'GET', + timeout: 5000, + url: '/refresh', + data: { 'room': Room, 'since': Timestamp }, + cache: false, + dataType: 'json', + success: onSuccess, + error: onError + }); +} + +function sendClicked(){ + track('UI', 'SendButtonActuallyClicked'); + submitMessage(); +} + +function paletteClicked(){ + track('UI', 'FavPaletteActuallyClicked'); + paletteToggle(); +} + + +function initChat() { + Search.init() + + $('.oldmsg').each(function() { + var dump = $(this); + var content = dump.find(".content") + MessageContentCache[dump.attr("id").substr(8)] = content.text() + content.html(buildMsgContent(content.text())); + if (!TextEnabled && !dump.hasClass('contains-image')) + dump.hide(); + else + dump.show(); + }); + + $('#msgInput').keyup(ifEnter(submitMessage)); + $('#msgSubmit').click(sendClicked); + $('#palette-button').click(paletteClicked); + + messageList = $("#messageList")[0] + + initChatThumb(); + + scrollToEnd() + scrollWatcher() + + // see /static/webcam/webcam.js + if ('webcam' in window) webcam.init() +} + +function startChatUpdater() { + 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 = '<img id="avatarPic" src="' + newVal + '" width="150" />'; + $('#avatarPic').replaceWith(s).show(); + } else { + $('#avatarPic').hide(); + } + } + return escapeHtml(newVal); + }; + + if ($('#avatar-editing').length > 0) + setupUploadAvatar('uploadp'); + + 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('<div id="contact" class="linkify"></div>'); + $('img#bio').replaceWith('<div id="bio" class="linkify"></div>'); + $('#contact, #bio, #avatar').addClass('editable'); + $('#avatar-editing').show(); + var resetPage = function() { location.reload() }; + $('#edit-toggle a').text('done editing').click(resetPage); + activateProfileEditable(); +} + +function initProfile() { + Search.init() + $(".linkify").each(function() { + var text = jQuery(this).text(); + jQuery(this).html(linkifyWithoutImage(text)); + }); + + $('#edit-toggle').click(enableProfileEdit); + activateProfileEditable(); + + $('.dash-dump .content').each(function() { + var t = $(this); + t.html(buildMsgContent(t.text())); + }); +}; + +function initLog() { + Search.init() + $('.logged-dump .content').each(function() { + var t = $(this); + t.html(buildMsgContent(t.text())); + }); + initLogThumb(".logged-dump .thumb", '.dump'); +} + +function initLogThumb(selector, parentSelector) { + $(selector).bind('mouseover mouseout', + function(e) { + var favorited = $(this).parents(parentSelector).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).delay(600).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<parsedImgs.length; i++){ + var img = parsedImgs[i] + if (!dupeFilter[img]) { + imgs.push(img) + dupeFilter[img] = true + } + } + } + paletteImageCache = imgs + } + + for(var i=0; i<imgs.length; i++){ + $("#palette-thumbs").append("<img onclick='paletteToChat(this)' src='"+imgs[i]+"'>") + } +} + +function paletteShow(){ + $("#palette").css("display", "block") + if (isEmptyObject(RawFavs)) { + $('#palette-thumbs').html('<div style="width:300px;color:#000;">This is where all the stuff you FAV goes!<br><br>To FAV a post click the little heart <img src="/static/img/thumbs/smallheart.gif"> next to a users name.<br><br> Everything you fav gets saved to your profile.. Have fun!</div>'); + } 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); + if (r.match(/FILE_TOO_BIG/)) { + var maxSize = r.split(" ")[1] / 1024; + alert("Sorry. Your file is just too darn 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(); + var r = $.trim(resp); + + if (r == 'INVALID_REQUEST') { + location.reload(); + } else if (r == 'NOT_LOGGED_IN') { + location.reload(); + } else if (r == 'INVALID_IMAGE') { + alert("Sorry, dump.fm can't deal with your image. Pick another :("); + return; + } else if (r.match(/FILE_TOO_BIG/)) { + var maxSize = r.split(" ")[1] / 1024; + alert("Sorry. Your avatar is just too fucking big. " + + maxSize + "KB or less please."); + return; + } else if (r.match(/INVALID_RESOLUTION/)) { + var maxWidth = r.split(" ")[1]; + var maxHeight = r.split(" ")[2]; + alert("Sorry, the maximum avatar resolution is " + + maxWidth + "x" + maxHeight); + return; + } + + var s = '<img id="dashavatarPic" src="' + r + '" />'; + $('#dashavatar').html(s).show(); + }; + new AjaxUpload(elementId, { + action: '/upload/avatar', + autoSubmit: true, + name: 'image', + onSubmit: onSubmit, + onComplete: onComplete + }); +} + + +/* + search adds a script to the page... the script will call either Search.searchResult() or Search.searchError() + todo: clean this up. remove duplicated function names etc +*/ + +var Search = { + + 'term': "", + 'imagesPerPage': 25, + 'images': [], + 'tokens': [], + + 'init': function(){ + $("#search-query").val("search dump.fm") + $("#search-query").focus(function(){ + if ($("#search-query").val() == 'search dump.fm') + $("#search-query").val("") + }) + $("#search-query").blur(function(){ + if ($("#search-query").val().trim() == '') + $("#search-query").val("search dump.fm") + }) + $("#search-query").keydown(ifEnter(Search.doSearch)) + $("#search-results-images a").live("mouseup", Search.click) + }, + + 'addScript': function(term) { + $("#search-script").remove() + $("head").append("<scr"+"ipt src='/cmd/ghettosearch/"+term+"' id='search-script'></sc"+"ript>") + }, + 'setContent': function(x){ + $("#search-results-images").html(x) + }, + 'setMessage': function(x){ + $("#search-controls").css("display", "block") + $("#search-control-text").html(x) + }, + 'searchError': function(error){ + Search.setContent("") + $('#search-control-previous').css("visibility", "hidden") + $('#search-control-next').css("visibility", "hidden") + Search.setMessage(error) + }, + + 'doSearch': function(){ + term = $("#search-query").val().trim().toLowerCase() + var rawTokens = term.split(" ") + Search.tokens = [] + for(var t = 0; t < rawTokens.length; t++) { + if (rawTokens[t].length > 2) + Search.tokens.push(rawTokens[t]) + } + if (Search.tokens.length == 0) { + Search.setMessage("search query too small") + } else { + Search.setMessage("searching for '"+Search.tokens.join(" and ")+"'") + Search.addScript(Search.tokens.join("+")) + } + }, + + 'renderPage': function(num){ + $("#search-results-images").css("display", "block") + $("#search-controls").css("display", "block") + if (Search.images.length > 0) + Search.setMessage("results for '"+Search.tokens.join(" and ")+"' (page " + (num + 1) + ")"); + var contentString = '' + var start = num * Search.imagesPerPage + var imageCounter = 0 + for(var i = start; i < Search.images.length; i++){ + if(imageCounter > Search.imagesPerPage) break; + contentString += '<a href="'+Search.images[i]+'" target="_blank" onclick="return false"><img src="'+Search.images[i]+'"></a>' + imageCounter += 1 + } + contentString += '<br><br><div id="search-commands">' + if(num > 0) { + $('#search-control-previous').attr("href", 'javascript:Search.renderPage('+(num-1)+')') + $('#search-control-previous').css("visibility", "visible") + } else { + $('#search-control-previous').attr("href", 'javascript:void()') + $('#search-control-previous').css("visibility", "hidden") + } + + if (Search.images.length > start + imageCounter) { + $('#search-control-next').attr("href", 'javascript:Search.renderPage('+(num+1)+')') + $('#search-control-next').css("visibility", "visible") + } else { + $('#search-control-next').attr("href", 'javascript:void()') + $('#search-control-next').css("visibility", "hidden") + } + Search.setContent(contentString) + }, + + 'click': function(e){ + if (e.which == 1) // left click + if (Search.addToChatBoxIfPossible(this)) + window.open(this.href) + else if (e.which == 2) // middle click + window.open(this.href) + }, + + 'addToChatBoxIfPossible': function(img){ + var chatBoxExists = $("#msgInput").length + if (chatBoxExists) { + var chatText = $("#msgInput").val() + if (chatText.length && chatText[chatText.length - 1] != " ") + chatText += " " + chatText += $(img).attr("href") + " " + $("#msgInput").val(chatText) + $("#msgInput").focus().val($("#msgInput").val()) //http://stackoverflow.com/questions/1056359/ + return false + } else return true + }, + + 'searchResult': function(results){ + Search.images = [] + var alreadyGot = {} + if(results === null || results.length == 0) { + Search.setContent("") + Search.setMessage("no results found") + } else { + for(var r = 0; r<results.length; r++){ + var content = results[r]['content'] + if (content.substring(0,6) == "<safe>") continue; // skip html posts + var imageUrls = getImagesAsArray(content); + for (var i=0; i<imageUrls.length; i++){ + var imageUrl = imageUrls[i]; + if (imageUrl in alreadyGot) continue; + alreadyGot[imageUrl] = true + var validImage = true; + for(var t = 0; t<Search.tokens.length; t++){ + if (imageUrl.toLowerCase().indexOf(Search.tokens[t]) == -1) { + validImage = false; + break; + } + } + if (validImage) + Search.images.push(imageUrl); + } + } + if (Search.images.length == 0) { + Search.setMessage("no results found") + } + Search.renderPage(0) + } + }, + 'close': function(){ + Search.setContent("") + $('#search-control-previous').css("visibility", "hidden") + $('#search-control-next').css("visibility", "hidden") + $("#search-results-images").css("display", "none") + $("#search-controls").css("display", "none") + } + + +} + + +// 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 t = $(this); + t.html(buildMsgContent(t.text())); + }); + Search.init() + initLogThumb('.dlogged-dump .thumb', '.dlogged-dump'); +} + +//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&&i<a.length&&(x=a[i])&&x.oSrc;i++) x.src=x.oSrc; +} + +function MM_preloadImages() { //v3.0 + var d=document;if(d.images){ if(!d.MM_p) d.MM_p=new Array();var i,j=d.MM_p.length,a=MM_preloadImages.arguments; for(i=0; i<a.length; i++) if (a[i].indexOf("#")!=0){ d.MM_p[j]=new Image; d.MM_p[j++].src=a[i];}} +} + +function MM_findObj(n, d) { //v4.01 + var p,i,x; if(!d) d=document; if((p=n.indexOf("?"))>0&&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<d.forms.length;i++) x=d.forms[i][n]; + for(i=0;!x&&d.layers&&i<d.layers.length;i++) x=MM_findObj(n,d.layers[i].document); + if(!x && d.getElementById) x=d.getElementById(n); return x; +} + +function MM_swapImage() { //v3.0 + var i,j=0,x,a=MM_swapImage.arguments; document.MM_sr=new Array; for(i=0;i<(a.length-2);i+=3) + if ((x=MM_findObj(a[i]))!=null){document.MM_sr[j++]=x; if(!x.oSrc) x.oSrc=x.src; x.src=a[i+2];} +} + +function timeFunc(f){ + var start = new Date().getTime(); + var res = f(); + console.log((new Date().getTime()) - start + " msecs"); + return res; +} + +// parseUri 1.2.2 from http://blog.stevenlevithan.com/archives/parseuri +// (c) Steven Levithan <stevenlevithan.com>, 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\./, "") +} + +var Away = { + "UnseenMsgCounter": 0, + "OrigTitle": "", + "HasFocus": true, + "UpdateFrequency": 3000, + + "onFocus": function() { + Away.HasFocus = true; + Away.UnseenMsgCounter = 0; + // Courtesy http://stackoverflow.com/questions/2952384/changing-the-window-title-when-focussing-the-window-doesnt-work-in-chrome + window.setTimeout(function () { $('title').text(Away.OrigTitle); }, 100); + }, + "onBlur": function() { + Away.HasFocus = false; + }, + + "updateTitle": function () { + if (Away.UnseenMsgCounter > 0) { + var plural = Away.UnseenMsgCounter > 1 ? 's' : ''; + $('title').text(Away.UnseenMsgCounter + ' new dump' + plural + '! | ' + Away.OrigTitle); + } + setTimeout(Away.updateTitle, Away.UpdateFrequency); + + }, + "startTitleUpdater": function() { + Away.OrigTitle = $('title').text(); + $(window).blur(Away.onBlur); + $(window).focus(Away.onFocus); + setTimeout(Away.updateTitle, Away.UpdateFrequency); + } +}; + +var imgZoomThreshhold = [125, 125]; + +function initChatMsgs() { + $('.msgDiv .content').live('mouseenter', function(e) { + $(this).addClass('msg-hover'); + }); + + $('.msgDiv .content').live('mouseleave', function(e) { + $(this).removeClass('msg-hover'); + }); + + $('.msgDiv .content .img-wrapper').live('mouseenter', function(e) { + var img = $(this).find('img'); + + if (img.width() < imgZoomThreshhold[0] || img.height() < imgZoomThreshhold[1]) + return; + + var zoomlink = $('<a>') + .attr({'href': img.attr('src') }) + .addClass('msg-image-zoom') + .append($('<img>').attr('src', 'http://dump.fm/static/img/zoom.gif') + .addClass('zoom-icon')) + .click(function() { window.open(img.attr('src')); return false; }); + $(this).append(zoomlink); + }); + + $('.msgDiv .content .img-wrapper').live('mouseleave', function(e) { + $(this).find('.msg-image-zoom').remove(); + }); + + + $('.content').live('click', function(e) { + var tagName = e.target.tagName; + if (tagName == 'A' || tagName == 'EMBED' || $(e.target).hasClass('youtube-thumb')) { + return true; + } + var msg = $(this).parent('.msgDiv'); + var wasFavorited = msg.hasClass("favorite"); + var button = msg.find('.chat-thumb'); + if (wasFavorited) { + $(button).attr("src", Imgs.chatThumbOff); + } else { + $(button).attr("src", Imgs.chatThumbBig); + $(button).stop().animate(Anim.chatThumbBig, 'fast').animate(Anim.chatThumb, 'fast', 'swing'); + } + Tag.favorite(button); + return false; + }); +} + +// sha1.js + +/* + * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined + * in FIPS 180-1 + * Version 2.2 Copyright Paul Johnston 2000 - 2009. + * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet + * Distributed under the BSD License + * See http://pajhome.org.uk/crypt/md5 for details. + */ + +/* + * Configurable variables. You may need to tweak these to be compatible with + * the server-side, but the defaults work in most cases. + */ +var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */ +var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */ + +/* + * These are the functions you'll usually want to call + * They take string arguments and return either hex or base-64 encoded strings + */ +function hex_sha1(s) { return rstr2hex(rstr_sha1(str2rstr_utf8(s))); } +function b64_sha1(s) { return rstr2b64(rstr_sha1(str2rstr_utf8(s))); } +function any_sha1(s, e) { return rstr2any(rstr_sha1(str2rstr_utf8(s)), e); } +function hex_hmac_sha1(k, d) + { return rstr2hex(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d))); } +function b64_hmac_sha1(k, d) + { return rstr2b64(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d))); } +function any_hmac_sha1(k, d, e) + { return rstr2any(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d)), e); } + +/* + * Perform a simple self-test to see if the VM is working + */ +function sha1_vm_test() +{ + return hex_sha1("abc").toLowerCase() == "a9993e364706816aba3e25717850c26c9cd0d89d"; +} + +/* + * Calculate the SHA1 of a raw string + */ +function rstr_sha1(s) +{ + return binb2rstr(binb_sha1(rstr2binb(s), s.length * 8)); +} + +/* + * Calculate the HMAC-SHA1 of a key and some data (raw strings) + */ +function rstr_hmac_sha1(key, data) +{ + var bkey = rstr2binb(key); + if(bkey.length > 16) bkey = binb_sha1(bkey, key.length * 8); + + var ipad = Array(16), opad = Array(16); + for(var i = 0; i < 16; i++) + { + ipad[i] = bkey[i] ^ 0x36363636; + opad[i] = bkey[i] ^ 0x5C5C5C5C; + } + + var hash = binb_sha1(ipad.concat(rstr2binb(data)), 512 + data.length * 8); + return binb2rstr(binb_sha1(opad.concat(hash), 512 + 160)); +} + +/* + * Convert a raw string to a hex string + */ +function rstr2hex(input) +{ + try { hexcase } catch(e) { hexcase=0; } + var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; + var output = ""; + var x; + for(var i = 0; i < input.length; i++) + { + x = input.charCodeAt(i); + output += hex_tab.charAt((x >>> 4) & 0x0F) + + hex_tab.charAt( x & 0x0F); + } + return output; +} + +/* + * Convert a raw string to a base-64 string + */ +function rstr2b64(input) +{ + try { b64pad } catch(e) { b64pad=''; } + var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + var output = ""; + var len = input.length; + for(var i = 0; i < len; i += 3) + { + var triplet = (input.charCodeAt(i) << 16) + | (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0) + | (i + 2 < len ? input.charCodeAt(i+2) : 0); + for(var j = 0; j < 4; j++) + { + if(i * 8 + j * 6 > input.length * 8) output += b64pad; + else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F); + } + } + return output; +} + +/* + * Convert a raw string to an arbitrary string encoding + */ +function rstr2any(input, encoding) +{ + var divisor = encoding.length; + var remainders = Array(); + var i, q, x, quotient; + + /* Convert to an array of 16-bit big-endian values, forming the dividend */ + var dividend = Array(Math.ceil(input.length / 2)); + for(i = 0; i < dividend.length; i++) + { + dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1); + } + + /* + * Repeatedly perform a long division. The binary array forms the dividend, + * the length of the encoding is the divisor. Once computed, the quotient + * forms the dividend for the next step. We stop when the dividend is zero. + * All remainders are stored for later use. + */ + while(dividend.length > 0) + { + quotient = Array(); + x = 0; + for(i = 0; i < dividend.length; i++) + { + x = (x << 16) + dividend[i]; + q = Math.floor(x / divisor); + x -= q * divisor; + if(quotient.length > 0 || q > 0) + quotient[quotient.length] = q; + } + remainders[remainders.length] = x; + dividend = quotient; + } + + /* Convert the remainders to the output string */ + var output = ""; + for(i = remainders.length - 1; i >= 0; i--) + output += encoding.charAt(remainders[i]); + + /* Append leading zero equivalents */ + var full_length = Math.ceil(input.length * 8 / + (Math.log(encoding.length) / Math.log(2))) + for(i = output.length; i < full_length; i++) + output = encoding[0] + output; + + return output; +} + +/* + * Encode a string as utf-8. + * For efficiency, this assumes the input is valid utf-16. + */ +function str2rstr_utf8(input) +{ + var output = ""; + var i = -1; + var x, y; + + while(++i < input.length) + { + /* Decode utf-16 surrogate pairs */ + x = input.charCodeAt(i); + y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0; + if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF) + { + x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF); + i++; + } + + /* Encode output as utf-8 */ + if(x <= 0x7F) + output += String.fromCharCode(x); + else if(x <= 0x7FF) + output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F), + 0x80 | ( x & 0x3F)); + else if(x <= 0xFFFF) + output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F), + 0x80 | ((x >>> 6 ) & 0x3F), + 0x80 | ( x & 0x3F)); + else if(x <= 0x1FFFFF) + output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07), + 0x80 | ((x >>> 12) & 0x3F), + 0x80 | ((x >>> 6 ) & 0x3F), + 0x80 | ( x & 0x3F)); + } + return output; +} + +/* + * Encode a string as utf-16 + */ +function str2rstr_utf16le(input) +{ + var output = ""; + for(var i = 0; i < input.length; i++) + output += String.fromCharCode( input.charCodeAt(i) & 0xFF, + (input.charCodeAt(i) >>> 8) & 0xFF); + return output; +} + +function str2rstr_utf16be(input) +{ + var output = ""; + for(var i = 0; i < input.length; i++) + output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF, + input.charCodeAt(i) & 0xFF); + return output; +} + +/* + * Convert a raw string to an array of big-endian words + * Characters >255 have their high-byte silently ignored. + */ +function rstr2binb(input) +{ + var output = Array(input.length >> 2); + for(var i = 0; i < output.length; i++) + output[i] = 0; + for(var i = 0; i < input.length * 8; i += 8) + output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (24 - i % 32); + return output; +} + +/* + * Convert an array of big-endian words to a string + */ +function binb2rstr(input) +{ + var output = ""; + for(var i = 0; i < input.length * 32; i += 8) + output += String.fromCharCode((input[i>>5] >>> (24 - i % 32)) & 0xFF); + return output; +} + +/* + * Calculate the SHA-1 of an array of big-endian words, and a bit length + */ +function binb_sha1(x, len) +{ + /* append padding */ + x[len >> 5] |= 0x80 << (24 - len % 32); + x[((len + 64 >> 9) << 4) + 15] = len; + + var w = Array(80); + var a = 1732584193; + var b = -271733879; + var c = -1732584194; + var d = 271733878; + var e = -1009589776; + + for(var i = 0; i < x.length; i += 16) + { + var olda = a; + var oldb = b; + var oldc = c; + var oldd = d; + var olde = e; + + for(var j = 0; j < 80; j++) + { + if(j < 16) w[j] = x[i + j]; + else w[j] = bit_rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1); + var t = safe_add(safe_add(bit_rol(a, 5), sha1_ft(j, b, c, d)), + safe_add(safe_add(e, w[j]), sha1_kt(j))); + e = d; + d = c; + c = bit_rol(b, 30); + b = a; + a = t; + } + + a = safe_add(a, olda); + b = safe_add(b, oldb); + c = safe_add(c, oldc); + d = safe_add(d, oldd); + e = safe_add(e, olde); + } + return Array(a, b, c, d, e); + +} + +/* + * Perform the appropriate triplet combination function for the current + * iteration + */ +function sha1_ft(t, b, c, d) +{ + if(t < 20) return (b & c) | ((~b) & d); + if(t < 40) return b ^ c ^ d; + if(t < 60) return (b & c) | (b & d) | (c & d); + return b ^ c ^ d; +} + +/* + * Determine the appropriate additive constant for the current iteration + */ +function sha1_kt(t) +{ + return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 : + (t < 60) ? -1894007588 : -899497514; +} + +/* + * Add integers, wrapping at 2^32. This uses 16-bit operations internally + * to work around bugs in some JS interpreters. + */ +function safe_add(x, y) +{ + var lsw = (x & 0xFFFF) + (y & 0xFFFF); + var msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); +} + +/* + * Bitwise rotate a 32-bit number to the left. + */ +function bit_rol(num, cnt) +{ + return (num << cnt) | (num >>> (32 - cnt)); +} |
