User:Jonas Lund/tri1.2

From XPUB & Lens-Based wiki
< User:Jonas Lund
Revision as of 11:17, 3 April 2012 by Jonas Lund (talk | contribs) (→‎Let's Talk About Pictures (LTAP))
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

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