summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xbin/repl.bat2
-rwxr-xr-xbin/repl.sh2
-rwxr-xr-xdb/0-create.psql4
-rwxr-xr-xlib/antlr-2.7.7.jarbin0 -> 445288 bytes
-rwxr-xr-xlib/stringtemplate-3.2.1.jarbin0 -> 124378 bytes
-rw-r--r--src/site.clj256
-rwxr-xr-xstatic/home.js35
-rwxr-xr-xstatic/index.html173
-rwxr-xr-xstatic/jquery.editinplace.1.0.1.packed.js287
-rw-r--r--static/main.css10
-rwxr-xr-xstatic/pichat.css241
-rwxr-xr-xstatic/pichat.js192
-rwxr-xr-xstatic/style.css22
-rwxr-xr-xtemplate/chat.st68
-rwxr-xr-xtemplate/footer.st3
-rwxr-xr-xtemplate/header.st13
-rwxr-xr-xtemplate/logged_dump.st6
-rwxr-xr-xtemplate/profile.st80
18 files changed, 970 insertions, 424 deletions
diff --git a/bin/repl.bat b/bin/repl.bat
index faa6ee2..5dab8ae 100755
--- a/bin/repl.bat
+++ b/bin/repl.bat
@@ -1,3 +1,3 @@
REM Windows REPL script
-java -server -cp lib/commons-io-1.4.jar;lib/commons-fileupload-1.2.1.jar;lib/commons-codec-1.3.jar;lib/clojure.jar;lib/clojure-contrib.jar;lib/compojure.jar;lib/jetty-6.1.14.jar;lib/jetty-util-6.1.14.jar;lib/servlet-api-2.5-6.1.14.jar;lib/jline-0.9.94.jar;lib/postgresql-8.4-701.jdbc4.jar jline.ConsoleRunner clojure.lang.Repl %1 \ No newline at end of file
+java -server -cp lib/commons-io-1.4.jar;lib/commons-fileupload-1.2.1.jar;lib/commons-codec-1.3.jar;lib/clojure.jar;lib/clojure-contrib.jar;lib/compojure.jar;lib/jetty-6.1.14.jar;lib/jetty-util-6.1.14.jar;lib/servlet-api-2.5-6.1.14.jar;lib/jline-0.9.94.jar;lib/postgresql-8.4-701.jdbc4.jar;lib/stringtemplate-3.2.1.jar;lib/antlr-2.7.7.jar jline.ConsoleRunner clojure.lang.Repl %1 \ No newline at end of file
diff --git a/bin/repl.sh b/bin/repl.sh
index e9a63aa..9267ad2 100755
--- a/bin/repl.sh
+++ b/bin/repl.sh
@@ -1,3 +1,3 @@
#!/bin/sh
-java -server -cp .:lib/commons-io-1.4.jar:lib/commons-fileupload-1.2.1.jar:lib/commons-codec-1.3.jar:lib/jline-0.9.94.jar:lib/clojure.jar:lib/clojure-contrib.jar:lib/compojure.jar:lib/jetty-6.1.14.jar:lib/jetty-util-6.1.14.jar:lib/servlet-api-2.5-6.1.14.jar:lib/postgresql-8.4-701.jdbc4.jar jline.ConsoleRunner clojure.lang.Repl $1
+java -server -cp .:lib/commons-io-1.4.jar:lib/commons-fileupload-1.2.1.jar:lib/commons-codec-1.3.jar:lib/jline-0.9.94.jar:lib/clojure.jar:lib/clojure-contrib.jar:lib/compojure.jar:lib/jetty-6.1.14.jar:lib/jetty-util-6.1.14.jar:lib/servlet-api-2.5-6.1.14.jar:lib/postgresql-8.4-701.jdbc4.jar:lib/stringtemplate-3.2.1.jar:lib/antlr-2.7.7.jar jline.ConsoleRunner clojure.lang.Repl $1
diff --git a/db/0-create.psql b/db/0-create.psql
index d34ccdd..35278c7 100755
--- a/db/0-create.psql
+++ b/db/0-create.psql
@@ -3,7 +3,9 @@ CREATE TABLE users (
nick text UNIQUE NOT NULL,
hash text NOT NULL,
email text NOT NULL,
- created_on timestamp NOT NULL DEFAULT now()
+ created_on timestamp NOT NULL DEFAULT now(),
+ contact text,
+ bio text
);
CREATE TABLE rooms (
diff --git a/lib/antlr-2.7.7.jar b/lib/antlr-2.7.7.jar
new file mode 100755
index 0000000..5e5f14b
--- /dev/null
+++ b/lib/antlr-2.7.7.jar
Binary files differ
diff --git a/lib/stringtemplate-3.2.1.jar b/lib/stringtemplate-3.2.1.jar
new file mode 100755
index 0000000..8a309cc
--- /dev/null
+++ b/lib/stringtemplate-3.2.1.jar
Binary files differ
diff --git a/src/site.clj b/src/site.clj
index 63e2c2a..1c43ae3 100644
--- a/src/site.clj
+++ b/src/site.clj
@@ -2,9 +2,12 @@
(ns pichat
(:import java.lang.System
+ java.text.SimpleDateFormat
+ java.util.Date
clojure.lang.PersistentQueue
org.apache.commons.codec.digest.DigestUtils
- javax.servlet.http.Cookie)
+ javax.servlet.http.Cookie
+ org.antlr.stringtemplate.StringTemplateGroup)
(:use compojure
clojure.contrib.json.write
clojure.contrib.sql))
@@ -18,8 +21,11 @@
:user "postgres"
:password "root"}))
+(def template-group (new StringTemplateGroup "dumpfm" "template"))
+(.setRefreshInterval template-group 3)
+
(defstruct user-struct :user-id :nick :last-seen)
-(defstruct message-struct :nick :content :timestamp)
+(defstruct message-struct :nick :avatar :content :created_on)
(def users (ref {}))
(def messages (ref []))
@@ -28,9 +34,6 @@
(def flusher-sleep-ms 4000)
(def user-timeout-ms 5000)
-(defn swap [f]
- (fn [& more] (apply f (reverse more))))
-
(def flusher (agent nil))
(defn flush! [x]
@@ -44,40 +47,42 @@
(. Thread (sleep flusher-sleep-ms))
x)
+;; Utils
+
+(defn encode-html-entities [s]
+ (loop [ret s
+ [[char replacement] & rest] [["&" "&"]
+ ["'" "'"]
+ ["\"" """]
+ ["<" "&lt;"]
+ [">" "&gt;"]]]
+ (if (nil? char)
+ ret
+ (recur (.replaceAll ret char replacement)
+ rest))))
+
+(defn swap [f]
+ (fn [& more] (apply f (reverse more))))
+
+(def formatter (new SimpleDateFormat "h:mm EEE M/d"))
+
+;;;;;
+
(defn resp-error [message]
{:status 400 :headers {} :body message})
(defn resp-success [message]
{:status 200 :headers {} :body (json-str message)})
-(defn new-messages
- ([since] (reverse (take-while (fn [m] (> (m :timestamp) since)) @messages)))
- ([] (reverse (take 25 @messages))))
-
-(def random (java.util.Random.))
-(def max-user-int 1000000)
-(defn make-random-nick []
- (let [nick (str "user-" (.nextInt random max-user-int))]
- (if (contains? @users nick)
- (make-random-nick)
- nick)))
-
-(defn updates
- ([] {"users" (sort (keys @users)) "messages" (new-messages)})
- ([since] {"users" (sort (keys @users)) "messages" (new-messages since)}))
+;; Database
(defn do-select [query]
(with-connection db
(with-query-results rs query
(doall rs))))
-(defn fetch-messages [room_id]
- (let [query (str "SELECT m.content, m.created_on, u.nick FROM messages m, users u "
- "WHERE room_id = ? AND m.user_id = u.user_id")
- res (do-select [query room_id])]
- (map (fn [r] (struct message-struct (r :nick) (r :content) (.getTime (r :created_on))))
- res)))
+;; User authentication
(defn fetch-nick [nick]
(let [query "SELECT * FROM users WHERE nick = ?"]
@@ -86,8 +91,89 @@
(defn authorize-nick-hash [nick hash]
(let [db-user (fetch-nick nick)]
(if (and db-user (= (db-user :hash) hash))
- (db-user :user_id)
- false)))
+ db-user false)))
+
+;; Message handling
+
+(defn process-message-for-json [d]
+ (assoc d :created_on (.getTime (d :created_on))))
+
+(defn process-message-for-output [d]
+ (let [avatar (d :avatar)]
+ {"nick" (encode-html-entities (d :nick))
+ "avatar" (if avatar (encode-html-entities avatar) nil)
+ "created_on" (.format formatter (d :created_on))
+ "content" (encode-html-entities (d :content))}))
+
+(defn new-messages
+ ([since-ts]
+ (let [since-date (new Date (long since-ts))]
+ (reverse (take-while (fn [m] (.after (m :created_on) since-date)) @messages))))
+ ([] (reverse (take 25 @messages))))
+
+(defn updates
+ ([] {"users" (sort (keys @users))
+ "messages" (map process-message-for-json (new-messages))})
+ ([since] {"users" (sort (keys @users))
+ "messages" (map process-message-for-json (new-messages since))}))
+
+(defn fetch-messages-by-room [room-id]
+ (let [query (str "SELECT m.content, m.created_on, u.nick, u.avatar "
+ "FROM messages m, users u "
+ "WHERE room_id = ? AND m.user_id = u.user_id "
+ "ORDER BY created_on DESC "
+ "LIMIT 20")]
+ (do-select [query room-id])))
+
+(defn fetch-messages-by-nick [nick]
+ (let [query (str "SELECT m.content, m.created_on, u.nick, u.avatar "
+ "FROM messages m, users u "
+ "WHERE m.user_id = u.user_id AND u.nick = ? "
+ "ORDER BY created_on DESC "
+ "LIMIT 20")]
+ (do-select [query nick])))
+
+;; Templates
+
+(defn set-user-attributes [st session]
+ (if (session :nick)
+ (.setAttribute st "user_nick" (session :nick))))
+
+(defn fetch-template [template-name session]
+ (let [st (.getInstanceOf template-group template-name)]
+ (and st
+ (do
+ (set-user-attributes st session)
+ st))))
+
+;; Landing
+
+(defn landing [session]
+ (let [nick (session :nick)]
+ (if nick
+ (redirect-to (str "/u/" nick))
+ (serve-file "static" "index.html"))))
+
+(defn login [session params]
+ (let [nick (params :nick)
+ hash (params :hash)
+ db-user (authorize-nick-hash nick hash)]
+ (if db-user
+ (dosync
+ (let [user-struct (struct user-struct (db-user :user_id) ; DB naming
+ nick (System/currentTimeMillis))]
+ (alter users assoc nick user-struct)
+ [(session-assoc :user-id (db-user :user_id)
+ :nick nick
+ :avatar (db-user :avatar))
+ (resp-success "OK")]))
+ (resp-error "BAD_LOGIN"))))
+
+(defn logout [session]
+ [(session-dissoc :nick :user-id)
+ (redirect-to "/")])
+
+;; Registration
(defn register [session params]
(let [nick (params :nick)
@@ -101,45 +187,80 @@
[nick hash email])
(resp-success "OK")))))
-(defn init [session]
+;; Profile
+
+(defn non-empty-string? [s]
+ (and s (> (count s) 0)))
+
+(defn profile [session profile-nick]
+ (let [user-info (fetch-nick profile-nick)]
+ (if user-info
+ (let [nick (session :nick)
+ is-home (and nick (= nick profile-nick))
+ has-avatar (non-empty-string? (user-info :avatar))
+ dumps (fetch-messages-by-nick profile-nick)
+ st (fetch-template "profile" session)]
+ (do
+ (.setAttribute st "is_home" is-home)
+ (doseq [a [:nick :avatar :contact :bio]]
+ (.setAttribute st (name a) (encode-html-entities (or (user-info a) nil))))
+ (.setAttribute st "dumps"
+ (to-array (map process-message-for-output dumps)))
+ (.toString st)))
+ (resp-error "NO_USER"))))
+
+(defn update-profile [session params]
+ (let [user-id (session :user-id)
+ attr (params :attr)
+ val (params :val)
+ attr-set #{"avatar" "contact" "bio"}]
+ (if (and user-id attr val
+ (contains? attr-set attr))
+ (do
+ (with-connection db
+ (update-values "users" ["user_id = ?" user-id] {attr val}))
+ (if (= attr "avatar")
+ [(session-assoc :avatar val) "OK"]
+ "OK"))
+ (resp-error "BAD_REQUEST"))))
+
+;; Chat
+
+(defn chat [session]
(let [now (System/currentTimeMillis)
+ nick (session :nick)
user-id (session :user-id)
- nick (session :nick)
- resp (updates)]
- (dosync
- (if (and user-id nick)
- (let [user-struct (struct user-struct user-id nick now)]
- (alter users assoc nick user-struct)
- (resp-success (merge resp {"nick" nick})))
- [(session-assoc :last-seen now)
- (resp-success resp)]))))
-
-(defn login [session params]
- (let [old-nick (session :nick)
- nick (params :nick)
- hash (params :hash)
- user-id (authorize-nick-hash nick hash)]
- (if user-id
+ st (fetch-template "chat" session)
+ message-list (to-array
+ (map process-message-for-output
+ (reverse (fetch-messages-by-room 1))))]
+ (if nick
(dosync
- (set-session {:user-id user-id :nick nick})
- (let [user-struct (struct user-struct user-id nick (System/currentTimeMillis))]
- (alter users dissoc old-nick)
- (alter users assoc nick user-struct)
- [(session-assoc :user-id user-id :nick nick)
- (resp-success "OK")]))
- (resp-error "BAD_LOGIN"))))
+ (let [user-struct (struct user-struct user-id nick now)]
+ (alter users assoc nick user-struct))))
+ (.setAttribute st "users" (map encode-html-entities
+ (sort (keys @users))))
+ (.setAttribute st "messages" message-list)
+ (.setAttribute st "json_user_nick" (if nick (json-str nick) "null"))
+ (if nick
+ (.toString st)
+ [(session-assoc :last-seen now)
+ (.toString st)])))
(defn refresh [session]
(let [nick (session :nick)
now (System/currentTimeMillis)]
- (dosync
- (if (contains? (ensure users) nick)
- (let [last-seen (get-in @users [nick :last-seen])]
- (alter users assoc-in [nick :last-seen] now)
- (resp-success (updates last-seen)))
- (let [last-seen (max (or (session :last-seen) 0) (- now (* 60 20)))]
- [(session-assoc :last-seen now)
- (updates last-seen)])))))
+ (if (or nick (session :last-seen))
+ (dosync
+ (if (contains? (ensure users) nick)
+ (let [last-seen (get-in @users [nick :last-seen])]
+ (alter users assoc-in [nick :last-seen] now)
+ (resp-success (updates last-seen)))
+ (let [last-seen (session :last-seen)]
+ [(session-assoc :last-seen now)
+ (updates last-seen)])))
+ ; TODO: session should store room-id of anon. users
+ (resp-error "NOT_IN_CHAT"))))
(defn msg-transaction [nick msg]
(dosync
@@ -157,8 +278,10 @@
(defn msg [session params]
(let [user-id (session :user-id)
nick (session :nick)
+ avatar (session :avatar)
content (params :content)
- msg (struct message-struct nick content (System/currentTimeMillis))]
+ now (new Date)
+ msg (struct message-struct nick avatar content now)]
(if (msg-transaction nick msg)
(do
(msg-db user-id 1 msg)
@@ -166,14 +289,17 @@
(resp-error "MUST_LOGIN"))))
(defroutes pichat
- (GET "/" (serve-file "static" "index.html"))
+ (GET "/" (landing session))
(GET "/static/*" (or (serve-file "static" (params :*))
:next))
(GET "/favicon.ico" (serve-file "static" "favicon.ico"))
+ (GET "/u/:nick" (profile session (-> request :route-params :nick)))
+ (GET "/update-profile" (update-profile session params))
+ (GET "/login" (login session params))
+ (GET "/logout" (logout session))
(GET "/register" (serve-file "static" "register.html"))
(GET "/submit-registration" (register session params))
- (GET "/login" (login session params))
- (GET "/init" (init session))
+ (GET "/chat" (chat session))
(GET "/refresh" (refresh session))
(GET "/msg" (msg session params))
(ANY "*" [404 "Page not found"]))
@@ -185,7 +311,7 @@
; Load messages from database
(dosync
- (ref-set messages (fetch-messages 1)))
+ (ref-set messages (fetch-messages-by-room 1)))
(run-server {:port 8080}
"/*" (servlet pichat))
diff --git a/static/home.js b/static/home.js
new file mode 100755
index 0000000..5857368
--- /dev/null
+++ b/static/home.js
@@ -0,0 +1,35 @@
+function ifEnter(fn) {
+ return function(e) {
+ if (e.keyCode == 13) { fn(); }
+ };
+}
+
+function initHome() {
+ $('#passwordInput').keyup(ifEnter(login));
+ $('#loginSubmit').click(login);
+}
+
+function login() {
+ var nick = $('#nickInput').val();
+ var password = $('#passwordInput').val();
+ var hash = hex_sha1(nick + '$' + password + '$dumpfm');
+
+ var onSuccess = function(json) {
+ location.href = "u/" + nick;
+ };
+
+ var onError = function(resp, textStatus, errorThrown) {
+ alert("Error logging in!");
+ };
+
+ $.ajax({
+ type: 'GET',
+ timeout: 5000,
+ url: 'login',
+ data: {'nick': nick, ts: '', 'hash': hash },
+ cache: false,
+ dataType: 'json',
+ success: onSuccess,
+ error: onError
+ });
+}; \ No newline at end of file
diff --git a/static/index.html b/static/index.html
index 94a8689..1daa65f 100755
--- a/static/index.html
+++ b/static/index.html
@@ -1,82 +1,99 @@
-<html>
- <head>
- <title>dump.fm</title>
- <link rel="stylesheet" type="text/css" href="static/reset.css">
- <link rel="stylesheet" type="text/css" href="static/pichat.css">
- <link rel="stylesheet" type="text/css" href="static/style.css">
- <link rel="shortcut icon" href="static/favicon.ico">
-
-
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <title>dump.fm</title>
+ <link rel="stylesheet" type="text/css" href="static/main2.css" />
+ <link rel="shortcut icon" href="static/favicon.ico" />
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<script type="text/javascript" src="static/sha1.js"></script>
- <script type="text/javascript" src="static/pichat.js"></script>
- <script>
- $(document).ready(function() {
- init();
- });
- </script>
-
- <script type="text/javascript">
- function showPopup(url) {
- newwindow=window.open(url,'name','height=600,width=820,top=30,left=10,resizable');
- if (window.focus) {newwindow.focus()}
- }
- </script>
- </head>
- <body>
-
- <div id="loginbar" style="display: none;">
- <span>Username:</span><input id="nickInput" type="input" />
- <br />
- <span>Password:</span><input id="passwordInput" type="password" />
- <br />
- <input name="Submit" type="submit" id="loginSubmit" value="login" />
- </div>
-
- <div id="content">
- <div id="logo"><img src="static/bubley.png" width="332" height="113"></div>
-
- <div id="chatbox">
-
- <div id="welcomebar" style="display: none">
- <span>Welcome, </span>
- <span id="nickspan"></span>
+ <script type="text/javascript" src="static/home.js"></script>
+ <script>
+ $(document).ready(initHome);
+ </script>
+ <style type="text/css">
+ <!--
+ a:link {
+ text-decoration: none;
+
+ }
+ a:visited {
+ text-decoration: none;
+ border: 0px;
+ }
+ a:hover {
+ text-decoration: none;
+ border: 0px;
+ }
+ a:active {
+ text-decoration: none;
+ border: 0px;
+ }
+ img{border: 0px;}
+ #apDiv7 {
+ position:absolute;
+ left:151px;
+ top:-106px;
+ width:351px;
+ height:66px;
+ z-index:4;
+ font-weight: bold;
+ line-height: 18px;
+ font-size: 14px;
+ }
+ #apDiv8 {
+ position:absolute;
+ left:964px;
+ top:131px;
+ width:416px;
+ height:140px;
+ z-index:4;
+ }
+ -->
+ </style>
+ </head>
+ <body>
+ <div id="header">
+ <div id="apDiv2">
+ <div align="center">
+ <img src="static/dfm.png" width="384" height="136" /><br />
+ <div id="about">
+ <p align="left"><a href="getstarted">
+ <img src="static/getstarted.gif" width="262" height="94" /></a></p>
+ <p align="left"><br />
+ </p>
+ </div>
+ <div id="txt">
+ <div align="right">Talk with pictures.</div>
+ </div>
+ <div id="signin">
+ <div align="right">
+ <div id="registerbox">
+ <h1>Sign In!<br />
+ <span>Nickname</span>
+ <input type="text" id="nickInput" size="17" />
+ <br />
+ <span>Password</span>
+ <input type="password" id="passwordInput" size="17" />
+ <br />
+ <input type="submit" id="loginSubmit" value="YEAH!!!" />
+ </h1>
+ </div>
+ </div>
+ </div>
+ <div class="info" id="info">
+ <div align="center" class="info"> © 2009 dump.fm
+ * About Us
+ * Contact
+ * Blog
+ * Goodies
+ * Help
+ * Terms
+ * Privacy
+ </div>
</div>
- <div id="messagetabs">
- <div align="right"><a href="log.h"><img src="static/log4.png" width="97" height="39"></a></div>
</div>
- <div id="rapper">
- <div id="userListicon">
-
- <div align="left">
- <p>UsrLst</p>
- </div>
- </div>
- <div id="popout">
- <div align="right"><a href="http://dump.fm"onClick='showPopup(this.href);return(false);'>popout room</a></div>
- </div>
- <div id="userList"></div>
-
- <div id="messagePane">
-
- <div id="messageList"></div>
- <div id="msgInputDiv">
- <input id="msgInput" type="input" disabled="disabled" />
- <input id="msgSubmit" type="submit" value="Send Image URL"
- disabled="disabled" />
- </div>
- </div>
- </div>
- </div>
- </div>
-
- <div id="lillogo" align="center">
- <div align="center">
- <div id="binfo">
- <p>dump.fm lets you talk with pictures. beta 0.0127! <img src="static/lillogo.png" width="16" height="16"></p>
- </div>
- </div>
- <div id="plane"></div>
- </div>
- </body>
-</html>
+ </div></div>
+ <br />
+ </body>
+</html>
diff --git a/static/jquery.editinplace.1.0.1.packed.js b/static/jquery.editinplace.1.0.1.packed.js
new file mode 100755
index 0000000..7b6fe5e
--- /dev/null
+++ b/static/jquery.editinplace.1.0.1.packed.js
@@ -0,0 +1,287 @@
+/*
+ * Another In Place Editor - a jQuery edit in place plugin
+ *
+ * Copyright (c) 2009 Dave Hauenstein
+ *
+ * License:
+ * This source file is subject to the BSD license bundled with this package.
+ * Available online: {@link http://www.opensource.org/licenses/bsd-license.php}
+ * If you did not receive a copy of the license, and are unable to obtain it,
+ * email davehauenstein@gmail.com,
+ * and I will send you a copy.
+ *
+ * Project home:
+ * http://code.google.com/p/jquery-in-place-editor/
+ *
+ */
+
+/*
+ * Version 1.0.2
+ *
+ * bg_out (string) default: transparent hex code of background color on restore from hover
+ * bg_over (string) default: #ffc hex code of background color on hover
+ * callback (function) default: null function to be called when editing is complete; cancels ajax submission to the url param
+ * cancel_button (string) default: <input type=”submit” class=”inplace_cancel” value=”Cancel”/> image button tag to use as “Cancel” button
+ * default_text (string) default: “(Click here to add text)” text to show up if the element that has this functionality is empty
+ * element_id (string) default: element_id name of parameter holding element_id
+ * error (function) this function gets called if server responds with an error
+ * field_type (string) “text”, “textarea”, or “select”; default: “text” The type of form field that will appear on instantiation
+ * on_blur (string) “save” or null; default: “save” what to do on blur; will be overridden if $param show_buttons is true
+ * original_html (string) default: original_html name of parameter holding original_html
+ * params (string) example: first_name=dave&last_name=hauenstein paramters sent via the post request to the server
+ * save_button (string) default: <input type=”submit” class=”inplace_save” value=”Save”/> image button tag to use as “Save” button
+ * saving_image (string) default: uses saving text specify an image location instead of text while server is saving
+ * saving_text (string) default: “Saving…” text to be used when server is saving information
+ * select_options (string) comma delimited list of options if field_type is set to select
+ * select_text (string)default text to show up in select box
+ * show_buttons (boolean) default: false will show the buttons: cancel or save; will automatically cancel out the onBlur functionality
+ * success (function) default: null this function gets called if server responds with a success
+ * textarea_cols (integer) default: 25 set cols attribute of textarea, if field_type is set to textarea
+ * textarea_rows (integer) default: 10 set rows attribute of textarea, if field_type is set to textarea
+ * update_value (string) default: update_value name of parameter holding update_value
+ * url (string) POST URL to send edited content
+ * value_required (string) default: false if set to true, the element will not be saved unless a value is entered
+ *
+ */
+
+jQuery.fn.editInPlace = function(options) {
+
+ /* DEFINE THE DEFAULT SETTINGS, SWITCH THEM WITH THE OPTIONS USER PROVIDES */
+ var settings = {
+ url: "",
+ params: "",
+ field_type: "text",
+ select_options: "",
+ textarea_cols: "25",
+ textarea_rows: "10",
+ bg_over: "#ffc",
+ bg_out: "transparent",
+ saving_text: "Saving...",
+ saving_image: "",
+ default_text: "(Click here to add text)",
+ select_text: "Choose new value",
+ value_required: null,
+ element_id: "element_id",
+ update_value: "update_value",
+ original_html: "original_html",
+ save_button: '<button class="inplace_save">Save</button>',
+ cancel_button: '<button class="inplace_cancel">Cancel</button>',
+ show_buttons: false,
+ on_blur: "save",
+ callback: null,
+ callbackShowErrors: true,
+ success: null,
+ error: function(request){
+ alert("Failed to save value: " + request.responseText || 'Unspecified Error');
+ }
+ };
+
+ if(options) {
+ jQuery.extend(settings, options);
+ }
+
+ /* preload the loading icon if it exists */
+ if(settings.saving_image != ""){
+ var loading_image = new Image();
+ loading_image.src = settings.saving_image;
+ }
+
+ /* THIS FUNCTION WILL TRIM WHITESPACE FROM BEFORE/AFTER A STRING */
+ String.prototype.trim = function() {
+ return this.replace(/^\s+/, '')
+ .replace(/\s+$/, '');
+ };
+
+ /* THIS FUNCTION WILL ESCAPE ANY HTML ENTITIES SO "Quoted Values" work */
+ String.prototype.escape_html = function() {
+ return this.replace(/&/g, "&amp;")
+ .replace(/</g, "&lt;")
+ .replace(/>/g, "&gt;")
+ .replace(/"/g, "&quot;");
+ };
+
+ /* CREATE THE INPLACE EDITOR */
+ return this.each(function(){
+
+ if(jQuery(this).html() == "") jQuery(this).html(settings.default_text);
+
+ var editing = false;
+
+ //save the original element - for change of scope
+ var original_element = jQuery(this);
+
+ var click_count = 0;
+
+ jQuery(this)
+
+ .mouseover(function(){
+ jQuery(this).css("background", settings.bg_over);
+ })
+
+ .mouseout(function(){
+ jQuery(this).css("background", settings.bg_out);
+ })
+
+ .click(function(){
+ click_count++;
+
+ if(!editing)
+ {
+ editing = true;
+
+ //save original text - for cancellation functionality
+ var original_html = jQuery(this).html();
+ var buttons_code = (settings.show_buttons) ? settings.save_button + ' ' + settings.cancel_button : '';
+
+ //if html is our default text, clear it out to prevent saving accidentally
+ if (original_html == settings.default_text) jQuery(this).html('');
+
+ if (settings.field_type == "textarea")
+ {
+ var use_field_type = '<textarea name="inplace_value" class="inplace_field" rows="' + settings.textarea_rows + '" cols="' + settings.textarea_cols + '">' + jQuery(this).text().trim().escape_html() + '</textarea>';
+ }
+ else if(settings.field_type == "text")
+ {
+ var use_field_type = '<input type="text" name="inplace_value" class="inplace_field" value="' +
+ jQuery(this).text().trim().escape_html() + '" />';
+ }
+ else if(settings.field_type == "select")
+ {
+ var optionsArray = settings.select_options.split(',');
+ var use_field_type = '<select name="inplace_value" class="inplace_field"><option value="">' + settings.select_text + '</option>';
+ for(var i=0; i<optionsArray.length; i++){
+ var optionsValuesArray = optionsArray[i].split(':');
+ var use_value = optionsValuesArray[1] || optionsValuesArray[0];
+ var selected = use_value == original_html ? 'selected="selected" ' : '';
+ use_field_type += '<option ' + selected + 'value="' + use_value.trim().escape_html() + '">' + optionsValuesArray[0].trim().escape_html() + '</option>';
+ }
+ use_field_type += '</select>';
+ }
+
+ /* insert the new in place form after the element they click, then empty out the original element */
+ jQuery(this).html('<form class="inplace_form" style="display: inline; margin: 0; padding: 0;">' + use_field_type + ' ' + buttons_code + '</form>');
+
+ }/* END- if(!editing) -END */
+
+ if(click_count == 1)
+ {
+ function cancelAction()
+ {
+ editing = false;
+ click_count = 0;
+
+ /* put the original background color in */
+ original_element.css("background", settings.bg_out);
+
+ /* put back the original text */
+ original_element.html(original_html);
+
+ return false;
+ }
+
+ function saveAction()
+ {
+ /* put the original background color in */
+ original_element.css("background", settings.bg_out);
+
+ var this_elem = jQuery(this);
+
+ var new_html = (this_elem.is('form')) ? this_elem.children(0).val() : this_elem.parent().children(0).val();
+
+ /* set saving message */
+ if(settings.saving_image != ""){
+ var saving_message = '<img src="' + settings.saving_image + '" alt="Saving..." />';
+ } else {
+ var saving_message = settings.saving_text;
+ }
+
+ /* place the saving text/image in the original element */
+ original_element.html(saving_message);
+
+ if(settings.params != ""){
+ settings.params = "&" + settings.params;
+ }
+
+ if(settings.callback) {
+ html = settings.callback(original_element.attr("id"), new_html, original_html, settings.params);
+ editing = false;
+ click_count = 0;
+ if (html == "") {
+ original_element.html(settings.default_text);
+ } else if(html) {
+ /* put the newly updated info into the original element */
+ original_element.html(html || new_html);
+ } else {
+ /* failure; put original back */
+ if(settings.callbackShowErrors)
+ {
+ alert("Failed to save value: " + new_html);
+ }
+ original_element.html(original_html);
+ }
+ } else if (settings.value_required && (new_html == "" || new_html == undefined)) {
+ editing = false;
+ click_count = 0;
+ original_element.html(original_html);
+ alert("Error: You must enter a value to save this field");
+ } else {
+ jQuery.ajax({
+ url: settings.url,
+ type: "POST",
+ data: settings.update_value + '=' + new_html + '&' + settings.element_id + '=' + original_element.attr("id") + settings.params + '&' + settings.original_html + '=' + original_html,
+ dataType: "html",
+ complete: function(request){
+ editing = false;
+ click_count = 0;
+ },
+ success: function(html){
+ /* if the text returned by the server is empty, */
+ /* put a marker as text in the original element */
+ var new_text = html || settings.default_text;
+
+ /* put the newly updated info into the original element */
+ original_element.html(new_text);
+ if (settings.success) settings.success(html, original_element);
+ },
+ error: function(request) {
+ original_element.html(original_html);
+ if (settings.error) settings.error(request, original_element);
+ }
+ });
+ }
+
+ return false;
+ }
+
+ /* set the focus to the new input element */
+ original_element.children("form").children(".inplace_field").focus().select();
+
+ /* CLICK CANCEL BUTTON functionality */
+ original_element.children("form").children(".inplace_cancel").click(cancelAction);
+
+ /* CLICK SAVE BUTTON functionality */
+ original_element.children("form").children(".inplace_save").click(saveAction);
+
+ /* if cancel/save buttons should be shown, cancel blur functionality */
+ if(!settings.show_buttons){
+ /* if on_blur is set to save, set the save funcion */
+ if(settings.on_blur == "save")
+ original_element.children("form").children(".inplace_field").blur(saveAction);
+ /* if on_blur is set to cancel, set the cancel funcion */
+ else
+ original_element.children("form").children(".inplace_field").blur(cancelAction);
+ }
+
+ /* hit esc key */
+ $(document).keyup(function(event){
+ if (event.keyCode == 27) {
+ cancelAction();
+ }
+ });
+
+ original_element.children("form").submit(saveAction);
+
+ }/* END- if(click_count == 1) -END */
+ });
+ });
+}; \ No newline at end of file
diff --git a/static/main.css b/static/main.css
index 22acda4..b3eef67 100644
--- a/static/main.css
+++ b/static/main.css
@@ -24,9 +24,7 @@
position:fixed;
top:150px;
}
-#infopage {
-
-
+#infopage {
margin: 0px auto -1px auto;
height: 14px;
color: #999;
@@ -52,10 +50,8 @@
left:0px;
width:410px;
height:281px;
- background-image: url(logbg.pngg);
- z-index:1;
-
-
+ background-image: url(logbg.png);
+ z-index:1;
}
#about {
position:absolute;
diff --git a/static/pichat.css b/static/pichat.css
index 584189e..fa20a36 100755
--- a/static/pichat.css
+++ b/static/pichat.css
@@ -1,189 +1,150 @@
/* pichat.css */
-html, body {
- padding: 1em;
+#header {
+ z-index: 100;
}
#content{
- position:fixed;
- z-index:3;
- overflow:auto;
-
+ z-index: 3;
+ overflow: auto;
}
#chatbox {
position: fixed;
-top:48px;
-
-
+ top:48px;
}
+
#rapper {
-top:0px;
-
+ top: 0px;
}
-h1,h2{
- font: 100% 'helvetica neue',sans-serif;
- letter-spacing:10px;
- text-transform:uppercase;
- font-family: Arial, Helvetica, sans-serif;
- font-size: 14px;
- left:2px;
- top:5px;
- position: fixed;
+
+h1,h2 {
+ font: 100% 'helvetica neue',sans-serif;
+ letter-spacing:10px;
+ text-transform:uppercase;
+ font-family: Arial, Helvetica, sans-serif;
+ font-size: 14px;
+ left:2px;
+ top:5px;
+ position: fixed;
}
+
#messagePane {
- border: 2px solid #15fff3;
- height: 70%;
- padding: 5px;
- position: fixed;
- width: 80%;
- max-width:1500px;
- background-color:#FFF;
- left:35px;
- top:80px;
- float: left;
- z-index:5;
-}
-#logo {
- position:fixed;
- left:18px;
- top:3px;
- z-index:7;
-}
-#welcomebar {
- font: 100% 'helvetica neue',sans-serif;
- letter-spacing:1px;
- top:20px;
- font-family: Arial, Helvetica, sans-serif;
- font-size: 9px;
- left:350px;
- top:67px;
- position: fixed;
+ border: 2px solid #15fff3;
+ height: 70%;
+ padding: 5px;
+ position: fixed;
+ width: 80%;
+ max-width:1500px;
+ background-color:#FFF;
+ left:35px;
+ top:80px;
+ float: left;
+ z-index:5;
}
+
#messageList {
height: 100%;
width: 100%;
- position:inherit
+ position:inherit
overflow-y: auto;
overflow-x: hidden;
- border-bottom: 1px solid grey;
}
#messagetabs {
- height: 40px;
- padding: 5px;
- position: fixed;
- width: 80%;
- max-width:1500px;
- overflow-y: hidden;
- overflow-x: hidden;
- top:48px;
- left:0px;
- z-index:1;
+ height: 40px;
+ padding: 5px;
+ position: fixed;
+ width: 80%;
+ max-width:1500px;
+ overflow-y: hidden;
+ overflow-x: hidden;
+ top:48px;
+ left:0px;
+ z-index:1;
}
+
#msgInputDiv {
height: 15px;
- position:relative;
- min-width:500px;
+ position:relative;
+ min-width:500px;
}
#msgInput {
width: 80%;
margin-right: 5px;
margin-top: 15px;
- position:relative;
- min-width:500px;
+ position:relative;
+ min-width:500px;
}
#msgSubmit {
width: 107px;
- position:relative;
-
+ position:relative;
}
.msgDiv img{
+ max-width:650px;
+ width: expression(this.width > 650 ? 650: true);
+ max-height:400px;
+ height: expression(this.width > 400 ? 400: true);
+ max-width:400px;
+}
- max-width:650px;
-width: expression(this.width > 650 ? 650: true);max-height:400px;
-height: expression(this.width > 400 ? 400: true);max-width:400px;}
+.oldmsg {
+ color: #666;
+}
#userList {
- overflow: auto;
- border: 1px solid gray;
- height: 70%;
- margin: 0px;
- position: fixed;
- padding: 5px;
- top:80px;
- width: 7%;
- float:right;
- right: 35px;
- font-family: Arial, Helvetica, sans-serif;
- font-size: 12px;
- text-transform: uppercase;
- min-width:72px;
- line-height:13px;
- background-color:#FFF;
-
- z-index:4;
+ overflow: auto;
+ border: 1px solid gray;
+ height: 70%;
+ margin: 0px;
+ position: fixed;
+ padding: 5px;
+ top:80px;
+ width: 7%;
+ float:right;
+ right: 35px;
+ font-family: Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ text-transform: uppercase;
+ min-width:72px;
+ line-height:13px;
+ background-color:#FFF;
+ z-index:4;
}
#userListicon {
- overflow: auto;
- height: 70%;
- margin: 0px;
- position: fixed;
- padding: 5px;
- top:48px;
- width: 7%;
- float:right;
- right: 35px;
- font-family: Arial, Helvetica, sans-serif;
- font-size: 12px;
- text-transform: uppercase;
- min-width:72px;
- line-height:13px;
- z-index:1;
- font-weight: bold;
- font-family: Arial, Helvetica, sans-serif;
- color: #666;
- text-transform:none;
-
+ overflow: auto;
+ height: 70%;
+ margin: 0px;
+ position: fixed;
+ padding: 5px;
+ top:48px;
+ width: 7%;
+ float:right;
+ right: 35px;
+ font-family: Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ text-transform: uppercase;
+ min-width:72px;
+ line-height:13px;
+ z-index:1;
+ font-weight: bold;
+ font-family: Arial, Helvetica, sans-serif;
+ color: #666;
+ text-transform:none;
+
}
#lillogo {
- bottom:0px;
- position:absolute;
- padding:1px;
- right:7px;
- line-height: 12px;
+ bottom:0px;
+ position:absolute;
+ padding:1px;
+ right:7px;
+ line-height: 12px;
}
#binfo {
- font-family: Arial, Helvetica, sans-serif;
- font-size: 11px;
- bottom:2px;
-
-}
-#popout {
- top:1px;
- position:fixed;
- padding:1px;
- right:0px;
- float:right;
- line-height: 12px;
- font-size:10px;
- text-align:right;
-}
-
-#loginbar {
- background: white;
- padding: 5px;
- position: absolute;
- bottom: 40px;
- left: 40px;
- line-height: 8px;
- z-index: 5;
- border: 1px solid #15fff3;
-}
-
-#loginsubmit {
- float: right;
+ font-family: Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ bottom:2px;
} \ No newline at end of file
diff --git a/static/pichat.js b/static/pichat.js
index 56dbd36..fe5beaa 100755
--- a/static/pichat.js
+++ b/static/pichat.js
@@ -1,7 +1,3 @@
-// pichat.js
-
-var Nick = null;
-
function handleMsgError(resp) {
var respText = resp.responseText ? resp.responseText.trim() : false;
if (respText == 'UNKNOWN_USER') {
@@ -14,11 +10,8 @@ function handleMsgError(resp) {
}
function escapeHtml(txt) {
- if (!txt) {
- return ""
- } else {
- return $("<span>").text(txt).html();
- }
+ if (!txt) { return "" }
+ else { return $("<span>").text(txt).html(); }
}
function buildUserDiv(user) {
@@ -28,52 +21,40 @@ function buildUserDiv(user) {
// http://stackoverflow.com/questions/37684/replace-url-with-html-links-javascript
function linkify(text) {
var exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gi;
- return text.replace(exp,"<a href='$1'>$1</a>");
+ return text.replace(exp,"<a href='$1'>$1</a>");
}
// http://snippets.dzone.com/posts/show/6995
var URLRegex = /((http\:\/\/|https\:\/\/|ftp\:\/\/)|(www\.))+(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/i;
var PicRegex = /\.(jpg|jpeg|png|gif|bmp)$/i;
+function buildMsgContent(content) {
+ var match = URLRegex.exec(content)
+ if (match && PicRegex.test(match[0])) {
+ return '<a href="' + match[0] + '" target="_blank">'
+ + '<img src="'+ match[0] + '" /></a>';
+ } else {
+ return linkify(escapeHtml(content));
+ }
+}
+
function buildMessageDiv(msg) {
- function buildContent(content) {
- var match = URLRegex.exec(content)
- if (match && PicRegex.test(match[0])) {
- return '<a href="' + match[0] + '" target="_blank">'
- + '<img src="'+ match[0] + '" /></a>';
- } else {
- return linkify(escapeHtml(msg.content));
- }
- }
return '<div class="msgDiv"><b>' + escapeHtml(msg.nick) + ': </b>'
- + buildContent(msg.content) + '</div>';
-}
-
-function setNick(nick) {
- Nick = nick;
- $('#nickspan').text(nick);
- $('#welcomebar').show();
- $('#msgInput, #msgSubmit').removeAttr('disabled');
- $('#msgInput').keyup(ifEnter(submitMessage));
- $('#msgSubmit').click(submitMessage);
+ + buildMsgContent(msg.content) + '</div>';
}
function submitMessage() {
var content = $('#msgInput').val();
- var msg = { 'nick': Nick, 'content': content, 'timestamp': new Date() };
if (content == '') { return; }
+
+ $('#msgInput, #msgSubmit').attr('disabled', 'disabled');
+ var onSuccess = function(json) {
+ $('#msgInput, #msgSubmit').removeAttr('disabled');
+ $('#msgInput').val('');
+ };
- var shouldScroll = isScrolledToBottom($('#messageList')[0]);
-
- $('#messageList').append($(buildMessageDiv(msg)));
- $('#msgInput').val('');
-
- if (shouldScroll) {
- scrollToBottom($('#messageList')[0]);
- }
-
- var onSuccess = function() {};
var onError = function(resp, textStatus, errorThrown) {
+ $('#msgInput, #msgSubmit').removeAttr('disabled');
handleMsgError(resp);
};
@@ -81,12 +62,14 @@ function submitMessage() {
type: 'GET',
timeout: 5000,
url: 'msg',
- data: {'content': content },
+ data: { 'content': content },
cache: false,
dataType: 'json',
success: onSuccess,
- error: onError
+ error: onError
});
+
+ updateUI([{ 'nick': Nick || "me", 'content': content}], null);
}
function ifEnter(fn) {
@@ -95,32 +78,6 @@ function ifEnter(fn) {
};
}
-function login() {
- var nick = $('#nickInput').val();
- var password = $('#passwordInput').val();
- var hash = hex_sha1(nick + '$' + password + '$dumpfm');
-
- var onSuccess = function(json) {
- $('#loginbar').hide();
- setNick(nick);
- };
-
- var onError = function(resp, textStatus, errorThrown) {
- alert("Error logging in!");
- };
-
- $.ajax({
- type: 'GET',
- timeout: 5000,
- url: 'login',
- data: {'nick': nick, 'hash': hash },
- cache: false,
- dataType: 'json',
- success: onSuccess,
- error: onError
- });
-};
-
function isScrolledToBottom(div) {
return Math.abs(div.scrollTop - (div.scrollHeight - div.offsetHeight)) <= 3;
}
@@ -129,32 +86,33 @@ function scrollToBottom(div) {
div.scrollTop = div.scrollHeight;
}
-function updateUI(json, initialUpdate) {
- if (json.messages.length > 0) {
- if (initialUpdate) {
- var messages = json.messages;
- } else {
- // Our own messages have already been displayed.
- var filterFunc = function(m) { return m.nick != Nick };
- var messages = $.grep(json.messages, filterFunc);
- }
-
- var msgStr = $.map(messages,
- buildMessageDiv).join('');
+// Give images time to start loading before scrolling.
+// Needed until server knows size of images.
+function delayedScrollToBottom(delay) {
+ setTimeout(scrollToBottom, delay, $('#messageList')[0]);
+}
+
+function updateUI(msgs, users) {
+ if (msgs && msgs.length > 0) {
+ var msgStr = $.map(msgs, buildMessageDiv).join('');
var wasScrolledToBottom = isScrolledToBottom($('#messageList')[0]);
$('#messageList').append(msgStr);
- if (initialUpdate || wasScrolledToBottom) {
- // Delay scrolling by .5 seconds so images can start loading.
- setTimeout(scrollToBottom, 500, $('#messageList')[0]);
+ if (wasScrolledToBottom) {
+ delayedScrollToBottom(500);
}
}
- $("#userList").html($.map(json.users, buildUserDiv).join(''));
+ if (users && users.length > 0) {
+ $("#userList").html($.map(users, buildUserDiv).join(''));
+ }
}
function refresh() {
var onSuccess = function(json) {
- updateUI(json, false);
+ var messages = $.grep(
+ json.messages,
+ function(m) { return m.nick != Nick });
+ updateUI(messages, json.users);
setTimeout(refresh, 1000);
};
var onError = function(resp, textStatus, errorThrown) {
@@ -168,35 +126,51 @@ function refresh() {
cache: false,
dataType: 'json',
success: onSuccess,
- error: onError
+ error: onError
});
}
-function init() {
- var onSuccess = function(json) {
- if (json.nick) {
- setNick(json.nick);
- } else {
- $('#loginbar').show();
- $('#passwordInput').keyup(ifEnter(login));
- $('#loginSubmit').click(login);
+function initChat() {
+ $('.msgDiv .content').each(function() {
+ var t = $(this);
+ t.html(buildMsgContent(t.text()));
+ });
+
+ $('#msgInput').keyup(ifEnter(submitMessage));
+ $('msgSubmit').click(submitMessage);
+
+ delayedScrollToBottom(500);
+ setTimeout(refresh, 1000);
+}
+
+function initProfile() {
+ var onSubmit = function(original_element, edit, old) {
+ if (edit == old) { return old };
+ // Prevent entering script tags. TODO: investigate better scheme.
+ if (original_element == 'avatar' && edit.indexOf("<") != -1) {
+ return old;
}
- updateUI(json, true);
- setTimeout(refresh, 1000);
- };
- var onError = function(resp, textStatus, errorThrown) {
- alert("Error initializing!");
+ $.ajax({
+ type: "GET",
+ timeout: 5000,
+ url: "/update-profile",
+ data: { 'attr': original_element, 'val': edit }
+ });
+ if (original_element == 'avatar') {
+ var s = '<img id="avatarPic" src="' + edit + '" width="150" />';
+ $('#avatarPic').replaceWith(s);
+ }
+ return escapeHtml(edit);
};
- $.ajax({
- type: 'GET',
- timeout: 5000,
- url: 'init',
- cache: false,
- dataType: 'json',
- success: onSuccess,
- error: onError
- });
+ var opt = { 'default_text': 'Enter here!',
+ 'callback': onSubmit,
+ 'field_type': 'text',
+ 'callbackShowErrors': false };
+ $('#avatar.editable').editInPlace(opt);
-}
+ opt['field_type'] = 'textarea';
+ $('#contact.editable, #bio.editable').editInPlace(opt);
+
+};
diff --git a/static/style.css b/static/style.css
index 56606ae..4524037 100755
--- a/static/style.css
+++ b/static/style.css
@@ -147,18 +147,7 @@ body {
background-repeat: repeat-x;
background-attachment:fixed;
}
-#plane {
- position:fixed;
- top:9px;
- right:0px;
- width:100%;
- height:100%;
- z-index:1;
- background-image: url(cloudbg.png);
- background-repeat: no-repeat;
- background-position:center;
-}
.form { font-size: 14px; color="#990000"; }
.info {
font-size: 10px;
@@ -169,18 +158,7 @@ body {
bottom: 0px;
color: #999;
}
-#plane {
- position:fixed;
- top:9px;
- right:0px;
- width:100%;
- height:100%;
- z-index:1;
- background-image: url("./cloudbg1.png");
- background-repeat: no-repeat;
- background-position:center;
-}
.extruder{
position:fixed;
diff --git a/template/chat.st b/template/chat.st
new file mode 100755
index 0000000..eb4ed1c
--- /dev/null
+++ b/template/chat.st
@@ -0,0 +1,68 @@
+<html>
+ <head>
+ <title>dump.fm</title>
+ <link rel="stylesheet" type="text/css" href="static/reset.css">
+ <link rel="stylesheet" type="text/css" href="static/pichat.css">
+ <link rel="stylesheet" type="text/css" href="static/style.css">
+ <link rel="shortcut icon" href="static/favicon.ico">
+ <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
+ <script type="text/javascript" src="static/sha1.js"></script>
+ <script type="text/javascript" src="static/pichat.js"></script>
+ <script>
+ jQuery(document).ready(initChat);
+ var Nick = $json_user_nick$;
+ </script>
+ </head>
+ <body>
+
+ <div style="z-index: 100;">
+ $header()$
+ </div>
+
+ <div id="content">
+ <div id="chatbox">
+ <div id="messagetabs">
+ <div align="right"><a href="log.h"><img src="static/log4.png" width="97" height="39"></a></div>
+ </div>
+ <div id="rapper">
+ <div id="userListicon">
+ <div align="left">
+ <p>UsrLst</p>
+ </div>
+ </div>
+ <div id="userList">
+ $users: { u |
+ <div>$u$</div>
+ }$
+ </div>
+
+ <div id="messagePane">
+ <div id="messageList">
+ $messages: { m |
+ <div class="msgDiv oldmsg"><b>$m.nick$: </b>
+ <span class="content">$m.content$<span></div>
+ }$
+ <hr />
+ </div>
+ $if(user_nick)$
+ <div id="msgInputDiv">
+ <input id="msgInput" type="input" />
+ <input id="msgSubmit" type="submit" value="Send Image URL"
+ />
+ </div>
+ $endif$
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div id="lillogo" align="center">
+ <div align="center">
+ <div id="binfo">
+ <p>dump.fm lets you talk with pictures. beta 0.0127! <img src="static/lillogo.png" width="16" height="16"></p>
+ </div>
+ </div>
+ </div>
+ $footer()$
+ </body>
+</html>
diff --git a/template/footer.st b/template/footer.st
new file mode 100755
index 0000000..0535ed6
--- /dev/null
+++ b/template/footer.st
@@ -0,0 +1,3 @@
+<div id="footer" style="text-align: center">
+ <span>@2009 dump.fm</span>
+</div>
diff --git a/template/header.st b/template/header.st
new file mode 100755
index 0000000..4d45ffa
--- /dev/null
+++ b/template/header.st
@@ -0,0 +1,13 @@
+<div id="header">
+ <a href="/"><img src="/static/dddump.png" height="50" /></a>
+ <div style="float: right;">
+ $if(user_nick)$
+ <span>Hi, $user_nick$</span>&nbsp;&nbsp;&nbsp;
+ <a href="/u/$user_nick$">HOME</a> |
+ $else$ <a href="/">Login</a> |
+ $endif$
+ <a href="/chat">Room A</a>
+ $if(user_nick)$ | <a href="/logout">Logout</a>$endif$
+ </div>
+</div>
+<hr />
diff --git a/template/logged_dump.st b/template/logged_dump.st
new file mode 100755
index 0000000..3131aaf
--- /dev/null
+++ b/template/logged_dump.st
@@ -0,0 +1,6 @@
+<div class="logged-dump">
+ $if(dump.avatar)$<img class="dump-avatar" src="$dump.avatar$" width="75" height="75">$endif$
+ <div><b>$dump.nick$</b> -- $dump.created_on$</div>
+ <div>$dump.content$</div>
+ <hr />
+</div>
diff --git a/template/profile.st b/template/profile.st
new file mode 100755
index 0000000..034c648
--- /dev/null
+++ b/template/profile.st
@@ -0,0 +1,80 @@
+<html>
+ <head>
+ <title>$nick$ | PERSONAL DUMP LOG</title>
+ <link rel="shortcut icon" href="static/favicon.ico">
+ <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
+ <script type="text/javascript" src="/static/pichat.js"></script>
+ <script type="text/javascript" src="/static/jquery.editinplace.1.0.1.packed.js"></script>
+ <style type="text/css">
+ #profile {
+ float: right;
+ border: 1px solid black;
+ padding: 10px;
+ width: 25%;
+ }
+
+ .editable {
+ color: #0AA;
+ }
+ .editing {
+ color: #F0F;
+ }
+ div#avatar {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ img#avatarPic {
+ max-height:250px;
+ }
+
+ #logged-dump {
+ border: 1px solid dotted;
+ padding: 5px;
+ }
+
+ </style>
+ <script>
+ jQuery(document).ready(initProfile);
+ </script>
+ </head>
+ <body>
+ $header()$
+ <h1>$nick$ | PERSONAL DUMP LOG</h1>
+
+ <div id="profile">
+ <h2>$nick$</h2>
+
+ $if(avatar)$
+ <img id="avatarPic" src="$avatar$" width="150px"/>
+ $else$
+ <b id="avatarPic">No avatar</b>
+ $endif$
+
+ $if(is_home)$
+ <div id="avatar" class="editable">$avatar$</div>
+ $endif$
+
+ <h3>Contact:</h3>
+ <div id="contact" $if(is_home)$class="editable"$endif$>$contact$</div>
+ <br>
+
+ <h3>Bio:</h3>
+ <div id="bio" $if(is_home)$class="editable"$endif$>$bio$</div>
+ <br>
+ </div>
+
+ <div id="log">
+ <h2>Log</h2>
+
+ $if(dumps)$
+ $dumps:{ d | $logged_dump(dump=d)$ }$
+ $else$
+ <h3>No dumps yet!</h3>
+ $endif$
+
+ </div>
+
+ $footer()$
+ </body>
+</html>