Permalink
Browse files

Merge branch 'release/1.8.0'

  • Loading branch information...
2 parents 89e599a + 37a2a31 commit 8b6259c4a96e239a6c3f80535b8ca67bc65512ca @amalloy amalloy committed Dec 30, 2011
View
@@ -36,6 +36,8 @@ vaguely like this:
* Run `lein ring server`
+* To run the tests: `lein test`
+
## Contributors
* [David Byrne](https://github.com/dbyrne) (dbyrne)
View
@@ -1,5 +1,6 @@
{:wrap-reload false
:db-host "localhost"
+ :db-name "mydb"
:db-user nil
:db-pwd nil
:jetty-port 8080
View
@@ -1,4 +1,4 @@
-(defproject foreclojure "1.7.0"
+(defproject foreclojure "1.8.0"
:description "4clojure - a website for learning Clojure"
:dependencies [[clojure "1.2.1"]
[clojure-contrib "1.2.0"]
@@ -21,6 +21,7 @@
[org.apache.commons/commons-email "1.2"]]
:dev-dependencies [[lein-ring "0.4.5"]
[midje "1.1.1"]]
+ :checksum-deps true
:main foreclojure.core
:ring {:handler foreclojure.core/app
:init foreclojure.mongo/prepare-mongo})
@@ -21,4 +21,11 @@ settings.email-exists=User with this email address already exists
security.login-required=You must %s to do this
security.err-reset-email=Something went wrong emailing your new password! Please contact <a href='mailto:team@4clojure.com?subject=Password Reset: %s'>team@4clojure.com</a> - we'll reset it manually and look into the problem. When you do, please mention your username.
-security.email-unknown=We don't know anyone with that email address!
+security.email-unknown=We don't know anyone with that email address!
+
+#######################################
+# solution related error messages #
+#######################################
+
+solution.scored-early=Scored %d, before 4clojure started saving solutions.
+solution.solved-early=Solved before 4clojure started scoring solutions
@@ -326,13 +326,26 @@ div.message #error-text {
color: red;
}
+div#prob-container {
+ position: relative;
+}
+
div#prob-title {
font-size: 15pt;
font-family: sans-serif;
font-weight: bold;
float: left;
}
+div#prob-number {
+ position: absolute;
+ right: 10px;
+ font-size: 2em;
+ font-weight: bold;
+ color: #888;
+ top: -10px;
+}
+
button#solutions-link {
margin-left: 20px;
margin-top: -1px;
@@ -6,7 +6,6 @@ var CodeBox = {
editor: null,
editorSession: null,
editorElement: null,
- cont: true,
high: false,
animationTime: 800,
waitTimePerItem: 500,
@@ -88,12 +87,14 @@ var CodeBox = {
beforeSendCallback: function() {
var anim = function() {
- if(this.cont) {
- this.images.animate({
+ if(this.images.filter('.animated').length > 0) {
+ this.images.filter('.animated').animate({
opacity: this.high ? 1.0 : 0.1,
}, this.animationTime);
this.high = !this.high;
- setTimeout(anim,this.animationTime);
+ setTimeout($.proxy(anim, this),this.animationTime);
+ } else {
+ this.high = false;
}
};
@@ -103,6 +104,7 @@ var CodeBox = {
setIconColor(element, "blue");
});
setTimeout(changeToCodeView,0);
+ this.images.addClass("animated");
setTimeout($.proxy(anim, this),0);
},
@@ -119,23 +121,24 @@ var CodeBox = {
setColor = function(index,element) {
var color = getColorFor(index);
waitTime = CodeBox.waitTimePerItem * (index+1);
- setIconColor(element, color, waitTime);
+ setIconColor(element, color, waitTime, true);
+ if(color == "red") { //failing test
+ setTimeout("CodeBox.stopAnimation()", waitTime);
+ }
},
setMessages = function() {
$("#message-text").html(data.message);
$("#error-message-text").html(data.error);
$("#golfgraph").html(data.golfChart);
$("#golfscore").html(data.golfScore);
configureGolf();
- },
- stopAnimation = function() {
- this.cont = false;
- this.images.stop(true);
- this.images.css({ opacity: 1.0, });
};
- setTimeout($.proxy(stopAnimation, this), waitTime);
this.images.filter( testWasExecuted ).each(setColor);
setTimeout(setMessages, waitTime);
},
+
+ stopAnimation: function() {
+ this.images.stop(true).removeClass("animated").css({ opacity: 1.0, });
+ },
}
@@ -78,6 +78,16 @@ var difficulty = {
// dataTable will call this function in preparation for sorting a column.
// We're responsible for giving it the "real" data to sort on, for all the
// rows at once
+jQuery.fn.dataTableExt.afnSortData['title'] = function(oSettings, iColumn) {
+ var aData = [];
+ $('td:eq('+iColumn+')', oSettings.oApi._fnGetTrNodes(oSettings)).each(function () {
+ // Prefix the data with the link text (title) for proper sorting, then append
+ // target ID so that searching picks it up.
+ aData.push($(this).text() + parseInt($(this).find('a').attr('href').split('/').slice(-1)[0]));
+ });
+ return aData;
+}
+
jQuery.fn.dataTableExt.afnSortData['difficulty'] = function(oSettings, iColumn)
{
var aData = [];
@@ -118,14 +128,14 @@ function configureDataTables(){
$('#problem-table').dataTable( {
"iDisplayLength": 100,
"aaSorting": [[5, "desc"], [1, "asc"], [4, "desc"]],
- "aoColumns": [
- {"sType": "string"},
+ "aoColumns": [
+ {"sSortDataType": "title", "sType": "string"},
{"sSortDataType": "difficulty", "sType": "numeric"},
{"sType": "string"},
- {"sType": "string"},
- {"sType": "numeric"},
+ {"sType": "string"},
+ {"sType": "numeric", "bSearchable": false},
{"sType": "string"}
- ]
+ ]
} );
$('#unapproved-problems').dataTable( {
@@ -165,10 +175,13 @@ function configureDataTables(){
} );
}
-function setIconColor(element, color, timeOut) {
- timeOut = (typeof timeOut == "undefined") ? 0 : timeOut
+function setIconColor(element, color, timeOut, stopAnimation) {
+ timeOut = (typeof timeOut == "undefined") ? 0 : timeOut;
+
setTimeout (function() {
- element.src = element.src.replace(new RegExp("(.*/images/).*(light.png)"), "$1" + color + "$2");
+ if(stopAnimation)
+ $(element).stop(true).removeClass("animated").css({ opacity: 1.0, });
+ element.src = element.src.replace(new RegExp("(.*/images/).*(light.png)"), "$1" + color + "$2");
}, timeOut);
}
@@ -6,10 +6,10 @@
[foreclojure.users :only [get-users]]))
(defn connect-to-db []
- (let [{:keys [db-user db-pwd db-host]} config]
+ (let [{:keys [db-user db-pwd db-host db-name]} config]
(mongo!
:host (or db-host "localhost")
- :db "mydb")
+ :db (or db-name "mydb"))
(when (and db-user db-pwd)
(authenticate db-user db-pwd))))
@@ -54,8 +54,9 @@
[(number-from-mongo-key id) score]
+ times))
{}))
- total (count (mapcat :solved users))]
- (send solved-stats (constantly (assoc scores :total total)))))
+ solved-counts (frequencies (map int (mapcat :solved users)))
+ total (reduce + (vals solved-counts))]
+ (send solved-stats (constantly (assoc scores :total total :solved-counts solved-counts)))))
(defn prepare-mongo []
(connect-to-db)
@@ -128,7 +128,11 @@
(when (not-any? #{problem-id} (get-solved username))
(update! :users {:_id user-id} {:$addToSet {:solved problem-id}
:$set {:last-solved-date current-time}})
- (send solved-stats update-in [:total] inc))
+ (let [inc (fnil inc 0)]
+ (send solved-stats
+ #(-> %
+ (update-in [:total] inc)
+ (update-in [:solved-counts problem-id] inc)))))
(record-golf-score! user-id problem-id (code-length code))
(save-solution user-id problem-id code)))
@@ -268,7 +272,8 @@ Return a map, {:message, :error, :url, :num-tests-passed}."
{:title (str _id ". " title)
:content
- [:div
+ [:div#prob-container
+ [:div#prob-number "#" id]
[:div#prob-title title]
(if-user [{:keys [solved]}]
(if (some #{(Integer. id)} solved)
@@ -402,7 +407,7 @@ Return a map, {:message, :error, :url, :num-tests-passed}."
(s/join " " (map #(str "<span class='tag'>" % "</span>")
tags))]
[:td.centered user]
- [:td.centered (reduce + (vals (get @solved-stats id)))]
+ [:td.centered (get-in @solved-stats [:solved-counts id] 0)]
[:td.centered (checkbox-img (contains? solved id))]])
problems))])}))
@@ -1,7 +1,8 @@
(ns foreclojure.solutions
(:require [clojure.string :as s])
(:use [somnium.congomongo :only [fetch-one update!]]
- [useful.debug :only [?]]))
+ [useful.debug :only [?]]
+ [foreclojure.messages :only [err-msg]]))
(defn get-solution
([perm-level user-id problem-id]
@@ -19,10 +20,8 @@
:where {:_id user-id}
:only [(keyword (str "scores." problem-id))
:solved])]
- (cond (seq scores) (str "Scored " (first (vals scores))
- ", before 4clojure started saving solutions."),
-
- (some #{problem-id} solved) "Solved before 4clojure started scoring solutions")))))
+ (cond (seq scores) (err-msg "solution.scored-early" (first (vals scores))),
+ (some #{problem-id} solved) (err-msg "solution.solved-early"))))))
(defn save-solution [user-id problem-id code]
(update! :solutions
@@ -73,10 +73,9 @@
[:br][:br]
"Some operations are prohibited for security reasons. For instance, you will not be able to use \"def\" or switch namespaces. In addition, some problems have special restrictions. For example, a function which is supposed to count the number of elements in a sequence will not be allowed to use the \"count\" function. Obviously, this would defeat the purpose. Any special restrictions will be listed on the problem page."
[:br][:br]
- "Many of the easier problems on this site can be solved using only your browser. However at some point you will want to install Clojure, and use your favorite IDE or text editor to write your code. We prefer using Emacs, but it's totally a matter of personal preference. Writing all your code directly on the site has a few disadvantages:"
+ "Many of the easier problems on this site can be solved using only your browser. However at some point you will want to install Clojure, and use your favorite IDE or text editor to write your code. We prefer using Emacs, but it's totally a matter of personal preference. Writing all your code directly on the site has a disadvantage:"
[:br][:br]
[:li "4Clojure is not an IDE, and doesn't try to be"]
- [:li "4Clojure won't save any of your code for later use"]
[:br]
"Check out the official Clojure "
[:a {:href "http://clojure.org/help"} "help"]
@@ -0,0 +1,43 @@
+(ns foreclojure.test.solutions
+ (:require [clojure.string :as s])
+ (:use [somnium.congomongo :only [fetch-one update!]])
+ (:use [useful.debug :only [?]])
+ (:use [foreclojure.messages :only [err-msg]])
+ (:use [foreclojure.solutions])
+ (:use [clojure.test])
+ (:use [midje.sweet]))
+
+(deftest test-get-solution
+ "this test covers the getting solutions to display"
+ (let [uid 1
+ pid 1
+ code "I AM SOME COOL CODE"]
+ (fact "about get-solution: private"
+ (get-solution :private uid pid) => code
+ (provided
+ (fetch-one :solutions :where {:user uid :problem pid}) => {:code code}))
+ (fact "about get-solution: hide solutions = true"
+ (get-solution :public uid pid) => falsey
+ (provided
+ (fetch-one :users :where {:_id uid} :only [:hide-solutions]) => {:hide-solutions true}))
+ (fact "about get-solution: hide solutions = true"
+ (get-solution :public uid pid) => code
+ (provided
+ (fetch-one :solutions :where {:user uid :problem pid}) => {:code code}
+ (fetch-one :users :where {:_id uid} :only [:hide-solutions]) => {:hide-solutions false}))
+ (fact "about get-solution: scored early"
+ (get-solution uid pid) => (err-msg "solution.scored-early" pid)
+ (provided
+ (fetch-one :solutions :where {:user uid, :problem pid}) => {}
+ (fetch-one :users
+ :where {:_id uid}
+ :only [(keyword (str "scores." pid))
+ :solved]) => {:scores {pid pid} :solved []}))
+ (fact "about get-solution: solved early"
+ (get-solution uid pid) => (err-msg "solution.solved-early")
+ (provided
+ (fetch-one :solutions :where {:user uid, :problem pid}) => {}
+ (fetch-one :users
+ :where {:_id uid}
+ :only [(keyword (str "scores." pid))
+ :solved]) => {:scores {} :solved [pid]}))))

0 comments on commit 8b6259c

Please sign in to comment.