User:Jonas Lund/tri1.2: Difference between revisions
Jonas Lund (talk | contribs) |
Jonas Lund (talk | contribs) |
||
(One intermediate revision by the same user not shown) | |||
Line 1: | Line 1: | ||
=Let's Talk About Pictures (LTAP)= | =Let's Talk About Pictures (LTAP)= | ||
LTAP is a collaboration with Louis Doulas and Harry Burke, to create a website that facilitates and encourages a one to one discussion about pictures. In LTAP, each user is initially presented with an upload form which accepts images. The user is required to upload a picture of their choosing and pick a nickname to enter the discussion, the chat room. The chat room displays a random picture from the database of images and the conversation can begin. | |||
=LTAP Development= | =LTAP Development= | ||
Line 48: | Line 48: | ||
server.js | server.js | ||
<source lang="javascript"> | <source lang="javascript"> | ||
//db | |||
var mysql = require('mysql'); | |||
var db = "ltappiting"; | |||
var chats = 'chats'; | |||
var chatLogs = 'chat_logs'; | |||
var client = mysql.createClient({ | |||
host: "localhost", | |||
user: "root", | |||
password: "root" | |||
}); | |||
//server | |||
var http = require("http"); | |||
var url = require("url"); | |||
var fs = require("fs"); | |||
var util = require('util'); | |||
var formidable = require('formidable'); | |||
var exec = require('child_process').exec; | |||
var fu = exports; | |||
function onRequest(req, res) { | |||
var pathname = url.parse(req.url).pathname; | |||
console.log("Request for " + pathname + " received."); | |||
//routing | |||
switch (pathname) { | |||
case '/': | |||
req.setEncoding("utf8"); | |||
var html = fs.readFileSync(__dirname+'/index.html'); | |||
res.end(html); | |||
break; | |||
case '/upload': | |||
uploadFile(req, res); | |||
break; | |||
default: | |||
req.setEncoding("utf8"); | |||
var content_type = fu.mime.lookupExtension(extname(pathname)); | |||
fs.readFile(__dirname+"/"+pathname, function(err, data) { | |||
if(err) { | |||
res.writeHead(404, {"Content-Type": "text/plain"}); | |||
res.write("404 Not found"); | |||
res.end(); | |||
} else { | |||
headers = { "Content-Type": content_type | |||
, "Content-Length": data.length | |||
}; | |||
res.writeHead(200, headers); | |||
res.write(data); | |||
res.end(); | |||
} | |||
}); | |||
break; | |||
} | |||
} | |||
var server = http.createServer(onRequest).listen(1337); | |||
console.log("server running on 1337"); | |||
var nowjs = require("now"); | |||
var everyone = nowjs.initialize(server); | |||
//Session Variables | |||
var sessions = {}; | |||
var rooms = []; | |||
rooms[0] = 0; | |||
// I'm working on a 1-1 chat client with nodejs and nowjs, and I'm wondering about the best practice for queuing/assigning rooms (groups) to the users, given that it should only be a 1 to 1 conversation, and the one who's been waiting the longest should be assigned first'. Any ideas? :) | |||
//Send message to everyone on the users group | |||
everyone.now.distributeMessage = function(message){ | |||
console.log('Received '+message+' from '+this.now.nick +' in serverroom '+this.now.room); | |||
var group = nowjs.getGroup(this.now.room); | |||
group.now.receiveMessage(this.now.id, this.now.nick, message); | |||
var date = new Date(); | |||
//insert into DB | |||
client.query('USE '+db); | |||
client.query( | |||
'INSERT INTO '+chatLogs+' '+ | |||
'SET user = ?, text = ?, date = ?', | |||
[this.user.clientId, message, date] | |||
); | |||
}; | |||
//function to be called from client | |||
everyone.now.join = function() { | |||
var userID = this.user.clientId; | |||
var nick = this.now.nick; | |||
var image = this.now.image; | |||
var id = this.now.id; | |||
//get available room | |||
var room = getRoom(); | |||
this.now.room = room; | |||
var user = { | var user = { | ||
id: userID, | id: userID, | ||
nick: nick, | nick: nick, | ||
room: room, | room: room, | ||
image: image | image: image | ||
//timestamp: new Date() | //timestamp: new Date() | ||
}; | }; | ||
//add user to room | //add user to room | ||
nowjs.getGroup(room).addUser(userID); | nowjs.getGroup(room).addUser(userID); | ||
//add user to session | //add user to session | ||
sessions[user.id] = user; | sessions[user.id] = user; | ||
var group = nowjs.getGroup(this.now.room); | var group = nowjs.getGroup(this.now.room); | ||
if(rooms[room] == 2) { | if(rooms[room] == 2) { | ||
var status = " | var status = "Buddy Joined, say Hi!"; | ||
//get-users, add to db | //get-users, add to db | ||
nowjs.getGroup(room).getUsers(function (users) { | nowjs.getGroup(room).getUsers(function (users) { | ||
Line 177: | Line 174: | ||
var date = new Date(); | var date = new Date(); | ||
// | //var randNumber = Math.floor(Math.random()*11) + 11; | ||
client.query('USE '+db); | client.query('USE '+db); | ||
client.query( | client.query( | ||
'INSERT INTO '+chats+' '+ | //'SELECT image1, id FROM '+chats + ' WHERE id=' + randNumber, | ||
'SELECT image1, id FROM '+chats + ' ORDER BY rand() LIMIT 1', | |||
function selectCb(err, results, fields) { | |||
); | if (err) { | ||
throw err; | |||
} | |||
var chatImage = results[0].image1; | |||
client.end(); | |||
console.log(chatImage); | |||
group.now.updateImage(chatImage); | |||
//insert into DB | |||
client.query('USE '+db); | |||
client.query( | |||
'INSERT INTO '+chats+' '+ | |||
'SET user1 = ?, user2 = ?, nick1 = ?, nick2 = ?, image1 = ?, image2 = ?, chatimage = ?, date = ?', | |||
[user1, user2, nick1, nick2, image1, image2, chatImage, date] | |||
); | |||
}); | |||
}); | }); | ||
} else { | } else { | ||
var status = "Finding chat buddy.."; | var status = "Finding chat buddy.."; | ||
} | } | ||
group.now.chatStatus(status); | group.now.chatStatus(status); | ||
} | |||
everyone.now.isTyping = function(type){ | |||
console.log('Received '+type+' from '+this.now.id); | |||
var group = nowjs.getGroup(this.now.room); | |||
group.now.updateType(type, this.now.nick, this.now.id); | |||
}; | |||
nowjs.on('disconnect', function () { | |||
var userroom = this.now.room; | |||
var nick = this.now.nick; | |||
var id = this.now.id; | |||
var userID = this.user.clientId; | |||
if(rooms[userroom] == 2) { | |||
rooms[userroom] = 1; | |||
var status = "Buddy Left.. – Looking for a new one..."; | |||
} else { | |||
rooms[userroom] = 0; | |||
} | } | ||
if (userID && sessions[id]) { | |||
delete sessions[userID]; | |||
} | } | ||
var group = nowjs.getGroup(this.now.room); | |||
group.now.chatStatus(status); | |||
console.log("user:", this.now.id, this.now.nick, " left"); | |||
}); | |||
function getRoom() { | |||
for (var i=0; i < rooms.length; i++) { | |||
if(rooms[i] === 1) { | |||
rooms[i] = 2; | |||
return i; | |||
} else if (rooms[i] === 0) { | |||
rooms[i] = 1; | |||
return i; | |||
} | |||
}; | |||
rooms.push(1); | |||
return rooms.length-1; | |||
} | |||
//upload | |||
function uploadFile(req, res) { | |||
var form = new formidable.IncomingForm(), | |||
files = [], | |||
fields = []; | |||
fileob = {}; | |||
form.uploadDir = __dirname + '/tmp'; | |||
form.encoding = 'binary'; | |||
form | |||
.on('field', function(field, value) { | |||
console.log(field, value); | |||
fields.push([field, value]); | |||
}) | |||
.on('file', function(field, file) { | |||
files.push([field, file]); | |||
}) | //check size type, cancel if wrong | ||
var newfile = { | |||
file: file.size, | |||
name: file.name, | |||
path: file.path, | |||
type: file.type | |||
}; | |||
fileob = newfile; | |||
}) | |||
.on('end', function() { | |||
console.log('-> upload done'); | |||
res.writeHead(200, {'content-type': 'text/plain'}); | |||
//move | |||
var ext = extname(fileob.name); | |||
var uni = Math.floor(Math.random()*99999).toString(); | |||
var cleanFilename = fileob.name.replace(/[^a-z0-9]/gi, '_').toLowerCase(); | |||
var filename = uni + cleanFilename + ext; | |||
res.write(filename); | |||
res.end(); | |||
var cmd = "mv " + fileob.path + " /home/jonasx/t2sp.net/upload/" + filename; | |||
console.log(cmd); | |||
exec(cmd, puts); | |||
}); | |||
form.parse(req, function(err, fields, files) { | |||
if (err) { | |||
console.log(err); | |||
} | |||
}); | |||
} | |||
function puts(error, stdout, stderr) { util.puts(stdout) } | |||
function include(arr,obj) { | |||
return (arr.indexOf(obj) != -1); | |||
} | |||
Array.prototype.remove = function(from, to) { | |||
var rest = this.slice((to || from) + 1 || this.length); | |||
} | this.length = from < 0 ? this.length + from : from; | ||
return this.push.apply(this, rest); | |||
}; | |||
function extname (path) { | |||
var index = path.lastIndexOf("."); | |||
return index < 0 ? "" : path.substring(index); | |||
} | |||
// | // stolen from node_chat, jack- thanks | ||
function | fu.mime = { | ||
// returns MIME type for extension, or fallback, or octet-steam | |||
lookupExtension : function(ext, fallback) { | |||
} | return fu.mime.TYPES[ext.toLowerCase()] || fallback || 'application/octet-stream'; | ||
}, | |||
//stolen from | // List of most common mime-types, stolen from Rack. | ||
TYPES : { ".3gp" : "video/3gpp" | |||
// | , ".a" : "application/octet-stream" | ||
, ".ai" : "application/postscript" | |||
, ".aif" : "audio/x-aiff" | |||
} | , ".aiff" : "audio/x-aiff" | ||
, ".asc" : "application/pgp-signature" | |||
, ".asf" : "video/x-ms-asf" | |||
, ".asm" : "text/x-asm" | |||
, ".asx" : "video/x-ms-asf" | |||
, ".atom" : "application/atom+xml" | |||
, ".au" : "audio/basic" | |||
, ".avi" : "video/x-msvideo" | |||
, ".bat" : "application/x-msdownload" | |||
, ".bin" : "application/octet-stream" | |||
, ".bmp" : "image/bmp" | |||
, ".bz2" : "application/x-bzip2" | |||
, ".c" : "text/x-c" | |||
, ".cab" : "application/vnd.ms-cab-compressed" | |||
, ".cc" : "text/x-c" | |||
, ".chm" : "application/vnd.ms-htmlhelp" | |||
, ".class" : "application/octet-stream" | |||
, ".com" : "application/x-msdownload" | |||
, ".conf" : "text/plain" | |||
, ".cpp" : "text/x-c" | |||
, ".crt" : "application/x-x509-ca-cert" | |||
, ".css" : "text/css" | |||
, ".csv" : "text/csv" | |||
, ".cxx" : "text/x-c" | |||
, ".deb" : "application/x-debian-package" | |||
, ".der" : "application/x-x509-ca-cert" | |||
, ".diff" : "text/x-diff" | |||
, ".djv" : "image/vnd.djvu" | |||
, ".djvu" : "image/vnd.djvu" | |||
, ".dll" : "application/x-msdownload" | |||
, ".dmg" : "application/octet-stream" | |||
, ".doc" : "application/msword" | |||
, ".dot" : "application/msword" | |||
, ".dtd" : "application/xml-dtd" | |||
, ".dvi" : "application/x-dvi" | |||
, ".ear" : "application/java-archive" | |||
, ".eml" : "message/rfc822" | |||
, ".eps" : "application/postscript" | |||
, ".exe" : "application/x-msdownload" | |||
, ".f" : "text/x-fortran" | |||
, ".f77" : "text/x-fortran" | |||
, ".f90" : "text/x-fortran" | |||
, ".flv" : "video/x-flv" | |||
, ".for" : "text/x-fortran" | |||
, ".gem" : "application/octet-stream" | |||
, ".gemspec" : "text/x-script.ruby" | |||
, ".gif" : "image/gif" | |||
, ".gz" : "application/x-gzip" | |||
, ".h" : "text/x-c" | |||
, ".hh" : "text/x-c" | |||
, ".htm" : "text/html" | |||
, ".html" : "text/html" | |||
, ".ico" : "image/vnd.microsoft.icon" | |||
, ".ics" : "text/calendar" | |||
, ".ifb" : "text/calendar" | |||
, ".iso" : "application/octet-stream" | |||
, ".jar" : "application/java-archive" | |||
, ".java" : "text/x-java-source" | |||
, ".jnlp" : "application/x-java-jnlp-file" | |||
, ".jpeg" : "image/jpeg" | |||
, ".jpg" : "image/jpeg" | |||
, ".js" : "application/javascript" | |||
, ".json" : "application/json" | |||
, ".log" : "text/plain" | |||
, ".m3u" : "audio/x-mpegurl" | |||
, ".m4v" : "video/mp4" | |||
, ".man" : "text/troff" | |||
, ".mathml" : "application/mathml+xml" | |||
, ".mbox" : "application/mbox" | |||
, ".mdoc" : "text/troff" | |||
, ".me" : "text/troff" | |||
, ".mid" : "audio/midi" | |||
, ".midi" : "audio/midi" | |||
, ".mime" : "message/rfc822" | |||
, ".mml" : "application/mathml+xml" | |||
, ".mng" : "video/x-mng" | |||
, ".mov" : "video/quicktime" | |||
, ".mp3" : "audio/mpeg" | |||
, ".mp4" : "video/mp4" | |||
, ".mp4v" : "video/mp4" | |||
, ".mpeg" : "video/mpeg" | |||
, ".mpg" : "video/mpeg" | |||
, ".ms" : "text/troff" | |||
, ".msi" : "application/x-msdownload" | |||
, ".odp" : "application/vnd.oasis.opendocument.presentation" | |||
, ".ods" : "application/vnd.oasis.opendocument.spreadsheet" | |||
, ".odt" : "application/vnd.oasis.opendocument.text" | |||
, ".ogg" : "application/ogg" | |||
, ".p" : "text/x-pascal" | |||
, ".pas" : "text/x-pascal" | |||
, ".pbm" : "image/x-portable-bitmap" | |||
, ".pdf" : "application/pdf" | |||
, ".pem" : "application/x-x509-ca-cert" | |||
, ".pgm" : "image/x-portable-graymap" | |||
, ".pgp" : "application/pgp-encrypted" | |||
, ".pkg" : "application/octet-stream" | |||
, ".pl" : "text/x-script.perl" | |||
, ".pm" : "text/x-script.perl-module" | |||
, ".png" : "image/png" | |||
, ".pnm" : "image/x-portable-anymap" | |||
, ".ppm" : "image/x-portable-pixmap" | |||
, ".pps" : "application/vnd.ms-powerpoint" | |||
, ".ppt" : "application/vnd.ms-powerpoint" | |||
, ".ps" : "application/postscript" | |||
, ".psd" : "image/vnd.adobe.photoshop" | |||
, ".py" : "text/x-script.python" | |||
, ".qt" : "video/quicktime" | |||
, ".ra" : "audio/x-pn-realaudio" | |||
, ".rake" : "text/x-script.ruby" | |||
, ".ram" : "audio/x-pn-realaudio" | |||
, ".rar" : "application/x-rar-compressed" | |||
, ".rb" : "text/x-script.ruby" | |||
, ".rdf" : "application/rdf+xml" | |||
, ".roff" : "text/troff" | |||
, ".rpm" : "application/x-redhat-package-manager" | |||
, ".rss" : "application/rss+xml" | |||
, ".rtf" : "application/rtf" | |||
, ".ru" : "text/x-script.ruby" | |||
, ".s" : "text/x-asm" | |||
, ".sgm" : "text/sgml" | |||
, ".sgml" : "text/sgml" | |||
, ".sh" : "application/x-sh" | |||
, ".sig" : "application/pgp-signature" | |||
, ".snd" : "audio/basic" | |||
, ".so" : "application/octet-stream" | |||
, ".svg" : "image/svg+xml" | |||
, ".svgz" : "image/svg+xml" | |||
, ".swf" : "application/x-shockwave-flash" | |||
, ".t" : "text/troff" | |||
, ".tar" : "application/x-tar" | |||
, ".tbz" : "application/x-bzip-compressed-tar" | |||
, ".tcl" : "application/x-tcl" | |||
, ".tex" : "application/x-tex" | |||
, ".texi" : "application/x-texinfo" | |||
, ".texinfo" : "application/x-texinfo" | |||
, ".text" : "text/plain" | |||
, ".tif" : "image/tiff" | |||
, ".tiff" : "image/tiff" | |||
, ".torrent" : "application/x-bittorrent" | |||
, ".tr" : "text/troff" | |||
, ".txt" : "text/plain" | |||
, ".vcf" : "text/x-vcard" | |||
, ".vcs" : "text/x-vcalendar" | |||
, ".vrml" : "model/vrml" | |||
, ".war" : "application/java-archive" | |||
, ".wav" : "audio/x-wav" | |||
, ".wma" : "audio/x-ms-wma" | |||
, ".wmv" : "video/x-ms-wmv" | |||
, ".wmx" : "video/x-ms-wmx" | |||
, ".wrl" : "model/vrml" | |||
, ".wsdl" : "application/wsdl+xml" | |||
, ".xbm" : "image/x-xbitmap" | |||
, ".xhtml" : "application/xhtml+xml" | |||
, ".xls" : "application/vnd.ms-excel" | |||
, ".xml" : "application/xml" | |||
, ".xpm" : "image/x-xpixmap" | |||
, ".xsl" : "application/xml" | |||
, ".xslt" : "application/xslt+xml" | |||
, ".yaml" : "text/yaml" | |||
, ".yml" : "text/yaml" | |||
, ".zip" : "application/zip" | |||
} | |||
}; | |||
</source> | </source> | ||
index.html | index.html | ||
<source lang="html4strict"> | <source lang="html4strict"> | ||
<!doctype html> | |||
<html lang="en"> | |||
<head> | |||
<meta charset="utf-8"> | |||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> | |||
<title>LTAP</title> | |||
<meta name="description" content=""> | |||
<meta name="author" content=""> | |||
<meta name="viewport" content="width=device-width,initial-scale=1"> | |||
<link rel="stylesheet" href="style.css"> | |||
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script> | |||
<script src="/nowjs/now.js"></script> | |||
< | <script type="text/javascript"> | ||
$(document).ready(function(){ | |||
util = { | |||
urlRE: /https?:\/\/([-\w\.]+)+(:\d+)?(\/([^\s]*(\?\S+)?)?)?/g, | |||
toStaticHTML: function(inputHtml) { | |||
inputHtml = inputHtml.toString(); | |||
return inputHtml.replace(/&/g, "&") | |||
.replace(/</g, "<") | |||
.replace(/>/g, ">"); | |||
}, | |||
zeroPad: function (digits, n) { | |||
n = n.toString(); | |||
while (n.length < digits) | |||
n = '0' + n; | |||
return n; | |||
}, | |||
timeString: function (date) { | |||
var minutes = date.getMinutes().toString(); | |||
var hours = date.getHours().toString(); | |||
return this.zeroPad(2, hours) + ":" + this.zeroPad(2, minutes); | |||
}, | |||
isBlank: function(text) { | |||
var blank = /^\s*$/; | |||
return (text.match(blank) !== null); | |||
} | |||
}; | |||
$(document).ready(function(){ | |||
var CONFIG = { focus: true | |||
,unread: 0 | |||
}; | |||
now.receiveMessage = function(id, nick, messageRaw){ | |||
if(id === now.id) { | if(id === now.id) { | ||
var nick = "you"; | var nick = "you"; | ||
} | } | ||
if(!CONFIG.focus){ | |||
CONFIG.unread++; | |||
updateTitle(); | |||
} | |||
var message = util.toStaticHTML(messageRaw); | |||
var time = new Date(); | |||
var relTime = util.timeString(time); | |||
var lastMessage = $("#message div:last"); | |||
var lastMessageTime = $(lastMessage).find(".time").text(); | |||
if($(lastMessage).attr("data-id") === id) { | |||
if(relTime === lastMessageTime) { | |||
var content = '<span class="clear">' | |||
// + ' <span class="time">' + relTime + '</span>' | |||
+ ' <span class="msg-text">' + message + '</span>' | |||
+ '</span>'; | |||
} else { | |||
var content = '<span class="clear">' | |||
+ ' <span class="time">' + relTime + '</span>' | |||
+ ' <span class="msg-text">' + message + '</span>' | |||
+ '</span>'; | |||
} | |||
$(lastMessage).append(content); | |||
$("#message").scrollTop(55000); | |||
} else { | |||
var content = '<div data-id="'+id+'">' | |||
+ ' <span class="time">' + util.timeString(time) + '</span>' | |||
+ ' <span class="nick '+nick+'">' + nick + '</span>' | |||
+ ' <span class="msg-text">' + message + '</span' | |||
+ '</div>' | |||
; | |||
$("#message").append(content).scrollTop(55000); | |||
} | |||
if($(".isTyping").is("visible")) { | |||
$(".isTyping").appendTo($("#messages")); | |||
} | |||
} | |||
$(".send").click(function() { | |||
var input = ""; | |||
now.messageRoom(currRoom, input.text()); | |||
}); | |||
now.chatStatus = function(status) { | |||
var time = new Date(); | |||
var content = '<div>' | |||
+ ' <span class="time">' + util.timeString(time) + '</span>' | |||
+ ' <span class="status">' + status + '</span>' | |||
+ '</div>' | |||
; | |||
if(status === "Buddy Joined, say Hi!") { | |||
$("#text-input").prop("disabled", false).focus(); | |||
} | |||
if(status === "Buddy Left.. – Looking for a new one...") { | |||
$("#text-input").prop("disabled", true); | |||
} | |||
$("#message").append(content).scrollTop(55000); | |||
} | |||
now.updateImage = function(image) { | |||
$("#image").attr("src", image).show(); | |||
} | |||
if( | |||
now.updateType = function(val, nick, id) { | |||
console.log(val, nick, id, now.id); | |||
if(id != now.id) { | |||
if(val === true) { | |||
$(".isTyping").text(nick + " is typing…").appendTo($("#message")).show(); | |||
$("#message").scrollTop(55000); | |||
} else { | } else { | ||
var | $(".isTyping").hide(); | ||
} | } | ||
} | |||
} | |||
$("#chatBox").bind("keypress", function(e) { | |||
var code = (e.keyCode ? e.keyCode : e.which); | |||
if(code == 13) { //Enter keycode | |||
var message = $("#text-input").val(); | |||
if(message) { | |||
now.distributeMessage($("#text-input").val()); | |||
$("#text-input").val(""); | |||
} | |||
now.isTyping(false); | |||
return false; | |||
} | |||
now.isTyping(true); | |||
}); | |||
$("#text-input").bind("blur", function() { | |||
console.log("blurred"); | |||
now.isTyping(false); | |||
}); | |||
$("#loginform").submit(function(){ | |||
var nick = $("#nick").val(); | |||
if(!nick) { | |||
var nick = "Stranger"; | |||
} | } | ||
var image = $("#imageurl").val(); | |||
if(!image) { | |||
$(".error").html("Image Can't Be Empty").show(); | |||
} else { | |||
//ugly regex hack for validating image | |||
var match = image.match(/http:\/\/.+?\.jpg|jpeg|png|gif/gi); | |||
if(!match) { | |||
$(".error").html("Not a valid Image Url").show(); | |||
} else { | |||
now.id = Math.floor(Math.random()*99999999999).toString(); | |||
now.room = 1; | |||
now.nick = nick; | |||
now.image = image; | |||
now.join(nick, image); | |||
$("#chat").show(); | |||
$("#login").hide(); | |||
$("#text-input").focus(); | |||
} | |||
} | |||
return false; | |||
}); | |||
$("#file").submit(function() { | |||
$("#transFrame").load(function() { | |||
response = $(this).contents().find('body').text(); | |||
if(response) { | |||
$("#file").hide(); | |||
var filename = "http://t2sp.net/upload/" + response; | |||
$("#imageurl").val(filename); | |||
$(".toggle-handle").hide(); | |||
$("#imageurl").prop("disabled", true); | |||
} | |||
//return false; | |||
}); | |||
}); | |||
$(".toggle-handle").click(function() { | |||
$(this).next(".toggle-box").show(); | |||
return false; | |||
}); | |||
// on establishing 'now' connection, set server room and allow message sending | |||
now.ready(function(){ | |||
}); | |||
//listen for browser events so we know to update the document title | |||
$(window).bind("blur", function() { | |||
CONFIG.focus = false; | |||
updateTitle(); | |||
}); | |||
$(window).bind("focus", function() { | |||
CONFIG.focus = true; | |||
CONFIG.unread = 0; | |||
updateTitle(); | |||
}); | |||
function updateTitle(){ | |||
if (CONFIG.unread) { | |||
document.title = "(" + CONFIG.unread.toString() + ") LTAP"; | |||
} else { | |||
document.title = "LTAP"; | |||
} | |||
} | |||
}); | |||
</script> | |||
</head> | |||
<body> | |||
<div id="login"> | |||
<hgroup> | |||
<h1>LTAP</h1> | |||
</hgroup> | |||
<form id="loginform" action="" method="get"> | |||
<h3 class="error hidden"></h3> | |||
<input type="text" id="nick" placeholder="nickname (optional)"/> | |||
<input type="text" id="imageurl" placeholder="Image (URL)"/> | |||
<input type="submit" id="submit" value="go" class="button"/> | |||
</form> | |||
<h3 class='toggle-handle'>Or Upload A File...</h3> | |||
<div class='toggle-box hidden'> | |||
<form target="transFrame" id="file" action="/upload" method="post" enctype="multipart/form-data"> | |||
<input type="file" name="file-upload" id="file-upload" multiple="multiple"> | |||
<input type="submit" value="Upload" class="button"> | |||
</form> | |||
</div> | |||
</div> | |||
<iframe style="" class="hidden" name="transFrame" id="transFrame"></iframe> | |||
<div id="chat" class="hidden"> | |||
<div class="image"> | |||
<img id="image" class="" src="http://www.pandl.org/img/1x1" alt="placeholder img"/> | |||
</div> | |||
<div class="right"> | |||
<div id="message"> | |||
<span class="isTyping hidden"></span> | |||
</div> | |||
<form id="chatBox" action="" method="get"> | |||
<textarea type="text" id="text-input" name="chat" value="" disabled></textarea> | |||
</form> | |||
</div> | |||
</div> | |||
<div class="disclaimer"> | |||
<p>* all conversations are being stored in a database</p> | |||
</div> | |||
</body> | |||
</html> | |||
</source> | |||
style.css | |||
<source lang="css"> | |||
article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { display: block; } | |||
audio, canvas, video { display: inline-block; *display: inline; *zoom: 1; } | |||
audio:not([controls]) { display: none; } | |||
[hidden] { display: none; } | |||
html { font-size: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } | |||
html, button, input, select, textarea { font-family: sans-serif; color: #222; } | |||
body { margin: 0; font-size: 1em; line-height: 1.4;} | |||
::-moz-selection { background: #333; color: #fff; text-shadow: none; } | |||
::selection { background: #333; color: #fff; text-shadow: none; } | |||
/* ============================================================================= | |||
Links | |||
========================================================================== */ | |||
a { color: #00e; } | |||
a:visited { color: #551a8b; } | |||
a:hover { color: #06e; } | |||
a:focus { outline: thin dotted; } | |||
a:hover, a:active { outline: 0; } | |||
ul, li, ol { margin: 0; padding: 0; list-style: none;} | |||
img { border: 0; -ms-interpolation-mode: bicubic; vertical-align: middle; } | |||
form { margin: 0; } | |||
fieldset { border: 0; margin: 0; padding: 0; } | |||
label { cursor: pointer; } | |||
legend { border: 0; *margin-left: -7px; padding: 0; white-space: normal; } | |||
button, input, select, textarea { font-size: 100%; margin: 0; vertical-align: baseline; *vertical-align: middle; } | |||
button, input { line-height: normal; } | |||
button, input[type="button"], input[type="reset"], input[type="submit"] { cursor: pointer; -webkit-appearance: button; *overflow: visible; } | |||
button[disabled], input[disabled] { cursor: default; } | |||
input[type="checkbox"], input[type="radio"] { box-sizing: border-box; padding: 0; *width: 13px; *height: 13px; } | |||
input[type="search"] { -webkit-appearance: textfield; -moz-box-sizing: content-box; -webkit-box-sizing: content-box; box-sizing: content-box; } | |||
input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; } | |||
button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } | |||
textarea { overflow: auto; vertical-align: top; resize: vertical; } | |||
html, body {width: 100%; height: 100%; font: 16px/1.3 sans-serif; overflow: hidden; margin: 0; padding: 0;} | |||
#login input {margin-bottom: 10px; width: 320px; border: 0; border-bottom: 1px solid; font-size: 24px; color: #000;} | |||
#login input[type='submit'] {width: 160px; margin-left: 80px; margin-bottom: 20px;} | |||
#login {width: 320px; margin: 100px auto;} | |||
.error {color: #f00;} | |||
.image {width: 50%; float: left; text-align: center; padding-top: 10px; } | |||
.image img {max-width: 80%; max-height: 80%;} | |||
.right {bottom: 60px;left: 50%;position: absolute;right: 20px;top: 10px;}#chat {height: 100%; width: 100%; margin: 0; padding: 0;} | |||
#message {height: 85%; width: 100%; border: 1px solid #ccc; padding: 10px 0 10px 10px; overflow-y: scroll; margin-bottom: 10px;} | |||
#message div {padding-bottom: 5px;} | |||
#chatBox {height: 15%;} | |||
#text-input {width: 100%;height: 100%; margin: 0; padding: 10px 0 10px 10px; border: 1px solid #ccc; font-size: 16px;} | |||
h3 {color: #333;} | |||
.nick {color: red;} | |||
.you {color: blue;} | |||
.disclaimer {font-size: 12px; color: #666; position: fixed; bottom: 5px; left: 5px;} | |||
.isTyping {font-size: 14px; font-weight: bold; padding-top: 10.4px;} | |||
input:valid, textarea:valid { } | |||
input:invalid, textarea:invalid { background-color: #f0dddd; } | |||
.hidden { display: none;} | |||
.invisible { visibility: hidden; } | |||
.cf:before, .cf:after, #message div:before, #message div:after { content: ""; display: table; } | |||
.cf:after, #message div:after { clear: both; } | |||
.cf, #message div { *zoom: 1; } | |||
.button { | |||
-moz-border-radius: 3px; | |||
-webkit-border-radius: 3px; | |||
background: white url('button.png') 0 0 repeat-x; | |||
background: -moz-linear-gradient(0% 170% 90deg, #c4c4c4, white); | |||
background: -webkit-gradient(linear, 0% 0%, 0% 170%, from(white), to(#c4c4c4)); | |||
border: 1px solid; | |||
border-color: #e6e6e6 #cccccc #cccccc #e6e6e6; | |||
border-radius: 3px; | |||
color: #404040; | |||
display: inline-block; | |||
font-family: "helvetica neue", helvetica, arial, freesans, "liberation sans", "numbus sans l", sans-serif; | |||
font-size: 13px; | |||
outline 0; | |||
padding: 5px 8px 5px; | |||
text-align: center; | |||
text-decoration: none; | |||
text-shadow: 1px 1px 0 white; | |||
white-space: nowrap; } | |||
.button:hover { | |||
background: -moz-linear-gradient(0% 170% 90deg, #b8b8b8, white); | |||
background: -webkit-gradient(linear, 0% 0%, 0% 170%, from(white), to(#b8b8b8)); | |||
} | border-color: #99ccff; | ||
color: #333333; } | |||
.button:active { | |||
position: relative; | |||
top: 1px; } | |||
.button:active, .button:focus { | |||
background-position: 0 -25px; | |||
background: -moz-linear-gradient(0% 170% 90deg, white, #dedede); | |||
background: -webkit-gradient(linear, 0% 0%, 0% 170%, from(#dedede), to(white)); | |||
border-color: #8fc7ff #94c9ff #94c9ff #8fc7ff; | |||
color: #1a1a1a; | |||
text-shadow: 1px -1px 0 rgba(255, 255, 255, 0.5); } | |||
.time {color: #666; font-size: 14px; float: right; margin-right: 10px;} | |||
#message div {border-bottom: 1px dotted rgb(230,230,230); padding: 6px 0px 5px 0; clear: both;} | |||
.nick {float: left; margin-right: 5px;} | |||
.msg-text {float: left; width: 90%; clear: both;} | |||
</source> | |||
sql | |||
<source lang="mysql"> | |||
CREATE TABLE `chat_logs` ( | |||
`id` int(11) NOT NULL AUTO_INCREMENT, | |||
`user` tinytext, | |||
`text` text, | |||
`date` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, | |||
PRIMARY KEY (`id`) | |||
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; | |||
CREATE TABLE `chats` ( | |||
`id` int(11) NOT NULL AUTO_INCREMENT, | |||
`user1` tinytext, | |||
`user2` tinytext, | |||
`nick1` tinytext, | |||
`nick2` tinytext, | |||
`image1` tinytext, | |||
`image2` tinytext, | |||
`chatimage` tinytext, | |||
`date` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, | |||
PRIMARY KEY (`id`) | |||
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; | |||
</source> | </source> | ||
Latest revision as of 11:17, 3 April 2012
Let's Talk About Pictures (LTAP)
LTAP is a collaboration with Louis Doulas and Harry Burke, to create a website that facilitates and encourages a one to one discussion about pictures. In LTAP, each user is initially presented with an upload form which accepts images. The user is required to upload a picture of their choosing and pick a nickname to enter the discussion, the chat room. The chat room displays a random picture from the database of images and the conversation can begin.
LTAP Development
Plan
Use nodejs, with the modules nowjs, mysql, forever, formidable, to create a realtime chat with image uploads and a mysql database for storage. (Consider switching mysql for couchdb or mongodb).
Resources
Application Logic
- Nodejs http://nodejs.org/
- Nowjs for handling socket.io http://nowjs.com/
- Express, a base app template systemconsider switching for next version. http://expressjs.com/guide.html
- Debugging with node-inspector http://elegantcode.com/2011/01/14/taking-baby-steps-with-node-js-debugging-with-node-inspector/
File Uploads
- Node Formidable Module for handling multipart uploadshttps://github.com/felixge/node-formidable/
- http://stackoverflow.com/questions/5149545/uploading-images-using-nodejs-express-and-mongo
- http://debuggable.com/posts/streaming-file-uploads-with-node-js:4ac094b2-b6c8-4a7f-bd07-28accbdd56cb
Mysql, Mysql Module
General articles to read
- http://www.debuggable.com/posts/understanding-node-js:4bd98440-45e4-4a9a-8ef7-0f7ecbdd56cb
- http://www.martinfowler.com/articles/injection.html
- http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html
- Simple beginner application stack http://www.nodebeginner.org/#the-application-stack
Deployment
- Look into Nginx
- Http-proxy https://github.com/nodejitsu/node-http-proxy
- Forever (node based CLI for starting/stopping node servers in the background) https://github.com/nodejitsu/forever/
- shell-script for stopping/starting server.js – replaced by Forever http://gitorious.org/naked-on-pluto/game-server/blobs/master/server.sh
Source
server.js
//db
var mysql = require('mysql');
var db = "ltappiting";
var chats = 'chats';
var chatLogs = 'chat_logs';
var client = mysql.createClient({
host: "localhost",
user: "root",
password: "root"
});
//server
var http = require("http");
var url = require("url");
var fs = require("fs");
var util = require('util');
var formidable = require('formidable');
var exec = require('child_process').exec;
var fu = exports;
function onRequest(req, res) {
var pathname = url.parse(req.url).pathname;
console.log("Request for " + pathname + " received.");
//routing
switch (pathname) {
case '/':
req.setEncoding("utf8");
var html = fs.readFileSync(__dirname+'/index.html');
res.end(html);
break;
case '/upload':
uploadFile(req, res);
break;
default:
req.setEncoding("utf8");
var content_type = fu.mime.lookupExtension(extname(pathname));
fs.readFile(__dirname+"/"+pathname, function(err, data) {
if(err) {
res.writeHead(404, {"Content-Type": "text/plain"});
res.write("404 Not found");
res.end();
} else {
headers = { "Content-Type": content_type
, "Content-Length": data.length
};
res.writeHead(200, headers);
res.write(data);
res.end();
}
});
break;
}
}
var server = http.createServer(onRequest).listen(1337);
console.log("server running on 1337");
var nowjs = require("now");
var everyone = nowjs.initialize(server);
//Session Variables
var sessions = {};
var rooms = [];
rooms[0] = 0;
// I'm working on a 1-1 chat client with nodejs and nowjs, and I'm wondering about the best practice for queuing/assigning rooms (groups) to the users, given that it should only be a 1 to 1 conversation, and the one who's been waiting the longest should be assigned first'. Any ideas? :)
//Send message to everyone on the users group
everyone.now.distributeMessage = function(message){
console.log('Received '+message+' from '+this.now.nick +' in serverroom '+this.now.room);
var group = nowjs.getGroup(this.now.room);
group.now.receiveMessage(this.now.id, this.now.nick, message);
var date = new Date();
//insert into DB
client.query('USE '+db);
client.query(
'INSERT INTO '+chatLogs+' '+
'SET user = ?, text = ?, date = ?',
[this.user.clientId, message, date]
);
};
//function to be called from client
everyone.now.join = function() {
var userID = this.user.clientId;
var nick = this.now.nick;
var image = this.now.image;
var id = this.now.id;
//get available room
var room = getRoom();
this.now.room = room;
var user = {
id: userID,
nick: nick,
room: room,
image: image
//timestamp: new Date()
};
//add user to room
nowjs.getGroup(room).addUser(userID);
//add user to session
sessions[user.id] = user;
var group = nowjs.getGroup(this.now.room);
if(rooms[room] == 2) {
var status = "Buddy Joined, say Hi!";
//get-users, add to db
nowjs.getGroup(room).getUsers(function (users) {
var user1 = users[0];
var user2 = users[1];
var nick1 = sessions[user1].nick;
var image1 = sessions[user1].image;
var nick2 = sessions[user2].nick;
var image2 = sessions[user2].image;
var date = new Date();
//var randNumber = Math.floor(Math.random()*11) + 11;
client.query('USE '+db);
client.query(
//'SELECT image1, id FROM '+chats + ' WHERE id=' + randNumber,
'SELECT image1, id FROM '+chats + ' ORDER BY rand() LIMIT 1',
function selectCb(err, results, fields) {
if (err) {
throw err;
}
var chatImage = results[0].image1;
client.end();
console.log(chatImage);
group.now.updateImage(chatImage);
//insert into DB
client.query('USE '+db);
client.query(
'INSERT INTO '+chats+' '+
'SET user1 = ?, user2 = ?, nick1 = ?, nick2 = ?, image1 = ?, image2 = ?, chatimage = ?, date = ?',
[user1, user2, nick1, nick2, image1, image2, chatImage, date]
);
});
});
} else {
var status = "Finding chat buddy..";
}
group.now.chatStatus(status);
}
everyone.now.isTyping = function(type){
console.log('Received '+type+' from '+this.now.id);
var group = nowjs.getGroup(this.now.room);
group.now.updateType(type, this.now.nick, this.now.id);
};
nowjs.on('disconnect', function () {
var userroom = this.now.room;
var nick = this.now.nick;
var id = this.now.id;
var userID = this.user.clientId;
if(rooms[userroom] == 2) {
rooms[userroom] = 1;
var status = "Buddy Left.. – Looking for a new one...";
} else {
rooms[userroom] = 0;
}
if (userID && sessions[id]) {
delete sessions[userID];
}
var group = nowjs.getGroup(this.now.room);
group.now.chatStatus(status);
console.log("user:", this.now.id, this.now.nick, " left");
});
function getRoom() {
for (var i=0; i < rooms.length; i++) {
if(rooms[i] === 1) {
rooms[i] = 2;
return i;
} else if (rooms[i] === 0) {
rooms[i] = 1;
return i;
}
};
rooms.push(1);
return rooms.length-1;
}
//upload
function uploadFile(req, res) {
var form = new formidable.IncomingForm(),
files = [],
fields = [];
fileob = {};
form.uploadDir = __dirname + '/tmp';
form.encoding = 'binary';
form
.on('field', function(field, value) {
console.log(field, value);
fields.push([field, value]);
})
.on('file', function(field, file) {
files.push([field, file]);
//check size type, cancel if wrong
var newfile = {
file: file.size,
name: file.name,
path: file.path,
type: file.type
};
fileob = newfile;
})
.on('end', function() {
console.log('-> upload done');
res.writeHead(200, {'content-type': 'text/plain'});
//move
var ext = extname(fileob.name);
var uni = Math.floor(Math.random()*99999).toString();
var cleanFilename = fileob.name.replace(/[^a-z0-9]/gi, '_').toLowerCase();
var filename = uni + cleanFilename + ext;
res.write(filename);
res.end();
var cmd = "mv " + fileob.path + " /home/jonasx/t2sp.net/upload/" + filename;
console.log(cmd);
exec(cmd, puts);
});
form.parse(req, function(err, fields, files) {
if (err) {
console.log(err);
}
});
}
function puts(error, stdout, stderr) { util.puts(stdout) }
function include(arr,obj) {
return (arr.indexOf(obj) != -1);
}
Array.prototype.remove = function(from, to) {
var rest = this.slice((to || from) + 1 || this.length);
this.length = from < 0 ? this.length + from : from;
return this.push.apply(this, rest);
};
function extname (path) {
var index = path.lastIndexOf(".");
return index < 0 ? "" : path.substring(index);
}
// stolen from node_chat, jack- thanks
fu.mime = {
// returns MIME type for extension, or fallback, or octet-steam
lookupExtension : function(ext, fallback) {
return fu.mime.TYPES[ext.toLowerCase()] || fallback || 'application/octet-stream';
},
// List of most common mime-types, stolen from Rack.
TYPES : { ".3gp" : "video/3gpp"
, ".a" : "application/octet-stream"
, ".ai" : "application/postscript"
, ".aif" : "audio/x-aiff"
, ".aiff" : "audio/x-aiff"
, ".asc" : "application/pgp-signature"
, ".asf" : "video/x-ms-asf"
, ".asm" : "text/x-asm"
, ".asx" : "video/x-ms-asf"
, ".atom" : "application/atom+xml"
, ".au" : "audio/basic"
, ".avi" : "video/x-msvideo"
, ".bat" : "application/x-msdownload"
, ".bin" : "application/octet-stream"
, ".bmp" : "image/bmp"
, ".bz2" : "application/x-bzip2"
, ".c" : "text/x-c"
, ".cab" : "application/vnd.ms-cab-compressed"
, ".cc" : "text/x-c"
, ".chm" : "application/vnd.ms-htmlhelp"
, ".class" : "application/octet-stream"
, ".com" : "application/x-msdownload"
, ".conf" : "text/plain"
, ".cpp" : "text/x-c"
, ".crt" : "application/x-x509-ca-cert"
, ".css" : "text/css"
, ".csv" : "text/csv"
, ".cxx" : "text/x-c"
, ".deb" : "application/x-debian-package"
, ".der" : "application/x-x509-ca-cert"
, ".diff" : "text/x-diff"
, ".djv" : "image/vnd.djvu"
, ".djvu" : "image/vnd.djvu"
, ".dll" : "application/x-msdownload"
, ".dmg" : "application/octet-stream"
, ".doc" : "application/msword"
, ".dot" : "application/msword"
, ".dtd" : "application/xml-dtd"
, ".dvi" : "application/x-dvi"
, ".ear" : "application/java-archive"
, ".eml" : "message/rfc822"
, ".eps" : "application/postscript"
, ".exe" : "application/x-msdownload"
, ".f" : "text/x-fortran"
, ".f77" : "text/x-fortran"
, ".f90" : "text/x-fortran"
, ".flv" : "video/x-flv"
, ".for" : "text/x-fortran"
, ".gem" : "application/octet-stream"
, ".gemspec" : "text/x-script.ruby"
, ".gif" : "image/gif"
, ".gz" : "application/x-gzip"
, ".h" : "text/x-c"
, ".hh" : "text/x-c"
, ".htm" : "text/html"
, ".html" : "text/html"
, ".ico" : "image/vnd.microsoft.icon"
, ".ics" : "text/calendar"
, ".ifb" : "text/calendar"
, ".iso" : "application/octet-stream"
, ".jar" : "application/java-archive"
, ".java" : "text/x-java-source"
, ".jnlp" : "application/x-java-jnlp-file"
, ".jpeg" : "image/jpeg"
, ".jpg" : "image/jpeg"
, ".js" : "application/javascript"
, ".json" : "application/json"
, ".log" : "text/plain"
, ".m3u" : "audio/x-mpegurl"
, ".m4v" : "video/mp4"
, ".man" : "text/troff"
, ".mathml" : "application/mathml+xml"
, ".mbox" : "application/mbox"
, ".mdoc" : "text/troff"
, ".me" : "text/troff"
, ".mid" : "audio/midi"
, ".midi" : "audio/midi"
, ".mime" : "message/rfc822"
, ".mml" : "application/mathml+xml"
, ".mng" : "video/x-mng"
, ".mov" : "video/quicktime"
, ".mp3" : "audio/mpeg"
, ".mp4" : "video/mp4"
, ".mp4v" : "video/mp4"
, ".mpeg" : "video/mpeg"
, ".mpg" : "video/mpeg"
, ".ms" : "text/troff"
, ".msi" : "application/x-msdownload"
, ".odp" : "application/vnd.oasis.opendocument.presentation"
, ".ods" : "application/vnd.oasis.opendocument.spreadsheet"
, ".odt" : "application/vnd.oasis.opendocument.text"
, ".ogg" : "application/ogg"
, ".p" : "text/x-pascal"
, ".pas" : "text/x-pascal"
, ".pbm" : "image/x-portable-bitmap"
, ".pdf" : "application/pdf"
, ".pem" : "application/x-x509-ca-cert"
, ".pgm" : "image/x-portable-graymap"
, ".pgp" : "application/pgp-encrypted"
, ".pkg" : "application/octet-stream"
, ".pl" : "text/x-script.perl"
, ".pm" : "text/x-script.perl-module"
, ".png" : "image/png"
, ".pnm" : "image/x-portable-anymap"
, ".ppm" : "image/x-portable-pixmap"
, ".pps" : "application/vnd.ms-powerpoint"
, ".ppt" : "application/vnd.ms-powerpoint"
, ".ps" : "application/postscript"
, ".psd" : "image/vnd.adobe.photoshop"
, ".py" : "text/x-script.python"
, ".qt" : "video/quicktime"
, ".ra" : "audio/x-pn-realaudio"
, ".rake" : "text/x-script.ruby"
, ".ram" : "audio/x-pn-realaudio"
, ".rar" : "application/x-rar-compressed"
, ".rb" : "text/x-script.ruby"
, ".rdf" : "application/rdf+xml"
, ".roff" : "text/troff"
, ".rpm" : "application/x-redhat-package-manager"
, ".rss" : "application/rss+xml"
, ".rtf" : "application/rtf"
, ".ru" : "text/x-script.ruby"
, ".s" : "text/x-asm"
, ".sgm" : "text/sgml"
, ".sgml" : "text/sgml"
, ".sh" : "application/x-sh"
, ".sig" : "application/pgp-signature"
, ".snd" : "audio/basic"
, ".so" : "application/octet-stream"
, ".svg" : "image/svg+xml"
, ".svgz" : "image/svg+xml"
, ".swf" : "application/x-shockwave-flash"
, ".t" : "text/troff"
, ".tar" : "application/x-tar"
, ".tbz" : "application/x-bzip-compressed-tar"
, ".tcl" : "application/x-tcl"
, ".tex" : "application/x-tex"
, ".texi" : "application/x-texinfo"
, ".texinfo" : "application/x-texinfo"
, ".text" : "text/plain"
, ".tif" : "image/tiff"
, ".tiff" : "image/tiff"
, ".torrent" : "application/x-bittorrent"
, ".tr" : "text/troff"
, ".txt" : "text/plain"
, ".vcf" : "text/x-vcard"
, ".vcs" : "text/x-vcalendar"
, ".vrml" : "model/vrml"
, ".war" : "application/java-archive"
, ".wav" : "audio/x-wav"
, ".wma" : "audio/x-ms-wma"
, ".wmv" : "video/x-ms-wmv"
, ".wmx" : "video/x-ms-wmx"
, ".wrl" : "model/vrml"
, ".wsdl" : "application/wsdl+xml"
, ".xbm" : "image/x-xbitmap"
, ".xhtml" : "application/xhtml+xml"
, ".xls" : "application/vnd.ms-excel"
, ".xml" : "application/xml"
, ".xpm" : "image/x-xpixmap"
, ".xsl" : "application/xml"
, ".xslt" : "application/xslt+xml"
, ".yaml" : "text/yaml"
, ".yml" : "text/yaml"
, ".zip" : "application/zip"
}
};
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>LTAP</title>
<meta name="description" content="">
<meta name="author" content="">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="style.css">
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="/nowjs/now.js"></script>
<script type="text/javascript">
util = {
urlRE: /https?:\/\/([-\w\.]+)+(:\d+)?(\/([^\s]*(\?\S+)?)?)?/g,
toStaticHTML: function(inputHtml) {
inputHtml = inputHtml.toString();
return inputHtml.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
},
zeroPad: function (digits, n) {
n = n.toString();
while (n.length < digits)
n = '0' + n;
return n;
},
timeString: function (date) {
var minutes = date.getMinutes().toString();
var hours = date.getHours().toString();
return this.zeroPad(2, hours) + ":" + this.zeroPad(2, minutes);
},
isBlank: function(text) {
var blank = /^\s*$/;
return (text.match(blank) !== null);
}
};
$(document).ready(function(){
var CONFIG = { focus: true
,unread: 0
};
now.receiveMessage = function(id, nick, messageRaw){
if(id === now.id) {
var nick = "you";
}
if(!CONFIG.focus){
CONFIG.unread++;
updateTitle();
}
var message = util.toStaticHTML(messageRaw);
var time = new Date();
var relTime = util.timeString(time);
var lastMessage = $("#message div:last");
var lastMessageTime = $(lastMessage).find(".time").text();
if($(lastMessage).attr("data-id") === id) {
if(relTime === lastMessageTime) {
var content = '<span class="clear">'
// + ' <span class="time">' + relTime + '</span>'
+ ' <span class="msg-text">' + message + '</span>'
+ '</span>';
} else {
var content = '<span class="clear">'
+ ' <span class="time">' + relTime + '</span>'
+ ' <span class="msg-text">' + message + '</span>'
+ '</span>';
}
$(lastMessage).append(content);
$("#message").scrollTop(55000);
} else {
var content = '<div data-id="'+id+'">'
+ ' <span class="time">' + util.timeString(time) + '</span>'
+ ' <span class="nick '+nick+'">' + nick + '</span>'
+ ' <span class="msg-text">' + message + '</span'
+ '</div>'
;
$("#message").append(content).scrollTop(55000);
}
if($(".isTyping").is("visible")) {
$(".isTyping").appendTo($("#messages"));
}
}
$(".send").click(function() {
var input = "";
now.messageRoom(currRoom, input.text());
});
now.chatStatus = function(status) {
var time = new Date();
var content = '<div>'
+ ' <span class="time">' + util.timeString(time) + '</span>'
+ ' <span class="status">' + status + '</span>'
+ '</div>'
;
if(status === "Buddy Joined, say Hi!") {
$("#text-input").prop("disabled", false).focus();
}
if(status === "Buddy Left.. – Looking for a new one...") {
$("#text-input").prop("disabled", true);
}
$("#message").append(content).scrollTop(55000);
}
now.updateImage = function(image) {
$("#image").attr("src", image).show();
}
now.updateType = function(val, nick, id) {
console.log(val, nick, id, now.id);
if(id != now.id) {
if(val === true) {
$(".isTyping").text(nick + " is typing…").appendTo($("#message")).show();
$("#message").scrollTop(55000);
} else {
$(".isTyping").hide();
}
}
}
$("#chatBox").bind("keypress", function(e) {
var code = (e.keyCode ? e.keyCode : e.which);
if(code == 13) { //Enter keycode
var message = $("#text-input").val();
if(message) {
now.distributeMessage($("#text-input").val());
$("#text-input").val("");
}
now.isTyping(false);
return false;
}
now.isTyping(true);
});
$("#text-input").bind("blur", function() {
console.log("blurred");
now.isTyping(false);
});
$("#loginform").submit(function(){
var nick = $("#nick").val();
if(!nick) {
var nick = "Stranger";
}
var image = $("#imageurl").val();
if(!image) {
$(".error").html("Image Can't Be Empty").show();
} else {
//ugly regex hack for validating image
var match = image.match(/http:\/\/.+?\.jpg|jpeg|png|gif/gi);
if(!match) {
$(".error").html("Not a valid Image Url").show();
} else {
now.id = Math.floor(Math.random()*99999999999).toString();
now.room = 1;
now.nick = nick;
now.image = image;
now.join(nick, image);
$("#chat").show();
$("#login").hide();
$("#text-input").focus();
}
}
return false;
});
$("#file").submit(function() {
$("#transFrame").load(function() {
response = $(this).contents().find('body').text();
if(response) {
$("#file").hide();
var filename = "http://t2sp.net/upload/" + response;
$("#imageurl").val(filename);
$(".toggle-handle").hide();
$("#imageurl").prop("disabled", true);
}
//return false;
});
});
$(".toggle-handle").click(function() {
$(this).next(".toggle-box").show();
return false;
});
// on establishing 'now' connection, set server room and allow message sending
now.ready(function(){
});
//listen for browser events so we know to update the document title
$(window).bind("blur", function() {
CONFIG.focus = false;
updateTitle();
});
$(window).bind("focus", function() {
CONFIG.focus = true;
CONFIG.unread = 0;
updateTitle();
});
function updateTitle(){
if (CONFIG.unread) {
document.title = "(" + CONFIG.unread.toString() + ") LTAP";
} else {
document.title = "LTAP";
}
}
});
</script>
</head>
<body>
<div id="login">
<hgroup>
<h1>LTAP</h1>
</hgroup>
<form id="loginform" action="" method="get">
<h3 class="error hidden"></h3>
<input type="text" id="nick" placeholder="nickname (optional)"/>
<input type="text" id="imageurl" placeholder="Image (URL)"/>
<input type="submit" id="submit" value="go" class="button"/>
</form>
<h3 class='toggle-handle'>Or Upload A File...</h3>
<div class='toggle-box hidden'>
<form target="transFrame" id="file" action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file-upload" id="file-upload" multiple="multiple">
<input type="submit" value="Upload" class="button">
</form>
</div>
</div>
<iframe style="" class="hidden" name="transFrame" id="transFrame"></iframe>
<div id="chat" class="hidden">
<div class="image">
<img id="image" class="" src="http://www.pandl.org/img/1x1" alt="placeholder img"/>
</div>
<div class="right">
<div id="message">
<span class="isTyping hidden"></span>
</div>
<form id="chatBox" action="" method="get">
<textarea type="text" id="text-input" name="chat" value="" disabled></textarea>
</form>
</div>
</div>
<div class="disclaimer">
<p>* all conversations are being stored in a database</p>
</div>
</body>
</html>
style.css
article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { display: block; }
audio, canvas, video { display: inline-block; *display: inline; *zoom: 1; }
audio:not([controls]) { display: none; }
[hidden] { display: none; }
html { font-size: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
html, button, input, select, textarea { font-family: sans-serif; color: #222; }
body { margin: 0; font-size: 1em; line-height: 1.4;}
::-moz-selection { background: #333; color: #fff; text-shadow: none; }
::selection { background: #333; color: #fff; text-shadow: none; }
/* =============================================================================
Links
========================================================================== */
a { color: #00e; }
a:visited { color: #551a8b; }
a:hover { color: #06e; }
a:focus { outline: thin dotted; }
a:hover, a:active { outline: 0; }
ul, li, ol { margin: 0; padding: 0; list-style: none;}
img { border: 0; -ms-interpolation-mode: bicubic; vertical-align: middle; }
form { margin: 0; }
fieldset { border: 0; margin: 0; padding: 0; }
label { cursor: pointer; }
legend { border: 0; *margin-left: -7px; padding: 0; white-space: normal; }
button, input, select, textarea { font-size: 100%; margin: 0; vertical-align: baseline; *vertical-align: middle; }
button, input { line-height: normal; }
button, input[type="button"], input[type="reset"], input[type="submit"] { cursor: pointer; -webkit-appearance: button; *overflow: visible; }
button[disabled], input[disabled] { cursor: default; }
input[type="checkbox"], input[type="radio"] { box-sizing: border-box; padding: 0; *width: 13px; *height: 13px; }
input[type="search"] { -webkit-appearance: textfield; -moz-box-sizing: content-box; -webkit-box-sizing: content-box; box-sizing: content-box; }
input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; }
button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; }
textarea { overflow: auto; vertical-align: top; resize: vertical; }
html, body {width: 100%; height: 100%; font: 16px/1.3 sans-serif; overflow: hidden; margin: 0; padding: 0;}
#login input {margin-bottom: 10px; width: 320px; border: 0; border-bottom: 1px solid; font-size: 24px; color: #000;}
#login input[type='submit'] {width: 160px; margin-left: 80px; margin-bottom: 20px;}
#login {width: 320px; margin: 100px auto;}
.error {color: #f00;}
.image {width: 50%; float: left; text-align: center; padding-top: 10px; }
.image img {max-width: 80%; max-height: 80%;}
.right {bottom: 60px;left: 50%;position: absolute;right: 20px;top: 10px;}#chat {height: 100%; width: 100%; margin: 0; padding: 0;}
#message {height: 85%; width: 100%; border: 1px solid #ccc; padding: 10px 0 10px 10px; overflow-y: scroll; margin-bottom: 10px;}
#message div {padding-bottom: 5px;}
#chatBox {height: 15%;}
#text-input {width: 100%;height: 100%; margin: 0; padding: 10px 0 10px 10px; border: 1px solid #ccc; font-size: 16px;}
h3 {color: #333;}
.nick {color: red;}
.you {color: blue;}
.disclaimer {font-size: 12px; color: #666; position: fixed; bottom: 5px; left: 5px;}
.isTyping {font-size: 14px; font-weight: bold; padding-top: 10.4px;}
input:valid, textarea:valid { }
input:invalid, textarea:invalid { background-color: #f0dddd; }
.hidden { display: none;}
.invisible { visibility: hidden; }
.cf:before, .cf:after, #message div:before, #message div:after { content: ""; display: table; }
.cf:after, #message div:after { clear: both; }
.cf, #message div { *zoom: 1; }
.button {
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
background: white url('button.png') 0 0 repeat-x;
background: -moz-linear-gradient(0% 170% 90deg, #c4c4c4, white);
background: -webkit-gradient(linear, 0% 0%, 0% 170%, from(white), to(#c4c4c4));
border: 1px solid;
border-color: #e6e6e6 #cccccc #cccccc #e6e6e6;
border-radius: 3px;
color: #404040;
display: inline-block;
font-family: "helvetica neue", helvetica, arial, freesans, "liberation sans", "numbus sans l", sans-serif;
font-size: 13px;
outline 0;
padding: 5px 8px 5px;
text-align: center;
text-decoration: none;
text-shadow: 1px 1px 0 white;
white-space: nowrap; }
.button:hover {
background: -moz-linear-gradient(0% 170% 90deg, #b8b8b8, white);
background: -webkit-gradient(linear, 0% 0%, 0% 170%, from(white), to(#b8b8b8));
border-color: #99ccff;
color: #333333; }
.button:active {
position: relative;
top: 1px; }
.button:active, .button:focus {
background-position: 0 -25px;
background: -moz-linear-gradient(0% 170% 90deg, white, #dedede);
background: -webkit-gradient(linear, 0% 0%, 0% 170%, from(#dedede), to(white));
border-color: #8fc7ff #94c9ff #94c9ff #8fc7ff;
color: #1a1a1a;
text-shadow: 1px -1px 0 rgba(255, 255, 255, 0.5); }
.time {color: #666; font-size: 14px; float: right; margin-right: 10px;}
#message div {border-bottom: 1px dotted rgb(230,230,230); padding: 6px 0px 5px 0; clear: both;}
.nick {float: left; margin-right: 5px;}
.msg-text {float: left; width: 90%; clear: both;}
sql
CREATE TABLE `chat_logs` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user` tinytext,
`text` text,
`date` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
CREATE TABLE `chats` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user1` tinytext,
`user2` tinytext,
`nick1` tinytext,
`nick2` tinytext,
`image1` tinytext,
`image2` tinytext,
`chatimage` tinytext,
`date` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;