Skip to content

Instantly share code, notes, and snippets.

@cheeaun
Created December 11, 2009 15:44
Show Gist options
  • Save cheeaun/254280 to your computer and use it in GitHub Desktop.
Save cheeaun/254280 to your computer and use it in GitHub Desktop.
Private Chat plugin for Talkerapp, now with chat log storage.
// https://cheeaun.talkerapp.com/plugins/82
var vendorStyles = function(str){
var s = str.split('-');
if (s.length == 4 && s[0] == 'border'){
var value = s[3].split(':')[1];
return str + '-webkit-' + str + '-moz-border-radius-' + s[1] + s[2] + ':' + value + ';';
}
return str + '-moz-' + str + '-webkit-' + str;
};
var $style = $('<style>'
+ '.talkerapp-private-chat-pane{'
+ 'position: fixed; bottom: 0; right: 10px; width: 240px; height: 20em; background-color: #fff; z-index: 100;'
+ vendorStyles('box-shadow: 0 0 10px #666;')
+ vendorStyles('border-top-left-radius: 3px;')
+ vendorStyles('border-top-right-radius: 3px;')
+ '}'
+ '.talkerapp-private-chat-pane.collapsed{'
+ 'height: 24px;'
+ '}'
+ '.talkerapp-private-chat-head{'
+ 'height: 24px; line-height: 24px; font-weight: bold; padding: 0 .5em; color: #fff; background-color: #4091BF; cursor: pointer;'
+ vendorStyles('border-top-left-radius: 3px;')
+ vendorStyles('border-top-right-radius: 3px;')
+ '}'
+ '.talkerapp-private-chat-pane.new-msg .talkerapp-private-chat-head{'
+ 'background-color: #EF9E2D;'
+ '}'
+ (window.localStorage ?
('.talkerapp-private-chat-pane .talkerapp-private-chat-head .log{'
+ 'width: 16px; height: 16px; text-align: center; line-height: 16px; position: absolute; top: 4px; right: 24px;'
+ 'color: #fff; text-decoration: none;'
+ vendorStyles('border-radius: 3px;')
+ '}'
+ '.talkerapp-private-chat-pane .talkerapp-private-chat-head .log:hover{'
+ 'background-color: rgba(0,0,0,.2)'
+ '}') :
'.talkerapp-private-chat-pane .talkerapp-private-chat-head .log{ display: none; }')
+ '.talkerapp-private-chat-pane .talkerapp-private-chat-head .close{'
+ 'width: 16px; height: 16px; text-align: center; line-height: 16px; position: absolute; top: 4px; right: 4px;'
+ 'color: #fff; text-decoration: none;'
+ vendorStyles('border-radius: 3px;')
+ '}'
+ '.talkerapp-private-chat-pane .talkerapp-private-chat-head .close:hover{'
+ 'background-color: rgba(0,0,0,.2)'
+ '}'
+ '.talkerapp-private-chat-body{'
+ 'position: absolute; top: 24px; left: 0; bottom: 0; right: 0; overflow: hidden;'
+ '}'
+ '.talkerapp-private-chat-body ol{'
+ 'margin: 0; padding: 0; list-style: none; position: absolute; top: 0; left: 0; bottom: 48px; width: 100%; overflow: auto;'
+ '}'
+ '.talkerapp-private-chat-body ol li{'
+ 'margin: 0; padding: 0 .5em; list-style: none; display: block; font-size: .9em; line-height: 1.5em; overflow: hidden;'
+ '}'
+ '.talkerapp-private-chat-body ol li .talker{'
+ 'font-weight: bold; float: left; margin-right: .5em;'
+ '}'
+ '.talkerapp-private-chat-body ol li .msg{'
+ 'display: block; padding-left: 1em;'
+ '}'
+ '.talkerapp-private-chat-body ol li.divider{'
+ 'padding: 0; line-height: 0; height: 0; border-top: 1px dotted #ccc; margin: 0 .5em;'
+ '}'
+ '.talkerapp-private-chat-msgbox{'
+ 'position: absolute; left: 0; bottom: 0; right: 0; overflow: hidden; height: 48px;'
+ '}'
+ '.talkerapp-private-chat-msgbox textarea{'
+ 'position: absolute; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; resize: none;'
+ '}'
+ '.message.me.private{display: none;}'
+ '</style>');
$('head').append($style);
var chatItemHTML = '<li title="<%= talkTime %>">'
+ '<strong class="talker"><%= talkerNick %>:</strong>'
+ '<span class="msg"><%= talkerMsg %></span>'
+ '</li>';
var chatItemHTML2 = '<li>'
+ '<span class="msg"><%= talkerMsg %></span>'
+ '</li>';
var chatPaneHTML = '<div class="talkerapp-private-chat-pane" data-talker="<%= talkerID %>">'
+ '<div class="talkerapp-private-chat-head">'
+ '<%= talkerID %> '
+ '<a href="#" class="log" title="log">=</a> '
+ '<a href="#" class="close" title="close">&#215;</a>'
+ '</div>'
+ '<div class="talkerapp-private-chat-body">'
+ '<ol class="talkerapp-private-chat-list">'
+ '</ol>'
+ '<div class="talkerapp-private-chat-msgbox">'
+ '<textarea></textarea>'
+ '</div>'
+ '</div>'
+ '</div>';
var chatDividerHTML = '<li class="divider"></li>';
var chatLogScript = 'function clearall(){'
+ 'if (localStorage["talkerapp:privatechat:<%= id %>"] && confirm("Are you sure to clear ALL this chat log?")){'
+ 'localStorage.removeItem("talkerapp:privatechat:<%= id %>");'
+ 'window.close();'
+ '} '
+ '}';
var chatLogHTML = '<!DOCTYPE html>'
+ '<title>Chat with <%= id %></title>'
+ '<meta charset="utf-8">'
+ '<link rel="stylesheet" href="http://cheeaun.github.com/cacss/ca.min.css">'
+ '<style>'
+ 'h1 {font-size: 1.5em;}'
+ '.clearall {position: absolute; top: 2em; right: 2em;}'
+ 'ol {margin: 0; list-style: none;}'
+ '.date {padding: .3em .75em; background-color: #eee; text-shadow: 0 1px #fff;}'
+ '.time {float: left; width: 6em; color: #999;}'
+ '.talker {float: left; margin-right: .5em;}'
+ '.msg {display: block; margin-left: 6em; padding-left: 1em;}'
+ '</style>'
+ '<script>' + chatLogScript + '</scr' + 'ipt>'
+ '<h1>Chat with <%= id %></h1>'
+ '<p class="clearall"><a href="#" onclick="clearall(); return false;">Clear all</a></p>'
+ '<ol><%= list %></ol>';
var chatLogItemDate = '<li class="date"><%= date %></li>';
var chatLogItem = '<li>'
+ '<span class="time"><%= time %></span> '
+ '<strong class="talker"><%= talker %></strong>'
+ '<span class="msg"><%= msg %></span>'
+ '</li>';
var chats = {};
var chatLogging = false;
var talkers = [];
var me = Talker.currentUser.name;
var paneWidth = 240;
var paneSpace = 10;
var url_expression = /(https?:\/\/|www\.)[^\s<]*/gi;
var protocol_expression = /^(http|https|ftp|ftps|ssh|irc|mms|file|about|mailto|xmpp):\/\//;
var formatMsg = function(msg){
if (!msg) return;
return msg.replace(/"/g, '&quot;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(url_expression, function(l){
return '<a href="'
+ (!l.match(protocol_expression) ? 'http://' : '') + l
+ '" target="_blank">' + l + '</a>';
});
};
var insertMsg = function(id, talker, msg){
if (id == me) return; // don't talk to yourself, ok?
var inTalkers = ($.inArray(id, talkers) != -1);
var pos = talkers.length;
if (!inTalkers){
talkers.push(id);
var html = _.template(chatPaneHTML, {
talkerID: id
});
$('body').append(html);
}
var pane = $('.talkerapp-private-chat-pane[data-talker=' + id + ']');
if (!inTalkers) pane.css('right', paneSpace+(paneSpace+paneWidth)*pos);
var collapsed = pane.hasClass('collapsed');
if (collapsed) pane.addClass('new-msg');
if (!msg){
if (!collapsed) pane.find('.talkerapp-private-chat-msgbox textarea').focus();
return;
}
var lastTalker = pane.data('lastTalker');
var datetime = new Date();
var time = datetime.toLocaleTimeString();
var html = _.template((lastTalker != talker) ? chatItemHTML : chatItemHTML2, {
talkTime: time,
talkerNick: talker,
talkerMsg: formatMsg(msg)
});
pane.find('ol').append(html);
pane.data('lastTalker', talker);
// scroll to bottom
var body = pane.find('.talkerapp-private-chat-body ol')[0];
body.scrollTop = body.scrollHeight;
if (window.localStorage){
if (!chats[id]){
var c = localStorage['talkerapp:privatechat:' + id];
chats[id] = (c) ? JSON.decode(c) : [];
}
chats[id].push({
datetime: +datetime,
talker: talker,
msg: msg
});
if (!chatLogging) setTimeout(function(){
var json = JSON.encode(chats[id]);
localStorage['talkerapp:privatechat:' + id] = json;
chatLogging = false;
}, 5000);
}
};
// Received private messages
plugin.onMessageReceived = function(e){
if (!e || !e.private || e.type != 'message') return;
var talker = e.user.name;
var msg = e.content;
insertMsg(talker, talker, msg);
return false;
};
// Send private messages
plugin.onMessageSent = function(e){
if (!e || e.type != 'command' || e.command != 'msg' || !e.args || e.args.length <= 1) return;
var args = e.args.slice();
var talker = args.shift().replace(/^@/, '');
var msg = args.join(' ');
insertMsg(talker, me, msg);
return false;
}
var offline = false;
var disableTextareas = function(){
var users = _.map(Talker.getRoomUsers(), function(user){
return user.name;
});
var offUsers = [];
for (var i=0, l=talkers.length; i<l; i++){
if ($.inArray(talkers[i], users) == -1) offUsers.push(talkers[i]);
}
var panes = $('.talkerapp-private-chat-pane');
panes.css('opacity', 1).find('textarea').removeAttr('disabled');
for (var j=0, l=offUsers.length; j<l; j++){
panes.filter('[data-talker=' + offUsers[j] + ']').css('opacity', .5).find('textarea').attr('disabled', true);
}
};
plugin.onOpen = function(){
offline = false;
disableTextareas();
};
plugin.onClose = function(){
offline = true;
}
plugin.onJoin = plugin.onLeave = disableTextareas;
$('.talkerapp-private-chat-head').live('click', function(){
var pane = $(this).parent('.talkerapp-private-chat-pane');
if (pane.hasClass('collapsed')){
pane.removeClass('collapsed');
pane.removeClass('new-msg');
pane.find('.talkerapp-private-chat-msgbox textarea').focus();
} else {
pane.addClass('collapsed');
var ol = pane.find('ol');
var divider = ol.find('li.divider');
ol.append(divider.length ? divider : chatDividerHTML);
}
});
$('.talkerapp-private-chat-msgbox textarea').live('keydown', function(e){
e.stopPropagation();
var textarea = $(this);
if (e.keyCode == 13){ // enter
e.preventDefault();
var val = $.trim(textarea.val());
if (val == '') return;
if (val == '/clear'){
var pane = textarea.parents('.talkerapp-private-chat-pane');
pane.find('.talkerapp-private-chat-list').html('');
textarea.val('').focus();
pane.data('lastTalker', '');
} else {
var talker = textarea.parents('.talkerapp-private-chat-pane').attr('data-talker');
Talker.trigger('MessageSend', {
type: 'message',
content: '/msg ' + talker + ' ' + val
});
textarea.val('').focus();
}
}
});
$('#people li').live('dblclick', function(){
var el = $(this);
var talker = $.trim(el.text());
insertMsg(talker, me, null);
});
$('.talkerapp-private-chat-head .close').live('click', function(){
if (window.localStorage || confirm('Are you sure? All chat messages are NOT saved.')){
var el = $(this);
var pane = el.parents('.talkerapp-private-chat-pane');
var talker = pane.attr('data-talker');
for (var i = talkers.length; i--; i){
if (talkers[i] === talker) talkers.splice(i, 1);
}
pane.nextAll('.talkerapp-private-chat-pane').each(function(){
$(this).animate({
right: '-=' + (paneWidth+paneSpace)
});
});
pane.remove();
}
return false;
});
var formatTime = function(datetime){
var hour = datetime.getHours();
var ap = (hour >= 12) ? 'PM' : 'AM';
if (hour == 0){
hour = 12;
} else if (hour > 12){
hour -= 12
}
var min = '' + datetime.getMinutes();
if (min.length == 1) min = '0' + min;
return hour + ':' + min + ' ' + ap;
};
var chatwindows = {};
$('.talkerapp-private-chat-head .log').live('click', function(){
var el = $(this);
var pane = el.parents('.talkerapp-private-chat-pane');
var talker = pane.attr('data-talker');
var hasWinFocus = !!window.focus;
if (chatwindows[talker]){
if (hasWinFocus) chatwindows[talker].focus();
} else {
var win = chatwindows[talker] = window.open('javascript:""', 'talkerapp:privatechat:' + talker, 'width=400,height=400,location=0,menubar=0,toolbar=0,resizable=1,scrollbars=1');
if (hasWinFocus) win.focus();
var olHTML = '<li>Empty.</li>';
var data = localStorage['talkerapp:privatechat:' + talker];
if (data){
var c = JSON.decode(data);
if (c.length){
olHTML = '';
var prevDate = '';
var prevTime = '';
var prevTalker = '';
$.each(c, function(i, chat){
var datetime = new Date(chat.datetime);
var date = datetime.toLocaleDateString();
var nowDate = new Date().toLocaleDateString();
if (date == nowDate) date = 'Today';
if (date != prevDate){
olHTML += _.template(chatLogItemDate, {
date: date
});
prevDate = date;
prevTime = '';
prevTalker = '';
}
var time = formatTime(datetime);
(time == prevTime) ? time = '&nbsp;' : prevTime = time;
var talker = chat.talker;
if (talker == prevTalker){
talker = '';
} else {
prevTalker = talker;
talker += ':';
}
olHTML += _.template(chatLogItem, {
time: time,
talker: talker,
msg: formatMsg(chat.msg)
});
});
}
}
var html = _.template(chatLogHTML, {
id: talker,
list: olHTML
});
var doc = win.document;
doc.open();
doc.write(html);
doc.close();
win.onunload = function(){
delete chatwindows[talker];
}
}
return false;
});
$('.talkerapp-private-chat-pane textarea').live('mousedown keydown click', function(){
if (offline) return false;
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment