diff options
| author | sostler <sbostler@gmail.com> | 2009-12-06 20:38:51 -0500 |
|---|---|---|
| committer | sostler <sbostler@gmail.com> | 2009-12-06 20:38:51 -0500 |
| commit | 631f492c328ee40414ee32f717215d3fdda6f55a (patch) | |
| tree | ad3c4e284b06c3ddc4fd2eb9ac6ea6280f7c07ac | |
| parent | 9dd2ee1b1eb63529f5694d2e400dd04cc1eb1663 (diff) | |
Profiles / templates
| -rwxr-xr-x | bin/repl.bat | 2 | ||||
| -rwxr-xr-x | bin/repl.sh | 2 | ||||
| -rwxr-xr-x | db/0-create.psql | 4 | ||||
| -rwxr-xr-x | lib/antlr-2.7.7.jar | bin | 0 -> 445288 bytes | |||
| -rwxr-xr-x | lib/stringtemplate-3.2.1.jar | bin | 0 -> 124378 bytes | |||
| -rw-r--r-- | src/site.clj | 256 | ||||
| -rwxr-xr-x | static/home.js | 35 | ||||
| -rwxr-xr-x | static/index.html | 173 | ||||
| -rwxr-xr-x | static/jquery.editinplace.1.0.1.packed.js | 287 | ||||
| -rw-r--r-- | static/main.css | 10 | ||||
| -rwxr-xr-x | static/pichat.css | 241 | ||||
| -rwxr-xr-x | static/pichat.js | 192 | ||||
| -rwxr-xr-x | static/style.css | 22 | ||||
| -rwxr-xr-x | template/chat.st | 68 | ||||
| -rwxr-xr-x | template/footer.st | 3 | ||||
| -rwxr-xr-x | template/header.st | 13 | ||||
| -rwxr-xr-x | template/logged_dump.st | 6 | ||||
| -rwxr-xr-x | template/profile.st | 80 |
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 Binary files differnew file mode 100755 index 0000000..5e5f14b --- /dev/null +++ b/lib/antlr-2.7.7.jar diff --git a/lib/stringtemplate-3.2.1.jar b/lib/stringtemplate-3.2.1.jar Binary files differnew file mode 100755 index 0000000..8a309cc --- /dev/null +++ b/lib/stringtemplate-3.2.1.jar 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] [["&" "&"] + ["'" "'"] + ["\"" """] + ["<" "<"] + [">" ">"]]] + (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, "&") + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/"/g, """); + }; + + /* 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> + <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> |
