Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
  • 17 commits
  • 11 files changed
  • 0 comments
  • 3 contributors
Sep 29, 2011
Alan Malloy amalloy Merge branch 'feature/followbox' into develop f0b263f
Alan Malloy amalloy Fix ranking 24b1716
Sep 30, 2011
Alan Malloy amalloy Version number de7dcac
Alan Malloy amalloy Merge branch 'release/1.4.0' into develop 5b86579
Alan Malloy amalloy Allow different behavior per hostname 4ef7b4e
Alan Malloy amalloy Version number b11820f
Alan Malloy amalloy Merge branch 'release/1.4.1' into develop 6f20d46
Alan Malloy amalloy Only use compojure 'site' for dynamic route; it's got cookies and stuff bf7e1f9
Alan Malloy amalloy Fix wrap-versioned-expiry.
It was never returning nil, even if it couldn't find the file.
7ccb3a2
Alan Malloy amalloy Version number 2580db5
Alan Malloy amalloy Merge branch 'release/1.4.2' into develop 611e296
Darren Austin Finished the work for adding the "following" checkboxes to the users …
…page. (Issue 117)

- In the case where the user has javascript disabled, we fall back to
just showing a "yes" in the following column instead of the checkbox.
- Optimized checking to see if a user was being followed by making the
following vector into a set.
- Hooked up the "Follow" buttons on the user profile page to use AJAX
instead of another page load (which also fixes the back button issue
with this).
54c9333
Alan Malloy amalloy Display tag instead of SHA on version page aa57d64
Alex McNamara amcnamara Pulled in small style fixes to user list 98c5e46
Darren Austin Switched the way we were detecting JS for the users page follow check…
…boxes
adbd030
Alex McNamara amcnamara Merge branch 'develop' of https://github.com/darrenaustin/4clojure in…
…to feature/style
5ad419a
Alex McNamara amcnamara Merge branch 'feature/style' into develop 5dabf99
3  config.clj
@@ -9,6 +9,9 @@
9 9 :advanced-user-count 50
10 10 :pass ""
11 11 :repo-url "https://github.com/4clojure/4clojure"
  12 + :hosts {;; :static "static.4clojure.com"
  13 + ;; :dynamic "www.4clojure.com"
  14 + :redirects ["4clojure.com"]}
12 15 :golfing-active true
13 16 :heartbeat nil ; set to, eg, [1 :hour] for periodic heap information on stdout
14 17 ;; this list is just for bootstrapping - the real DB is authoritative
2  project.clj
... ... @@ -1,4 +1,4 @@
1   -(defproject foreclojure "1.3.3"
  1 +(defproject foreclojure "1.4.2"
2 2 :description "4clojure - a website for lisp beginners"
3 3 :dependencies [[clojure "1.2.1"]
4 4 [clojure-contrib "1.2.0"]
33 resources/public/css/style.css
@@ -217,6 +217,30 @@ a.novisited {color: #00e;}
217 217 padding: 15px;
218 218 }
219 219
  220 +
  221 +/*
  222 + * By default only show the text label for the "Following" column on
  223 + * the users page. If they have javascript turned on (detected
  224 + * via. the js-enabled class set by our page's javascript) then turn
  225 + * off the text label and turn on the checkbox.
  226 + */
  227 +#user-table input.following {
  228 + display: none;
  229 +}
  230 +
  231 +#user-table span.following {
  232 + display: inline;
  233 +}
  234 +
  235 +#user-table.js-enabled input.following {
  236 + display: inline;
  237 +}
  238 +
  239 +#user-table.js-enabled span.following {
  240 + display: none;
  241 +}
  242 +
  243 +
220 244 span.error, div.error {
221 245 color: red;
222 246 }
@@ -254,6 +278,7 @@ div#sub-heading {
254 278 }
255 279
256 280 div#main {
  281 + width: 100%;
257 282 display: block;
258 283 float: left;
259 284 clear: both;
@@ -368,6 +393,14 @@ table.my-table td {
368 393 font-size: 12px;
369 394 }
370 395
  396 +tr.evenrow {
  397 + background-color: #E2E4FF;
  398 +}
  399 +
  400 +tr.oddrow {
  401 + background-color: white;
  402 +}
  403 +
371 404 td.oddrank {
372 405 background-color: #DDF !important;
373 406 }
26 resources/public/script/foreclojure.js
@@ -22,7 +22,26 @@ $(document).ready(function() {
22 22 $(this).parents("form").attr("action", "/problem/edit").submit();
23 23 });
24 24
25   - $("form input.following").live("click", function(e) {
  25 + $("button.user-follow-button").live("click", function(e) {
  26 + e.preventDefault();
  27 + var $form = $(this).parents("form");
  28 + var $button = $(this);
  29 + $.ajax({type: "POST",
  30 + url: "/rest" + $form.attr("action"),
  31 + dataType: "json",
  32 + success: function(data) {
  33 + if (data) {
  34 + $button.text(data["next-label"]);
  35 + $form.attr("action", data["next-action"]);
  36 + }
  37 + },
  38 + });
  39 + return false;
  40 + });
  41 +
  42 + $("#user-table").addClass("js-enabled");
  43 +
  44 + $("#user-table input.following").live("click", function(e) {
26 45 e.preventDefault();
27 46 var $checkbox = $(this)
28 47 var $form = $checkbox.parents("form")
@@ -58,7 +77,7 @@ jQuery.fn.dataTableExt.oSort['difficulty-desc'] = function(a, b) {
58 77 function configureDataTables(){
59 78
60 79 $('#problem-table').dataTable( {
61   - "iDisplayLength": 25,
  80 + "iDisplayLength": 100,
62 81 "aaSorting": [[5, "desc"], [1, "asc"], [4, "desc"]],
63 82 "aoColumns": [
64 83 {"sType": "string"},
@@ -88,7 +107,8 @@ function configureDataTables(){
88 107 {"sType": "numeric"},
89 108 {"sType": "string"},
90 109 {"sType": "numeric"},
91   - {"sType": "string"}
  110 + {"sType": "string"},
  111 + {"sType": "string"}
92 112 ]
93 113 } );
94 114 }
6 src/foreclojure/config.clj
@@ -9,3 +9,9 @@
9 9 ;; Defs both for convenience and compile-time verification of simple settings
10 10 (def repo-url (or (:repo-url config)
11 11 (throw (Exception. "config.clj needs a :repo-url key"))))
  12 +
  13 +(letfn [(host [key]
  14 + (get-in config [:hosts key]))]
  15 + (def static-host (host :static))
  16 + (def dynamic-host (host :dynamic))
  17 + (def redirect-hosts (host :redirects)))
71 src/foreclojure/core.clj
... ... @@ -1,14 +1,15 @@
1 1 (ns foreclojure.core
2 2 (:require [compojure.route :as route]
3 3 [compojure.handler :as handler]
  4 + [foreclojure.config :as config]
4 5 [sandbar.stateful-session :as session])
5   - (:use [compojure.core :only [defroutes GET]]
  6 + (:use [compojure.core :only [defroutes routes GET]]
6 7 [foreclojure.static :only [static-routes welcome-page]]
7 8 [foreclojure.problems :only [problems-routes]]
8 9 [foreclojure.login :only [login-routes]]
9 10 [foreclojure.register :only [register-routes]]
10 11 [foreclojure.golf :only [golf-routes]]
11   - [foreclojure.ring :only [resources wrap-strip-trailing-slash wrap-url-as-file wrap-versioned-expiry]]
  12 + [foreclojure.ring :only [resources wrap-strip-trailing-slash wrap-url-as-file wrap-versioned-expiry split-hosts wrap-404 wrap-debug]]
12 13 [foreclojure.users :only [users-routes]]
13 14 [foreclojure.config :only [config]]
14 15 [foreclojure.social :only [social-routes]]
@@ -25,31 +26,55 @@
25 26
26 27 (def *block-server* false)
27 28
28   -(defroutes main-routes
29   - (GET "/" [] (welcome-page))
30   - login-routes
31   - register-routes
32   - problems-routes
33   - users-routes
34   - static-routes
35   - social-routes
36   - version-routes
37   - graph-routes
38   - golf-routes
  29 +(defroutes resource-routes
39 30 (-> (resources "/*")
40 31 (wrap-url-as-file)
41 32 (wrap-file-info)
42   - (wrap-versioned-expiry))
43   - (route/not-found "Page not found"))
  33 + (wrap-versioned-expiry)))
44 34
45   -(def app (-> #'main-routes
46   - ((if (:wrap-reload config)
47   - #(wrap-reload % '(foreclojure.core))
48   - identity))
49   - session/wrap-stateful-session
50   - handler/site
51   - wrap-uri-binding
52   - wrap-strip-trailing-slash
  35 +(def dynamic-routes
  36 + (-> (routes (GET "/" [] (welcome-page))
  37 + login-routes
  38 + register-routes
  39 + problems-routes
  40 + users-routes
  41 + static-routes
  42 + social-routes
  43 + version-routes
  44 + graph-routes
  45 + golf-routes)
  46 + ((if (:wrap-reload config)
  47 + #(wrap-reload % '(foreclojure.core))
  48 + identity))
  49 + session/wrap-stateful-session
  50 + wrap-uri-binding
  51 + handler/site
  52 + wrap-strip-trailing-slash))
  53 +
  54 +(let [canonical-host (or config/dynamic-host "www.4clojure.com")]
  55 + (defn redirect-routes [request]
  56 + (let [{:keys [scheme uri]} request
  57 + proper-uri (str (name scheme)
  58 + "://"
  59 + canonical-host
  60 + uri)]
  61 + {:status 302
  62 + :headers {"Location" proper-uri}
  63 + :body (str "<a href='" proper-uri "'>"
  64 + proper-uri
  65 + "</a>")})))
  66 +
  67 +(def host-handlers (reduce into
  68 + {:default (routes dynamic-routes resource-routes)}
  69 + [(for [host config/redirect-hosts]
  70 + [host redirect-routes])
  71 + (for [[host route] [[config/static-host resource-routes]
  72 + [config/dynamic-host dynamic-routes]]
  73 + :when host]
  74 + [host (wrap-debug route)])]))
  75 +
  76 +(def app (-> (split-hosts host-handlers)
  77 + wrap-404
53 78 wrap-gzip))
54 79
55 80 (defn register-heartbeat []
29 src/foreclojure/ring.clj
... ... @@ -1,9 +1,11 @@
1 1 (ns foreclojure.ring
2 2 (:require [clojure.java.io :as io]
3   - [clojure.string :as s])
  3 + [clojure.string :as s]
  4 + [compojure.route :as route])
4 5 (:import [java.net URL])
5 6 (:use [compojure.core :only [GET]]
6 7 [foreclojure.utils :only [strip-version-number]]
  8 + [useful.debug :only [?]]
7 9 [ring.util.response :only [response]]))
8 10
9 11 ;; copied from compojure.route, modified to use File instead of Stream
@@ -31,8 +33,23 @@
31 33
32 34 (defn wrap-versioned-expiry [handler]
33 35 (fn [request]
34   - (-> request
35   - (update-in [:uri] strip-version-number)
36   - (handler)
37   - (assoc-in [:headers "Cache-control"]
38   - "public, max-age=31536000"))))
  36 + (when-let [resp (handler
  37 + (update-in request [:uri] strip-version-number))]
  38 + (assoc-in resp [:headers "Cache-control"]
  39 + "public, max-age=31536000"))))
  40 +
  41 +(defn wrap-debug [handler]
  42 + (fn [request]
  43 + (? (handler (? request)))))
  44 +
  45 +(defn split-hosts [host-handlers]
  46 + (let [default (:default host-handlers)]
  47 + (fn [request]
  48 + (let [host (get-in request [:headers "host"])
  49 + handler (or (host-handlers host) default)]
  50 + (handler request)))))
  51 +
  52 +(defn wrap-404 [handler]
  53 + (fn [request]
  54 + (or (handler request)
  55 + (route/not-found "Page not found"))))
19 src/foreclojure/template.clj
@@ -3,7 +3,7 @@
3 3 (:use [hiccup.core :only [html]]
4 4 [hiccup.page-helpers :only [doctype javascript-tag link-to]]
5 5 [foreclojure.config :only [config repo-url]]
6   - [foreclojure.utils :only [css js page-attributes rendering-info login-url approver? can-submit?]]))
  6 + [foreclojure.utils :only [css js page-attributes rendering-info login-url approver? can-submit? static-url]]))
7 7
8 8 ;; Global wrapping template
9 9 (defn html-doc [body]
@@ -14,22 +14,23 @@
14 14 [:html
15 15 [:head
16 16 [:title (:title attrs)]
17   - [:link {:rel "alternate" :type "application/atom+xml" :title "Atom" :href "http://4clojure.com/problems/rss"}]
18   - [:link {:rel "shortcut icon" :href "/favicon2.ico"}]
  17 + [:link {:rel "alternate" :type "application/atom+xml" :title "Atom" :href "/problems/rss"}]
  18 + [:link {:rel "shortcut icon" :href (static-url "favicon2.ico")}]
19 19 [:style {:type "text/css"}
20 20 ".syntaxhighlighter { overflow-y: hidden !important; }"]
21   - (css "/css/style.css" "/css/demo_table.css" "/css/shCore.css" "/css/shThemeDefault.css")
22   - (js "/vendor/script/jquery-1.5.2.min.js" "/vendor/script/jquery.dataTables.min.js")
23   - (js "/script/foreclojure.js")
24   - (js "/vendor/script/xregexp.js" "/vendor/script/shCore.js" "/vendor/script/shBrushClojure.js")
25   - (js "/vendor/script/ace/ace.js" "/vendor/script/ace/mode-clojure.js")
  21 + (css "css/style.css" "css/demo_table.css" "css/shCore.css" "css/shThemeDefault.css")
  22 + (js "vendor/script/jquery-1.5.2.min.js" "vendor/script/jquery.dataTables.min.js")
  23 + (js "script/foreclojure.js")
  24 + (js "vendor/script/xregexp.js" "vendor/script/shCore.js" "vendor/script/shBrushClojure.js")
  25 + (js "vendor/script/ace/ace.js" "vendor/script/ace/mode-clojure.js")
26 26 [:script {:type "text/javascript"} "SyntaxHighlighter.all()"]]
27 27 [:body
28 28 (when (:fork-banner attrs)
29 29 [:div#github-banner [:a {:href repo-url
30 30 :alt "Fork 4Clojure on Github!"}]])
31 31 [:div#top
32   - (link-to "/" [:img#logo {:src "/images/4clj-logo.png" :alt "4clojure.com"}])]
  32 + (link-to "/" [:img#logo {:src (static-url "images/4clj-logo.png")
  33 + :alt "4clojure.com"}])]
33 34 [:div#content
34 35 [:div#menu
35 36 (for [[link text & [tabbed]]
56 src/foreclojure/users.clj
@@ -26,22 +26,27 @@
26 26 :only [:user :solved :contributor])))
27 27
28 28 (defn get-ranked-users []
29   - (let [users (get-users)]
30   - (mapcat
31   - (fn [rank tied-users]
32   - (for [user (sort-by :user tied-users)]
33   - (assoc user :rank (inc rank))))
34   - (range)
35   - (map second
36   - (sort-by #(-> % first -)
37   - (group-by #(count (or (:solved %) []))
38   - users))))))
  29 + (let [users (get-users)
  30 + tied-groups (map val
  31 + (sort-by #(-> % key -)
  32 + (group-by #(count (or (:solved %) []))
  33 + users)))]
  34 + (first
  35 + (reduce (fn [[user-list position rank] new-group]
  36 + [(into user-list
  37 + (for [user (sort-by :user new-group)]
  38 + (into user {:rank rank
  39 + :position position})))
  40 + (inc position)
  41 + (+ rank (count new-group))])
  42 + [[] 1 1]
  43 + tied-groups))))
39 44
40 45 (defn get-top-100-and-current-user [username]
41 46 (let [ranked-users (get-ranked-users)
42 47 this-user (first (filter (comp #{username} :user)
43 48 ranked-users))
44   - this-user-ranking (update-in this-user [:rank] #(str (or % "?") " out of " (count ranked-users)))]
  49 + this-user-ranking (update-in this-user [:rank] #(str (or % "?") " out of " (count ranked-users)))]
45 50 {:user-ranking this-user-ranking
46 51 :top-100 (take 100 ranked-users)}))
47 52
@@ -78,29 +83,28 @@
78 83
79 84 (defn following-checkbox [current-user-id following user-id user]
80 85 (when (and current-user-id (not= current-user-id user-id))
81   - (let [following? (some #{user-id} following)]
  86 + (let [following? (contains? following user-id)]
82 87 (form-to [:post (follow-url user (not following?))]
83   - [:input.following {:type "checkbox" :name "following"
84   - :checked following? :value following?}]))))
  88 + [:input.following {:type "checkbox" :checked following?}]
  89 + [:span.following (when following? "yes")]))))
85 90
86 91 (defn generate-user-list [user-set]
87   - (let [[user-id following]
88   - (if (session/session-get :user)
  92 + (let [[user-id following]
  93 + (when (session/session-get :user)
89 94 (with-user [{:keys [_id following]}]
90   - [_id following])
91   - [nil nil])]
  95 + [_id (set following)]))]
92 96 (list
93 97 [:br]
94 98 [:table#user-table.my-table
95 99 [:thead
96 100 [:tr
97   - [:th {:style "width: 40px;"} "Rank"]
98   - [:th "Username"]
99   - [:th "Problems Solved"]
  101 + [:th {:style "width: 40px;" } "Rank"]
  102 + [:th {:style "width: 200px;"} "Username"]
  103 + [:th {:style "width: 180px;"} "Problems Solved"]
100 104 [:th "Following"]]]
101   - (map-indexed (fn [rownum {:keys [_id rank user contributor solved]}]
  105 + (map-indexed (fn [rownum {:keys [_id position rank user contributor solved]}]
102 106 [:tr (row-class rownum)
103   - [:td (rank-class rank) rank]
  107 + [:td (rank-class position) rank]
104 108 [:td
105 109 (when contributor [:span.contributor "* "])
106 110 [:a.user-profile-link {:href (str "/user/" user)} user]]
@@ -117,14 +121,14 @@
117 121 :main (generate-user-list (get-ranked-users))})})
118 122
119 123 (def-page top-users-page []
120   - (let [username (session/session-get :user)
  124 + (let [username (session/session-get :user)
121 125 {:keys [user-ranking top-100]} (get-top-100-and-current-user username)]
122 126 {:title "Top 100 Users"
123 127 :content
124 128 (content-page
125 129 {:heading "Top 100 Users"
126 130 :heading-note (list "[show " (link-to "/users/all" "all") "]")
127   - :sub-heading (list (format-user-ranking user-ranking)
  131 + :sub-heading (list (format-user-ranking user-ranking)
128 132 [:span.contributor "*"] "&nbsp;"
129 133 (link-to repo-url "4clojure contributor"))
130 134 :main (generate-user-list top-100)})}))
@@ -195,7 +199,7 @@
195 199 (update! :users
196 200 {:_id _id}
197 201 {operation {:following follow-id}}))))
198   -
  202 +
199 203 (defn static-follow-user [username follow?]
200 204 (follow-user username follow?)
201 205 (response/redirect (str "/user/" username)))
9 src/foreclojure/utils.clj
... ... @@ -1,6 +1,7 @@
1 1 (ns foreclojure.utils
2 2 (:require [sandbar.stateful-session :as session]
3 3 [ring.util.response :as response]
  4 + [foreclojure.config :as config]
4 5 [clojure.walk :as walk]
5 6 [clojure.string :as string]
6 7 [foreclojure.git :as git]
@@ -167,6 +168,12 @@
167 168 (>= (count (get-solved username))
168 169 (:advanced-user-count config)))))
169 170
  171 +(let [prefix (str (when-let [host config/static-host]
  172 + (str "http://" host))
  173 + "/")]
  174 + (defn static-url [url]
  175 + (str prefix url)))
  176 +
170 177 (let [version-suffix (str "__" git/tag)]
171 178 (defn add-version-number [file]
172 179 (let [[_ path ext] (re-find #"(.*)\.(.*)$" file)]
@@ -178,7 +185,7 @@
178 185 (letfn [(wrap-versioning [f]
179 186 (fn [& files]
180 187 (for [file files]
181   - (f (add-version-number file)))))]
  188 + (f (static-url (add-version-number file))))))]
182 189 (def js (wrap-versioning hiccup/include-js))
183 190 (def css (wrap-versioning hiccup/include-css)))
184 191
8 src/foreclojure/version.clj
... ... @@ -1,15 +1,15 @@
1 1 (ns foreclojure.version
2 2 (:use [foreclojure.template :only [def-page]]
3 3 [foreclojure.config :only [repo-url]]
4   - [foreclojure.git :only [sha]]
  4 + [foreclojure.git :only [sha tag]]
5 5 [compojure.core :only [defroutes GET]]))
6 6
7 7 (def-page version []
8 8 {:title "About/version"
9 9 :content
10   - (if sha
11   - [:p "SHA: "
12   - [:a {:href (str repo-url "/tree/" sha)} sha]]
  10 + (if tag
  11 + [:p
  12 + [:a {:href (str repo-url "/tree/" sha)} tag]]
13 13 [:p "No git repository found"])})
14 14
15 15 (defroutes version-routes

No commit comments for this range

Something went wrong with that request. Please try again.