Permalink
Browse files

Merge branch 'release/1.4.0'

  • Loading branch information...
2 parents 5383458 + de7dcac commit 5eee06d68f5f3b3d8ec20906c89ac262023ce221 @amalloy amalloy committed Sep 30, 2011
View
4 4clojure.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+java -cp `lein classpath` clojure.main -e \
+ "(do (require 'foreclojure.core) (foreclojure.core/-main))"
View
4 README.md
@@ -1,12 +1,12 @@
# 4Clojure
-An interactive problem website for Clojure beginners
+An interactive problem website for learning Clojure:
[https://www.4clojure.com](https://www.4clojure.com).
## Contributing
Anyone interested in contributing should check out
-the [Issues](https://github.com/dbyrne/4clojure/issues) page for ideas
+the [Issues](https://github.com/4clojure/4clojure/issues) page for ideas
on what to work on.
Join us in #4clojure on freenode for help or discussion.
View
2 project.clj
@@ -1,4 +1,4 @@
-(defproject foreclojure "1.3.3"
+(defproject foreclojure "1.4.0"
:description "4clojure - a website for lisp beginners"
:dependencies [[clojure "1.2.1"]
[clojure-contrib "1.2.0"]
View
15 resources/public/css/style.css
@@ -57,7 +57,7 @@ div.progress-bar {
#content {
width: 85%;
margin: 15px auto;
- padding: 15px 0px 15px 0px;
+ border: 2px solid #3D3C3C;
text-align: left;
background-color: #fff;
overflow: auto;
@@ -92,6 +92,7 @@ h3 {
#menu{
background-color:#445599;
+ border-radius: 12px 12px 0 0;
padding: 20px 15px 15px 15px;
color: #fff;
}
@@ -162,6 +163,9 @@ a.novisited {color: #00e;}
}
#logo {
+ width: 249px;
+ height: 84px;
+ margin-left: -7px;
float:left;
padding:15px 0px 15px 0px;
border: 0;
@@ -181,6 +185,7 @@ a.novisited {color: #00e;}
#footer{
background-color:#445599;
+ border-radius: 0 0 12px 12px;
padding: 15px;
color: #fff;
text-align: center;
@@ -363,6 +368,14 @@ table.my-table td {
font-size: 12px;
}
+td.oddrank {
+ background-color: #DDF !important;
+}
+
+td.evenrank {
+ background-color: #EEF !important;
+}
+
td.title-link {
font-size: 16px;
}
View
BIN resources/public/favicon.ico
Binary file not shown.
View
BIN resources/public/favicon2.ico
Binary file not shown.
View
BIN resources/public/images/4clj-logo.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN resources/public/images/logo.png
Deleted file not rendered
View
49 resources/public/script/foreclojure.js
@@ -22,6 +22,22 @@ $(document).ready(function() {
$(this).parents("form").attr("action", "/problem/edit").submit();
});
+ $("form input.following").live("click", function(e) {
+ e.preventDefault();
+ var $checkbox = $(this)
+ var $form = $checkbox.parents("form")
+ $.ajax({type: "POST",
+ url: "/rest" + $form.attr("action"),
+ dataType: "json",
+ success: function(data) {
+ if (data) {
+ $checkbox.attr("checked", data["following"]);
+ $form.attr("action", data["next-action"]);
+ }
+ },
+ });
+ return false;
+ });
});
var difficulty = new Array();
@@ -43,35 +59,36 @@ function configureDataTables(){
$('#problem-table').dataTable( {
"iDisplayLength": 25,
- "aaSorting": [[5, "desc"], [ 1, "asc" ], [ 4, "desc" ]],
+ "aaSorting": [[5, "desc"], [1, "asc"], [4, "desc"]],
"aoColumns": [
- null,
- { "sType": "difficulty" },
- null,
- null,
- null,
- { "sType": "string" }
+ {"sType": "string"},
+ {"sType": "difficulty"},
+ {"sType": "string"},
+ {"sType": "string"},
+ {"sType": "numeric"},
+ {"sType": "string"}
]
} );
$('#unapproved-problems').dataTable( {
"iDisplayLength": 25,
- "aaSorting": [[ 3, "desc" ]],
+ "aaSorting": [[3, "desc"]],
"aoColumns": [
- null,
- null,
- null,
- null
+ {"sType": "string"},
+ {"sType": "string"},
+ {"sType": "string"},
+ {"sType": "string"}
]
} );
$('#user-table').dataTable( {
"iDisplayLength":100,
- "aaSorting": [[ 0, "asc" ]],
+ "aaSorting": [[0, "asc"]],
"aoColumns": [
- null,
- null,
- null
+ {"sType": "numeric"},
+ {"sType": "string"},
+ {"sType": "numeric"},
+ {"sType": "string"}
]
} );
}
View
1 src/foreclojure/login.clj
@@ -212,5 +212,4 @@
(set-disable-codebox disable-codebox))
(POST "/users/set-hide-solutions" [hide-solutions]
- (println "POST")
(set-hide-solutions hide-solutions)))
View
6 src/foreclojure/template.clj
@@ -15,7 +15,7 @@
[:head
[:title (:title attrs)]
[:link {:rel "alternate" :type "application/atom+xml" :title "Atom" :href "http://4clojure.com/problems/rss"}]
- [:link {:rel "shortcut icon" :href "/favicon.ico"}]
+ [:link {:rel "shortcut icon" :href "/favicon2.ico"}]
[:style {:type "text/css"}
".syntaxhighlighter { overflow-y: hidden !important; }"]
(css "/css/style.css" "/css/demo_table.css" "/css/shCore.css" "/css/shThemeDefault.css")
@@ -29,10 +29,8 @@
[:div#github-banner [:a {:href repo-url
:alt "Fork 4Clojure on Github!"}]])
[:div#top
- (link-to "/" [:img#logo {:src "/images/logo.png" :alt "4clojure.com"
- :width 230 :height 57}])]
+ (link-to "/" [:img#logo {:src "/images/4clj-logo.png" :alt "4clojure.com"}])]
[:div#content
- [:br]
[:div#menu
(for [[link text & [tabbed]]
[["/" "Main Page"]
View
130 src/foreclojure/users.clj
@@ -1,13 +1,14 @@
(ns foreclojure.users
(:require [ring.util.response :as response]
[sandbar.stateful-session :as session])
- (:use [foreclojure.utils :only [from-mongo row-class get-user with-user]]
+ (:use [foreclojure.utils :only [from-mongo row-class rank-class get-user with-user]]
[foreclojure.template :only [def-page content-page]]
[foreclojure.config :only [config repo-url]]
[somnium.congomongo :only [fetch-one fetch update!]]
[compojure.core :only [defroutes GET POST]]
[hiccup.form-helpers :only [form-to hidden-field]]
- [hiccup.page-helpers :only [link-to]]))
+ [hiccup.page-helpers :only [link-to]]
+ [clojure.contrib.json :only [json-str]]))
(def golfer-tags (into [:contributor]
(when (:golfing-active config)
@@ -19,35 +20,33 @@
:where {:user name}
:only [:_id])))
-(def sort-by-solved-and-date (juxt (comp count :solved) :last-login))
-
-(defn users-sort [users]
- (reverse (sort-by sort-by-solved-and-date users)))
-
(defn get-users []
- (let [users (from-mongo
- (fetch :users
- :only [:user :solved :contributor]))
- sortfn (comp - count :solved)]
- (sort-by sortfn users)))
-
-(defn get-user-with-ranking [username, users]
- (when username
- (let [total (count users)
- users-with-rankings (map-indexed
- (fn [idx itm]
- (assoc itm :rank
- (str (inc idx) " out of " total)))
- users)]
- (first
- (filter #(= username (% :user)) users-with-rankings)))))
+ (from-mongo
+ (fetch :users
+ :only [:user :solved :contributor])))
-(defn get-top-100-and-current-user [username]
+(defn get-ranked-users []
(let [users (get-users)
- user-ranking (get-user-with-ranking username users)]
- {:user-ranking user-ranking
- :top-100 (take 100 users)}))
+ tied-groups (map val
+ (sort-by #(-> % key -)
+ (group-by #(count (or (:solved %) []))
+ users)))]
+ (first
+ (reduce (fn [[user-list rank] new-group]
+ [(into user-list
+ (for [user (sort-by :user new-group)]
+ (assoc user :rank rank)))
+ (+ rank (count new-group))])
+ [[] 1]
+ tied-groups))))
+(defn get-top-100-and-current-user [username]
+ (let [ranked-users (get-ranked-users)
+ this-user (first (filter (comp #{username} :user)
+ ranked-users))
+ this-user-ranking (update-in this-user [:rank] #(str (or % "?") " out of " (count ranked-users)))]
+ {:user-ranking this-user-ranking
+ :top-100 (take 100 ranked-users)}))
(defn golfer? [user]
(some user golfer-tags))
@@ -77,41 +76,58 @@
[:br]
[:br]]))
+(defn follow-url [username follow?]
+ (str "/user/" (if follow? "follow" "unfollow") "/" username))
+
+(defn following-checkbox [current-user-id following user-id user]
+ (when (and current-user-id (not= current-user-id user-id))
+ (let [following? (some #{user-id} following)]
+ (form-to [:post (follow-url user (not following?))]
+ [:input.following {:type "checkbox" :name "following"
+ :checked following? :value following?}]))))
+
(defn generate-user-list [user-set]
- (list
- [:br]
- [:table#user-table.my-table
- [:thead
- [:tr
- [:th {:style "width: 40px;"} "Rank"]
- [:th "Username"]
- [:th "Problems Solved"]]]
- (map-indexed (fn [rownum {:keys [user contributor solved]}]
- [:tr (row-class rownum)
- [:td (inc rownum)]
- [:td
- (when contributor [:span.contributor "* "])
- [:a.user-profile-link {:href (str "/user/" user)} user]]
- [:td.centered (count solved)]])
- user-set)]))
+ (let [[user-id following]
+ (if (session/session-get :user)
+ (with-user [{:keys [_id following]}]
+ [_id following])
+ [nil nil])]
+ (list
+ [:br]
+ [:table#user-table.my-table
+ [:thead
+ [:tr
+ [:th {:style "width: 40px;"} "Rank"]
+ [:th "Username"]
+ [:th "Problems Solved"]
+ [:th "Following"]]]
+ (map-indexed (fn [rownum {:keys [_id rank user contributor solved]}]
+ [:tr (row-class rownum)
+ [:td (rank-class rank) rank]
+ [:td
+ (when contributor [:span.contributor "* "])
+ [:a.user-profile-link {:href (str "/user/" user)} user]]
+ [:td.centered (count solved)]
+ [:td (following-checkbox user-id following _id user)]])
+ user-set)])))
(def-page all-users-page []
{:title "All 4Clojure Users"
:content
(content-page
{:heading "All 4Clojure Users"
:sub-heading (list [:span.contributor "*"] " " (link-to repo-url "4clojure contributor"))
- :main (generate-user-list (get-users))})})
+ :main (generate-user-list (get-ranked-users))})})
(def-page top-users-page []
- (let [username (session/session-get :user)
+ (let [username (session/session-get :user)
{:keys [user-ranking top-100]} (get-top-100-and-current-user username)]
{:title "Top 100 Users"
:content
(content-page
{:heading "Top 100 Users"
:heading-note (list "[show " (link-to "/users/all" "all") "]")
- :sub-heading (list (format-user-ranking user-ranking)
+ :sub-heading (list (format-user-ranking user-ranking)
[:span.contributor "*"] " "
(link-to repo-url "4clojure contributor"))
:main (generate-user-list top-100)})}))
@@ -175,14 +191,24 @@
(count (get-solved username)) "/"
(count (get-problems))]]])}))
-(defn follow-user [username operation]
+(defn follow-user [username follow?]
(with-user [{:keys [_id]}]
- (let [follow-id (:_id (get-user username))]
+ (let [follow-id (:_id (get-user username))
+ operation (if follow? :$addToSet :$pull)]
(update! :users
{:_id _id}
- {operation {:following follow-id}})))
+ {operation {:following follow-id}}))))
+
+(defn static-follow-user [username follow?]
+ (follow-user username follow?)
(response/redirect (str "/user/" username)))
+(defn rest-follow-user [username follow?]
+ (follow-user username follow?)
+ (json-str {"following" follow?
+ "next-action" (follow-url username (not follow?))
+ "next-label" (if follow? "Unfollow" "Follow")}))
+
(defn set-disable-codebox [disable-flag]
(with-user [{:keys [_id]}]
(update! :users
@@ -201,5 +227,7 @@
(GET "/users" [] (top-users-page))
(GET "/users/all" [] (all-users-page))
(GET "/user/:username" [username] (user-profile username))
- (POST "/user/follow/:username" [username] (follow-user username :$addToSet))
- (POST "/user/unfollow/:username" [username] (follow-user username :$pull)))
+ (POST "/user/follow/:username" [username] (static-follow-user username true))
+ (POST "/user/unfollow/:username" [username] (static-follow-user username false))
+ (POST "/rest/user/follow/:username" [username] (rest-follow-user username true))
+ (POST "/rest/user/unfollow/:username" [username] (rest-follow-user username false)))
View
5 src/foreclojure/utils.clj
@@ -191,3 +191,8 @@
{:class (if (even? x)
"evenrow"
"oddrow")})
+
+(defn rank-class [x]
+ {:class (if (even? x)
+ "evenrank"
+ "oddrank")})

0 comments on commit 5eee06d

Please sign in to comment.