Permalink
Browse files

Merge branch 'release/1.3.0'

  • Loading branch information...
2 parents 667590b + 86d5685 commit 7abcce88ad7ddcf0f24eeb33957981452d79d938 @amalloy amalloy committed Sep 16, 2011
View
@@ -3,13 +3,14 @@
An interactive problem website for Clojure beginners
[https://www.4clojure.com](https://www.4clojure.com).
-## State of the Art
+## Contributing
-This site is in a very early stage of development, so there is not a
-lot of polish yet. Anyone interested in contributing should check out
+Anyone interested in contributing should check out
the [Issues](https://github.com/dbyrne/4clojure/issues) page for ideas
on what to work on.
+Join us in #4clojure on freenode for help or discussion.
+
## Setup instructions for running locally
* Download and install [leiningen](https://github.com/technomancy/leiningen).
@@ -32,9 +33,8 @@ vaguely like this:
* For the first time use, you will need to load the problem data. Run the script `load-data.sh`:
./load-data.sh
-* Run `lein run` and then open the brower to http://localhost:8080/
- lein run
+* Run `lein ring server`
## Contributors
View
@@ -1,4 +1,4 @@
-(defproject foreclojure "1.2.0"
+(defproject foreclojure "1.3.0"
:description "4clojure - a website for lisp beginners"
:dependencies [[clojure "1.2.1"]
[clojure-contrib "1.2.0"]
@@ -16,8 +16,9 @@
[incanter/incanter-core "1.2.3"]
[incanter/incanter-charts "1.2.3"]
[org.apache.commons/commons-email "1.2"]]
- :dev-dependencies [[lein-ring "0.4.0"]
+ :dev-dependencies [[lein-ring "0.4.5"]
[swank-clojure "1.2.1"]
[midje "1.1.1"]]
:main foreclojure.core
- :ring {:handler foreclojure.core/app})
+ :ring {:handler foreclojure.core/app
+ :init foreclojure.mongo/prepare-mongo})
@@ -116,6 +116,18 @@ a.novisited {color: #00e;}
#menu a {text-decoration: none; font-size: 14px; font-weight:bold;}
#menu a.menu {margin: 15px;}
+.user-follow-button{
+ margin-left: 20px;
+ margin-top: -3px;
+ margin-bottom: 10px;
+}
+
+.user-profile-name{
+ float: left;
+ font-weight: bold;
+ font-size: 15px;
+}
+
.user-profile-link{
text-decoration: none;
color: black;
@@ -210,10 +222,16 @@ div.message {
float: left;
}
-span#prob-title {
- font-size: 20px;
+div#prob-title {
+ font-size: 15pt;
font-family: sans-serif;
font-weight: bold;
+ float: left;
+}
+
+button#solutions-link {
+ margin-left: 20px;
+ margin-top: -1px;
}
div#prob-desc {
@@ -436,6 +454,11 @@ a.graph-class :hover {color: #445599;}
width: 90%;
}
+.ranking {
+ font-weight: bold;
+ font-size: 14px;
+}
+
#github-banner {
float: right;
display: block;
View
@@ -8,7 +8,7 @@
[foreclojure.login :only [login-routes]]
[foreclojure.register :only [register-routes]]
[foreclojure.golf :only [golf-routes]]
- [foreclojure.ring :only [resources wrap-strip-trailing-slash wrap-url-as-file]]
+ [foreclojure.ring :only [resources wrap-strip-trailing-slash wrap-url-as-file wrap-versioned-expiry]]
[foreclojure.users :only [users-routes]]
[foreclojure.config :only [config]]
[foreclojure.social :only [social-routes]]
@@ -37,7 +37,8 @@
golf-routes
(-> (resources "/*")
(wrap-url-as-file)
- (wrap-file-info))
+ (wrap-file-info)
+ (wrap-versioned-expiry))
(route/not-found "Page not found"))
(def app (-> #'main-routes
View
@@ -0,0 +1,12 @@
+(ns foreclojure.git
+ (:require [clojure.string :as s])
+ (:use [clojure.java.shell :only [sh]]))
+
+(letfn [(cmd [& args]
+ (not-empty (s/trim (:out (apply sh args)))))]
+
+ ;; fetch these at load time rather than on demand, so that it's accurate even
+ ;; if someone checks out a different revision to poke at without restarting
+ ;; the server (eg to diagnose bugs in a release)
+ (def sha (cmd "git" "rev-parse" "--verify" "HEAD"))
+ (def tag (cmd "git" "describe" "--abbrev=0" "master")))
View
@@ -4,13 +4,13 @@
(:import [org.jasypt.util.password StrongPasswordEncryptor])
(:use [hiccup.form-helpers :only [form-to label text-field password-field check-box]]
[foreclojure.utils :only [def-page from-mongo flash-error flash-msg with-user form-row assuming send-email login-url]]
- [foreclojure.users :only [disable-codebox? set-disable-codebox]]
+ [foreclojure.users :only [disable-codebox? set-disable-codebox hide-solutions? set-hide-solutions]]
[compojure.core :only [defroutes GET POST]]
[useful.map :only [keyed]]
[clojail.core :only [thunk-timeout]]
[clojure.stacktrace :only [print-cause-trace]]
[somnium.congomongo :only [update! fetch-one]]))
-
+
(def-page my-login-page [location]
{:title "4clojure - login"
:content
@@ -50,6 +50,7 @@
(response/redirect (or location "/problems")))
(flash-error "Error logging in." "/login"))))
+;; TODO this page is getting hella gross. Need a real Settings page soon.
(def-page update-credentials-page []
{:title "Change password"
:content
@@ -66,17 +67,30 @@
[password-field :pwd "New password"]
[password-field :repeat-pwd "Repeat password"]])
[:tr
- [:td [:button {:type "submit"} "Reset now"]]])]
- [:div#settings-codebox
- [:h2 "Disable JavaScript Code Box"]
- [:p "Selecting this will disable the JavaScript code entry box and just give you plain text entry"]
- (form-to [:post "/users/set-disable-codebox"]
- (check-box :disable-codebox
+ [:td [:button {:type "submit"} "Reset now"]]])]
+ [:hr]
+ [:div#settings-codebox
+ [:h2 "Disable JavaScript Code Box"]
+ [:p "Selecting this will disable the JavaScript code entry box and just give you plain text entry"]
+ (form-to [:post "/users/set-disable-codebox"]
+ (check-box :disable-codebox
(disable-codebox? user-obj))
- [:label {:for "disable-codebox"}
+ [:label {:for "disable-codebox"}
"Disable JavaScript in code entry box"]
[:br]
[:div#button-div
+ [:button {:type "submit"} "Submit"]])]
+ [:hr]
+ [:div#settings-follow
+ [:h2 "Hide My Solutions"]
+ [:p "When you solve a problem, we allow any user who has solved a problem to view your solutions to that problem. Check this box to keep your solutions private."]
+ (form-to [:post "/users/set-hide-solutions"]
+ (check-box :hide-solutions
+ (hide-solutions? user-obj))
+ [:label {:for "hide-solutions"}
+ "Hide my solutions"]
+ [:br]
+ [:div#button-div
[:button {:type "submit"} "Submit"]])]]])})
(defn do-update-credentials! [new-username old-pwd new-pwd repeat-pwd]
@@ -181,10 +195,14 @@
(GET "/login/reset" [] (reset-password-page))
(POST "/login/reset" [email]
(do-reset-password! email))
-
+
(GET "/logout" []
(do (session/session-delete-key! :user)
(response/redirect "/")))
(POST "/users/set-disable-codebox" [disable-codebox]
- (set-disable-codebox disable-codebox)))
+ (set-disable-codebox disable-codebox))
+
+ (POST "/users/set-hide-solutions" [hide-solutions]
+ (println "POST")
+ (set-hide-solutions hide-solutions)))
@@ -55,7 +55,7 @@
([problem]
(str "Now try " (problem-link problem) "!"))
([skipped not-tried]
- (str "Now move on to " (problem-link not-tried)
+ (str "Now you can move on to " (problem-link not-tried)
", or go back and try " (problem-link skipped) " again!"))))
(defn next-problem-link [completed-problem-id]
@@ -84,7 +84,8 @@
(when code (.trim code)))
(defn code-length [code]
- (count (remove #(Character/isWhitespace %)
+ (count (remove #(or (Character/isWhitespace %)
+ (= % \,))
code)))
(defn record-golf-score! [user-id problem-id score]
@@ -132,20 +133,24 @@
(defn mark-completed [problem code & [user]]
(let [user (or user (session/session-get :user))
{:keys [_id approved]} problem
- gist-link (html [:div.share
- [:a.novisited {:href "/share/code"} "Share"]
- " this solution with your friends!"])
+ gist-link (html [:span.share
+ [:a.novisited {:href "/share/code"} "share"]
+ " this solution on github and twitter! "])
message
(cond
(not approved) (str "You've solved the unapproved problem. Now you can approve it!")
user (do
(store-completed-state! user _id code)
- (str "Congratulations, you've solved the problem!"
- "<br />" (next-problem-link _id)))
- :else (str "You've solved the problem! If you "
- (login-link "log in" (str "/problem/" _id)) " we can track your progress."))]
+ (str "Congratulations, you've solved the problem! See the "
+ "<a href='/problem/solutions/" _id "'>solutions</a>"
+ " that the users you follow have submitted, or "
+ gist-link
+ (next-problem-link _id)))
+ :else (str "You've solved the problem; "
+ gist-link
+ "You need to " (login-link "log in" (str "/problem/" _id)) " in order to save your solutions and track progress."))]
(session/session-put! :code [_id code])
- {:message (str message " " gist-link), :error "", :url (str "/problem/" _id)}))
+ {:message message, :error "", :url (str "/problem/" _id)}))
(def restricted-list '[use require in-ns future agent send send-off pmap pcalls])
@@ -253,15 +258,22 @@ Return a map, {:message, :error, :url, :num-tests-passed}."
(let [{:keys [_id title difficulty tags description
restricted tests approved user]}
(get-problem (Integer. id)),
-
+ session-user (session/session-get :user)
title (str (when-not approved
"Unapproved: ")
title)]
{:title (str _id ". " title)
:content
[:div
- [:span#prob-title title]
+ [:div#prob-title title]
+ (if session-user
+ (with-user [{:keys [solved]}]
+ (if (some #{(Integer. id)} solved)
+ (link-to (str "/problem/solutions/" id)
+ [:button#solutions-link {:type "submit"} "Solutions"])
+ [:div {:style "clear: right; margin-bottom: 15px;"} "&nbsp;"]))
+ [:div {:style "clear: right; margin-bottom: 15px;"} "&nbsp;"])
[:hr]
[:table#tags
[:tr [:td "Difficulty:"] [:td (or difficulty "N/A")]]
@@ -311,6 +323,43 @@ Return a map, {:message, :error, :url, :num-tests-passed}."
(code-box id)
(flash-error "You cannot access this page" "/problems")))
+(def-page show-solutions-page [problem-id]
+ {:title "4Clojure - Problem Solutions"
+ :content
+ (list
+ [:div.message (session/flash-get :message)]
+ [:div#problems-error.error (session/flash-get :error)]
+ [:h3 {:style "margin-top: -20px;"} "Solutions:"]
+ (with-user [{:keys [following]}]
+ (if (empty? following)
+ [:p "You can only see solutions of users whom you follow. Click on any name from the " (link-to "/users" "users") " listing page to see their profile, and click follow from there."]
+ (if (some (complement nil?) (map #(get-solution :public % problem-id) following))
+ (interpose [:hr {:style "margin-top: 20px; margin-bottom: 20px;"}]
+ (for [f-user-id following
+ :let [f-user (:user (from-mongo
+ (fetch-one :users
+ :where {:_id f-user-id}
+ :only [:user])))
+ f-code (get-solution :public
+ f-user-id problem-id)]
+ :when f-code]
+ [:div.follower-solution
+ [:div.follower-username (str f-user "'s solution:")]
+ [:pre.follower-code f-code]]))
+ [:p "None of the users you follow have solved this problem yet!"]))))})
+
+(defn show-solutions [id]
+ (let [problem-id (Integer. id)
+ user (session/session-get :user)]
+ (if user
+ (with-user [{:keys [solved]}]
+ (if (some #{problem-id} solved)
+ (show-solutions-page problem-id)
+ (flash-error "You must solve this problem before you can see others' solutions!" (str "/problem/" problem-id))))
+ (do
+ (session/session-put! :login-to (str "/problem/solutions/" problem-id))
+ (flash-error "You must login to see solutions!" "/login")))))
+
(let [checkbox-img (image-builder {true ["/images/checkmark.png" "completed"]
false ["/images/empty-sq.png" "incomplete"]})]
(def-page problem-list-page []
@@ -409,16 +458,21 @@ Return a map, {:message, :error, :url, :num-tests-passed}."
"create a user submitted problem"
[title difficulty tags restricted description code id author]
(let [user (session/session-get :user)]
- (if (can-submit? user)
+ (if (or (approver? user)
+ (and (can-submit? user)
+ (not id)))
(let [id (or id
(:seq (fetch-and-modify
:seqs
{:_id "problems"}
{:$inc {:seq 1}})))
edit-url (str "https://4clojure.com/problem/"
- id)]
+ id)
+ approved (true? (:approved (fetch-one :problems
+ :where {:_id id}
+ :only [:approved])))]
- (when (empty? author) ; newly submitted, not a moderator tweak
+ (when (empty? author) ; newly submitted, not a moderator tweak
(try
(send-email
{:from "team@4clojure.com"
@@ -442,7 +496,7 @@ Return a map, {:message, :error, :url, :num-tests-passed}."
:restricted (re-seq #"\S+" restricted)
:tests (s/split code #"\r\n\r\n")
:user (if (empty? author) user author)
- :approved false})
+ :approved approved})
(flash-msg "Thank you for submitting a problem! Be sure to check back to see it posted." "/problems"))
(flash-error "You are not authorized to submit a problem." "/problems"))))
@@ -501,17 +555,21 @@ Return a map, {:message, :error, :url, :num-tests-passed}."
(POST "/problems/submit" [prob-id author title difficulty tags restricted description code]
(create-problem title difficulty tags restricted description code (when (not= "" prob-id) (Integer. prob-id)) author))
(GET "/problems/unapproved" [] (unapproved-problem-list))
+ (GET "/problem/:id/edit" [id]
+ (edit-problem (Integer. id)))
(POST "/problem/edit" [id]
(edit-problem (Integer. id)))
(POST "/problem/approve" [id]
(approve-problem (Integer. id)))
(POST "/problem/reject" [id]
(reject-problem (Integer. id) "We didn't like your problem."))
+ (GET "/problem/solutions/:id" [id]
+ (show-solutions id))
(POST "/problem/:id" [id code]
(static-run-code (Integer. id) (trim-code code)))
(POST "/rest/problem/:id" [id code]
- {:headers {"Content-Type" "application/json"}}
- (rest-run-code (Integer. id) (trim-code code)))
+ {:headers {"Content-Type" "application/json"}}
+ (rest-run-code (Integer. id) (trim-code code)))
(GET "/problems/rss" [] (create-feed
"4Clojure: Recent Problems"
"http://4clojure.com/problems"
Oops, something went wrong.

0 comments on commit 7abcce8

Please sign in to comment.