User:Jonas Lund/tri1.2: Difference between revisions

From XPUB & Lens-Based wiki
 
(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)=
The 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 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">
  //initiate database
//db
  var mysql = require('mysql');
var mysql = require('mysql');
  var db = 'ltap';
var db = "ltappiting";
  var chats = 'chats';
var chats = 'chats';
  var chatLogs = 'chat_logs'
var chatLogs = 'chat_logs';
  var client = mysql.createClient({
var client = mysql.createClient({
    host: '127.0.0.1',
  host: "localhost",
    user: 'root',
  user: "root",
    password: 'root',
  password: "root"
    port: 8889
});
  });
 
  //includes
  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) {
//server
    var pathname = url.parse(req.url).pathname;
var http = require("http");
    console.log("Request for " + pathname + " received.");
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;


    //routing
function onRequest(req, res) {
    switch (pathname) {
  var pathname = url.parse(req.url).pathname;
        case '/':
  console.log("Request for " + pathname + " received.");
        req.setEncoding("utf8");
            var html = fs.readFileSync(__dirname+'/index.html');
            res.end(html);
        break;
        //handle file upload
        case '/upload':
            uploadFile(req, res);
        break;
          default:
            //check if file exits, otherwise return 404
            req.setEncoding("utf8");
            var content_type = fu.mime.lookupExtension(extname(pathname));


              fs.readFile(__dirname+"/"+pathname, function(err, data) {  
  //routing
                console.log(pathname);
  switch (pathname) {
                if(err) {
      case '/':
                  res.writeHead(404, {"Content-Type": "text/plain"});
      req.setEncoding("utf8");
                  res.write("404 Not found");
          var html = fs.readFileSync(__dirname+'/index.html');
                  res.end();
          res.end(html);
                } else {
      break;
                  headers = { "Content-Type": content_type
      case '/upload':
                            , "Content-Length": data.length
          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");


                  res.writeHead(200, headers);
var nowjs = require("now");
                  res.write(data);
var everyone = nowjs.initialize(server);
                  res.end();               
                }
              });
        break;
      }
  }
  var server = http.createServer(onRequest).listen(1337);
  console.log("server running on 1337");


  var nowjs = require("now");
//Session Variables
  var everyone = nowjs.initialize(server);
var sessions = {};
var rooms = [];
rooms[0] = 0;


  //Session Variables
// 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? :)
  var sessions = {};
  var rooms = [];
  rooms[0] = 0;


  //Send message to everyone on the users group
//Send message to everyone on the users group
  everyone.now.distributeMessage = function(message){
everyone.now.distributeMessage = function(message){
      console.log('Received '+message+' from '+this.now.nick +' in serverroom '+this.now.room);
    console.log('Received '+message+' from '+this.now.nick +' in serverroom '+this.now.room);
      var group = nowjs.getGroup(this.now.room);     
    var group = nowjs.getGroup(this.now.room);     
      group.now.receiveMessage(this.now.id, this.now.nick, message);
    group.now.receiveMessage(this.now.id, this.now.nick, message);
     
   
      var date = new Date();
    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]
    ); 


      //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() {         
  //Join action, called from client
    var userID = this.user.clientId;
  everyone.now.join = function() {         
    var nick = this.now.nick;   
      var userID = this.user.clientId;
    var image = this.now.image;
      var nick = this.now.nick;   
    var id = this.now.id;
      var image = this.now.image;
   
      var id = this.now.id;
    //get available room
 
    var room = getRoom();
      //get available room
    this.now.room = room;
      var room = getRoom();
   
      this.now.room = room;
       
     
      //create user object
       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 = "Partner Joined";   
         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();
            
            
           //send image to the client
           //var randNumber = Math.floor(Math.random()*11) + 11;
          group.now.updateImage(image1,image2);
 
          //insert into DB
           client.query('USE '+db);
           client.query('USE '+db);
           client.query(
           client.query(
             'INSERT INTO '+chats+' '+
             //'SELECT image1, id FROM '+chats + ' WHERE id=' + randNumber,
            'SET user1 = ?, user2 = ?, nick1 = ?, nick2 = ?, image1 = ?, image2 = ?, date = ?',
            'SELECT image1, id FROM '+chats + ' ORDER BY rand() LIMIT 1',
            [user1, user2, nick1, nick2, image1, image2, date]
            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);
  }
    
    
  //User Disconnect, remove from sessions
}
  nowjs.on('disconnect', function () {
    var userroom = this.now.room;
    var nick = this.now.nick;
    var id = this.now.id;
    var userID = this.user.clientId;
   
    //check if user sticks around for a new chat buddy
    if(rooms[userroom] == 2) {
      rooms[userroom] = 1;
      var status = "Partner Left.. ;( – Looking for a new one...";
    } else {
      rooms[userroom] = 0;
    }


    //remove user
everyone.now.isTyping = function(type){
    if (userID && sessions[id]) {
     console.log('Received '+type+' from '+this.now.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");
     var group = nowjs.getGroup(this.now.room);   
  });
    group.now.updateType(type, this.now.nick, this.now.id);
};


  //check room array for available room
nowjs.on('disconnect', function () {  
  function getRoom() {
  var userroom = this.now.room;
    for (var i=0; i < rooms.length; i++) {
  var nick = this.now.nick;
      if(rooms[i] === 1) {
  var id = this.now.id;
        rooms[i] = 2;    
  var userID = this.user.clientId;
        return i;
   
 
  if(rooms[userroom] == 2) {
      } else if (rooms[i] === 0) {
    rooms[userroom] = 1;
          rooms[i] = 1;
    var status = "Buddy Left.. – Looking for a new one...";
          return i;
  } else {
      }
     rooms[userroom] = 0;
     };
  }


    rooms.push(1);
  if (userID && sessions[id])
    return rooms.length-1;  
    delete sessions[userID];
   }
   }


   //handle image upload
   var group = nowjs.getGroup(this.now.room);
   function uploadFile(req, res) {
   group.now.chatStatus(status);
    var form = new formidable.IncomingForm(),
     
        files = [],
  console.log("user:", this.now.id, this.now.nick, " left");
        fields = [];
});
        fileob = {};


    form.uploadDir = __dirname + '/tmp';
function getRoom() { 
     form.encoding = 'binary';
  for (var i=0; i < rooms.length; i++) {
     if(rooms[i] === 1) {
      rooms[i] = 2;     
      return i;


     form
     } else if (rooms[i] === 0) {
      .on('field', function(field, value) {
         rooms[i] = 1;
         console.log(field, value);
         return i;
         fields.push([field, value]);
    }
      })
  };
      .on('file', function(field, file) {
 
        //console.log("on file", field, file);
  rooms.push(1);
        files.push([field, file]);
  return rooms.length-1;  
}


        //check size type, cancel if wrong                 
//upload
        var newfile = {
function uploadFile(req, res) {
            file: file.size,  
  var form = new formidable.IncomingForm(),
            name: file.name,
      files = [],
            path: file.path,
      fields = [];
            type: file.type
      fileob = {};
        };
     
  form.uploadDir = __dirname + '/tmp';
  form.encoding = 'binary';


        fileob = newfile;
  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                 
      .on('end', function() {
      var newfile = {
        console.log('-> upload done');
          file: file.size,
        res.writeHead(200, {'content-type': 'text/plain'});
          name: file.name,
 
          path: file.path,
        //filename cleanup
          type: file.type
        var ext = extname(fileob.name);       
      };
        var uni = Math.floor(Math.random()*99999).toString();
                         
        var cleanFilename = fileob.name.replace(/[^a-z0-9]/gi, '_').toLowerCase();
      fileob = newfile;
        var filename = uni + cleanFilename + ext;
     
       
    })
        //return file name to client
    .on('end', function() {
        res.write(filename);
      console.log('-> upload done');
        res.end();       
      res.writeHead(200, {'content-type': 'text/plain'});
       
         
        //move file in place
      //move
        var cmd = "mv " + fileob.path + " /path/to/upload/folder/" + filename;
      var ext = extname(fileob.name);       
        console.log(cmd);
      var uni = Math.floor(Math.random()*99999).toString();
        exec(cmd, puts);
      var cleanFilename = fileob.name.replace(/[^a-z0-9]/gi, '_').toLowerCase();
 
      var filename = uni + cleanFilename + ext;
      });
           
  //  form.parse(req);
      res.write(filename);
 
      res.end();       
      form.parse(req, function(err, fields, files) {
           
        if (err) {
      var cmd = "mv " + fileob.path + " /home/jonasx/t2sp.net/upload/" + filename;
          console.log(err);
      console.log(cmd);
        }
      exec(cmd, puts);
      });
                 
  }
    });
  function puts(error, stdout, stderr) { util.puts(stdout) }
    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);
}


  function include(arr,obj) {
Array.prototype.remove = function(from, to) {
      return (arr.indexOf(obj) != -1);
  var rest = this.slice((to || from) + 1 || this.length);
   }
   this.length = from < 0 ? this.length + from : from;
  return this.push.apply(this, rest);
};


  Array.prototype.remove = function(from, to) {
function extname (path) {
    var rest = this.slice((to || from) + 1 || this.length);
  var index = path.lastIndexOf(".");
    this.length = from < 0 ? this.length + from : from;
  return index < 0 ? "" : path.substring(index);
    return this.push.apply(this, rest);
}
  };


   //get ext name
// stolen from node_chat, jack- thanks
   function extname (path) {
fu.mime = {
     var index = path.lastIndexOf(".");
   // returns MIME type for extension, or fallback, or octet-steam
    return index < 0 ? "" : path.substring(index);
   lookupExtension : function(ext, fallback) {
   }
     return fu.mime.TYPES[ext.toLowerCase()] || fallback || 'application/octet-stream';
   },


   //stolen from node_chat, jack- thanks
   // List of most common mime-types, stolen from Rack.
   fu.mime = {
  TYPES : { ".3gp"  : "video/3gpp"
     // returns MIME type for extension, or fallback, or octet-steam
          , ".a"    : "application/octet-stream"
     lookupExtension : function(ext, fallback) {
          , ".ai"    : "application/postscript"
      return fu.mime.TYPES[ext.toLowerCase()] || fallback || 'application/octet-stream';
          , ".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"
          }
};


    // 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>
<!doctype html>
  <html lang="en">
<html lang="en">
  <head>
<head>
    <meta charset="utf-8">
  <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title>LTAP</title>
  <title>LTAP</title>
    <meta name="description" content="">
  <meta name="description" content="">
    <meta name="author" content="">
  <meta name="author" content="">
    <meta name="viewport" content="width=device-width,initial-scale=1">
  <meta name="viewport" content="width=device-width,initial-scale=1">
    <link rel="stylesheet" href="style.css">
  <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 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 src="/nowjs/now.js"></script>
     <script>
  <script type="text/javascript">
     $(document).ready(function(){
 
    now.receiveMessage = function(id, nick, message){
  util = {
     urlRE: /https?:\/\/([-\w\.]+)+(:\d+)?(\/([^\s]*(\?\S+)?)?)?/g,
 
    toStaticHTML: function(inputHtml) {
      inputHtml = inputHtml.toString();
      return inputHtml.replace(/&/g, "&amp;")
                      .replace(/</g, "&lt;")
                      .replace(/>/g, "&gt;");
    },
   
     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);


    var content = '<div>'
      }
                  + '  <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() {
  $(".send").click(function() {
        var input = "";
      var input = "";
        now.messageRoom(currRoom, input.text());
      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>'
                ;


     now.chatStatus = function(status) {
     if(status === "Buddy Joined, say Hi!") {
    var content = '<div>'
      $("#text-input").prop("disabled", false).focus();
                  + '  <span class="status">' + status + '</span>'
    }
                  + '</div>'
   
                  ;
    if(status === "Buddy Left.. – Looking for a new one...") {
      if(status === "Partner Joined") {
      $("#text-input").prop("disabled", true);
        $("#text-input").prop("disabled", false).focus();
    }      
      }


      $("#message").append(content);
    $("#message").append(content).scrollTop(55000);
     }
  }
   
  now.updateImage = function(image) {
     $("#image").attr("src", image).show();       


    now.updateImage = function(image1,image2) {
  }
       if(image1 === now.image) {
 
         var image = image2;
  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 image = image1;
         $(".isTyping").hide();
       }
      }     
      $("#image").attr("src", image);      
  }
  }
 
  $("#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);


  // Send message to people in the same group
        $("#chat").show();
  $("#chatBox").submit(function(){
        $("#login").hide();
    var message = $("#text-input").val();
        $("#text-input").focus();       
    if(message) {
      }     
    now.distributeMessage($("#text-input").val());
    }   
    $("#text-input").val("");
    return false;
    }
  });
    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>
      
      
     //handle login
     <form id="loginform" action="" method="get">
    $("#loginform").submit(function(){
       <h3 class="error hidden"></h3>
       var nick = $("#nick").val();


       if(!nick) {
       <input type="text" id="nick" placeholder="nickname (optional)"/>
        var nick = "Stranger";
      <input type="text" id="imageurl" placeholder="Image (URL)"/>
      }
        
        
       var image = $("#imageurl").val();   
       <input type="submit" id="submit" value="go" class="button"/>
       if(!image) {
    </form>       
         $(".error").html("Image Can't Be Empty").show();     
 
       } else {
    <h3 class='toggle-handle'>Or Upload A File...</h3>
                         
    <div class='toggle-box hidden'>
        //ugly regex hack for validating image
       <form target="transFrame" id="file" action="/upload" method="post" enctype="multipart/form-data">
        var match = image.match(/http:\/\/.+?\.jpg|jpeg|png|gif/gi);
        <input type="file" name="file-upload" id="file-upload" multiple="multiple">
         if(!match) {
         <input type="submit" value="Upload" class="button">
          $(".error").html("Not a valid Image Url").show();
       </form>   
        } else {
    </div>
          now.id = Math.floor(Math.random()*99999999999).toString();
  </div>
          now.room = 1;
 
          now.nick = nick;
  <iframe style="" class="hidden" name="transFrame" id="transFrame"></iframe>
          now.image = image;
  <div id="chat" class="hidden">
          now.join(nick, image);
    <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; }


          $("#chat").show();
html, body {width: 100%; height: 100%; font: 16px/1.3 sans-serif; overflow: hidden; margin: 0; padding: 0;}
          $("#login").hide();
#login input {margin-bottom: 10px; width: 320px; border: 0; border-bottom: 1px solid; font-size: 24px; color: #000;}
          $("#text-input").focus();      
#login input[type='submit'] {width: 160px; margin-left: 80px; margin-bottom: 20px;}
        }     
      }   
      return false;
    });


    //ugly file uploading hack
#login {width: 320px; margin: 100px auto;}
    $("#file").submit(function() {  
.error {color: #f00;}
      $("#transFrame").load(function() {      
.image {width: 50%; float: left; text-align: center; padding-top: 10px; }
      response = $(this).contents().find('body').text();
.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;}


      if(response) {
input:valid, textarea:valid { }
        $("#file").hide();
input:invalid, textarea:invalid { background-color: #f0dddd; }
        var filename = "http://localhost:1337/upload/" + response;
        $("#imageurl").val(filename);     
        $(".toggle-handle").hide();


        $("#imageurl").prop("disabled", true);
.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; }


      //return false;
.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; }


     $(".toggle-handle").click(function() {
  .button:hover {
      $(this).next(".toggle-box").show();
     background:          -moz-linear-gradient(0% 170% 90deg, #b8b8b8, white);
      return false;
    background:          -webkit-gradient(linear, 0% 0%, 0% 170%, from(white), to(#b8b8b8));
     });
    border-color:          #99ccff;
     color:                #333333; }


    // on establishing 'now' connection, set server room and allow message sending
  .button:active {
  now.ready(function(){
    position:              relative;
     
    top:                  1px; }
  });
  });
  </script>
  </head>
  <body> 
    <div id="login">
      <form id="loginform" action="" method="get">
        <h3 class="error hidden"></h3>


        <input type="text" id="nick" placeholder="nickname"/>
  .button:active, .button:focus {
        <input type="text" id="imageurl" placeholder="Image (URL)"/>
    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); }


        <input type="submit" id="submit" value="go" />
.time {color: #666; font-size: 14px; float: right; margin-right: 10px;}
      </form>       
#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;}


      <h3 class='toggle-handle'>Or Upload A File...</h3>
</source>
      <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" >
        </form>   
      </div>
    </div>


    <iframe style="" class="hidden" name="transFrame" id="transFrame"></iframe>
sql
<source lang="mysql">


    <div id="chat" class="hidden">
CREATE TABLE `chat_logs` (
      <div class="image">
  `id` int(11) NOT NULL AUTO_INCREMENT,
        <img id="image" src="http://i.imgur.com/aED4b.jpg" />
  `user` tinytext,
      </div>
  `text` text,
  `date` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;


      <div class="right">
CREATE TABLE `chats` (
        <div id="message">     
  `id` int(11) NOT NULL AUTO_INCREMENT,
        </div>   
  `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;


        <form id="chatBox" action="" method="get">
          <input type="text" id="text-input" name="chat" value="" disabled/>
        </form>
      </div>
    </div>


  </body>
  </html>
</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

File Uploads

  • Node Formidable Module for handling multipart uploadshttps://github.com/felixge/node-formidable/

Mysql, Mysql Module

General articles to read


Deployment

  • Look into Nginx


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, "&amp;")
                      .replace(/</g, "&lt;")
                      .replace(/>/g, "&gt;");
    },
    
    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;

doc1.png doc2.png doc3.png doc4.png