diff --git a/.gitignore b/.gitignore index 8addd76..b5c1da3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +PrivateMakefile dump.rdb resources/ pom.xml diff --git a/Makefile b/Makefile index c815de1..d49d616 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,6 @@ +# This one is necessary to start the app in :dev mode, because +# I changed the default mode to the production, because AFAIK +# it's not possible to parameterize the app start on Heroku. run: lein run dev diff --git a/settings b/settings index 754f1b3..380245e 100644 --- a/settings +++ b/settings @@ -1,2 +1,2 @@ -page-width = 800px +page-width = 800 max-title-length = 80 diff --git a/src/NoteHub/settings.clj b/src/NoteHub/settings.clj index 74ea8fe..09cd6b3 100644 --- a/src/NoteHub/settings.clj +++ b/src/NoteHub/settings.clj @@ -1,24 +1,29 @@ (ns NoteHub.settings - (:require [clojure.string :as cs])) + (:refer-clojure :exclude [replace reverse]) + (:use [clojure.string])) + +; Load and parse te settings file returning a map +(def settings-map + (let [file-content (slurp "settings") + lines (split file-content #"\n") + pairs (map #(map trim (split % #"=")) lines)] + (apply hash-map + (mapcat #(list (keyword (first %)) (second %)) pairs)))) (defn get-setting - "Takes a settings key, a default value and a converter function and returns a corresponding - settings value. The default value is returned back when no setting value was found. - The converter function can be provided to convert the setting from string to a needed format." + "Takes a settings key, a converter function and a default value, and returns a corresponding + setting value. The default value is returned back when no setting value was found. + The converter function can be provided to convert the setting from string to a needed type." [key & more] - (let [default (first more) - converter (second more) - file-content (slurp "settings") - lines (cs/split file-content #"\n") - pairs (map #(map cs/trim %) (map #(cs/split % #"=") lines)) - config-map (apply hash-map (mapcat #(list (keyword (first %)) (second %)) pairs)) - value (config-map key) + (let [converter (first more) + default (second more) + value (settings-map key) ; Through this hack we can read security-critical settings from (previously ; set) shell variables without commiting their content to CVS value (if-not value (System/getenv - (cs/upper-case - (cs/replace (name key) #"-" ""))))] + (upper-case + (replace (name key) #"-" ""))))] (if value (if (fn? converter) (converter value) value) default))) diff --git a/src/NoteHub/storage.clj b/src/NoteHub/storage.clj index 3672c9f..f36b16e 100644 --- a/src/NoteHub/storage.clj +++ b/src/NoteHub/storage.clj @@ -21,8 +21,7 @@ (defn set-note "Creates a note with the given title and text in the given date namespace" [date title text] - (let [key (build-key date title)] - (redis/hset db note key text))) + (redis/hset db note (build-key date title) text)) (defn get-note "Gets the note from the given date namespaces for the specified title" diff --git a/src/NoteHub/views/css_generator.clj b/src/NoteHub/views/css_generator.clj index 0535b90..fd25d34 100644 --- a/src/NoteHub/views/css_generator.clj +++ b/src/NoteHub/views/css_generator.clj @@ -12,7 +12,7 @@ ; CSS Mixins (def page-width (mixin - :width (get-setting :page-width :800px keyword))) + :width (px (get-setting :page-width #(Integer/parseInt %) 800)))) (def helvetica-neue (mixin @@ -71,18 +71,32 @@ :color foreground :margin 0 :padding 0) + (rule "table,tr,td" + :margin 0 + :border :none) + (rule "td" + :padding :0.5em) + (rule ".one-third-column" + :text-align :justify + :vertical-align :top + ; Replace this by arithmetic with css-lengths as soon as they fix the bug + :width (px (quot (get-setting :page-width #(Integer/parseInt %) 800) 3))) (rule ".helvetica-neue" helvetica-neue) (rule "#hero" :padding-top :5em :padding-bottom :5em :text-align :center + (rule "h1" + :font-size :2.5em) (rule "h2" - helvetica-neue)) + helvetica-neue + :margin :2em)) (rule "article" central-element :line-height (% 140) :font-family text-fonts + :text-align :justify :font-size :1.2em (rule "& > h1:first-child" :text-align :center @@ -97,9 +111,8 @@ (rule "textarea" page-width :font-family :Courier - :font-size :1.2em + :font-size :1em :border :none - ; TODO: make this dynamic :height :500px :margin-bottom :2em) (rule ".hidden" diff --git a/src/NoteHub/views/pages.clj b/src/NoteHub/views/pages.clj index 4988e12..5f647da 100644 --- a/src/NoteHub/views/pages.clj +++ b/src/NoteHub/views/pages.clj @@ -1,9 +1,9 @@ (ns NoteHub.views.pages - (:require [NoteHub.views.common :as common]) (:require [NoteHub.crossover.lib :as lib]) (:use [NoteHub.storage] [NoteHub.settings] + [NoteHub.views.common] [clojure.string :rename {replace sreplace} :only [split replace lower-case]] [clojure.core.incubator :only [-?>]] [hiccup.form] @@ -31,15 +31,15 @@ (defn- wrap [params md-text] (if md-text (let [title (-?> md-text (split #"\n") first (sreplace #"[_\*#]" ""))] - (common/layout params title [:article (md-to-html md-text)])) + (layout params title [:article (md-to-html md-text)])) (status 404 (get-page 404)))) ; Template for the error sites (defn page-setter [code message] (set-page! code - (common/layout message - [:article - [:h1 message]]))) + (layout message + [:article + [:h1 message]]))) ; Sets a message for each corresponding HTTP status (page-setter 404 "Nothing Found.") @@ -58,29 +58,59 @@ ; Landing Page (defpage "/" {} - (common/layout "Free Markdown Hosting" - [:div#hero - [:h1 "NoteHub"] - [:h2 "Free hosting for markdown pages."] - [:br] - [:a.landing-button {:href "/new"} "New Page"]])) + (layout "Free Markdown Hosting" + [:div#hero + [:h1 "NoteHub"] + [:h2 "Free and hassle-free hosting for markdown pages."] + [:br] + [:a.landing-button {:href "/new"} "New Page"]] + [:div#preview-start-line] + [:table.central-element.helvetica-neue + [:tr + [:td.one-third-column + [:h2 "Why?"] + "Not every person, who occasionally wants to express some thoughts, needs a blog. + Blogs are tedious for writers and for readers. Most people are not interested in thoughts + of other random people. Moreover, nowadays everything rotates around social networks and not + individual blogs. It makes much more sense to publish something somewhere and to share + the link with the audience on the community or social network of your choice, than to maintain a blog + trying to keep your readers interested. + NoteHub should be the place, where you can publish your thoughts without hassle."] + [:td.one-third-column + [:h2 "How to Use?"] + "First create a new page using the markdown syntax. Now, besides just sharing the link, you can + view some rudimentary statistics by appending /stats to the note url: +
notehub.org/.../title/stats
+ If you want to export a note in the original Markdown format, append /export +
notehub.org/.../title/export
+ And if you want, you also can invert the color scheme by appending ?theme=dark to the note url. +
notehub.org/.../title?theme=dark
"] + [:td.one-third-column + [:h2 "For Geeks!"] + "NoteHub was an experiment and is implemented entirely in Clojure and ClojureScript. Its source code can + be found on GitHub. Look at the code to find some undocumented NoteHub features (or bugs) and — feel free to contribute! + (If you are interested in more detailed code overview, read the following note.) NoteHub's design + is intentionally kept extremelly simple and minimalistic, and should stay like this. + NoteHub's persistence layer bases on the key-value store redis. + Currently, NoteHub is hosted for free on Heroku. + Send your feedback and comments directly to @chmllr."]]])) ; New Note Page (defpage "/new" {} - (common/layout {:js true} "New Markdown Note" - [:div.central-element - (form-to [:post "/post-note"] - (hidden-field :session-key (get-flash-key)) - (hidden-field {:id :session-value} :session-value) - (text-area {:class :max-width} :draft "Loading...") - [:div#buttons.hidden - (submit-button {:style "float: left" - :class :button - :id :publish-button} "Publish") - [:button#preview-button.button {:type :button - :style "float: right"} "Preview"]])] - [:div#preview-start-line.hidden] - [:article#preview])) + (layout {:js true} "New Markdown Note" + [:div.central-element + (form-to [:post "/post-note"] + (hidden-field :session-key (get-flash-key)) + (hidden-field {:id :session-value} :session-value) + (text-area {:class :max-width} :draft "Loading...") + [:div#buttons.hidden + (submit-button {:style "float: left" + :class :button + :id :publish-button} "Publish") + [:button#preview-button.button {:type :button + :style "float: right"} "Preview"]])] + [:div#preview-start-line.hidden] + [:article#preview])) ; Display the note (defpage "/:year/:month/:day/:title" {:keys [year month day title theme header-font text-font] :as params} @@ -97,15 +127,15 @@ (defpage "/:year/:month/:day/:title/stat" {:keys [year month day title]} (let [views (get-views [year month day] title)] (if views - (common/layout "Statistics" - [:article.helvetica-neue - [:table {:style "width: 100%"} - [:tr - [:td "Published"] - [:td (interpose "-" [year month day])]] - [:tr - [:td "Article views"] - [:td views]]]]) + (layout "Statistics" + [:article.helvetica-neue + [:table {:style "width: 100%"} + [:tr + [:td "Published"] + [:td (interpose "-" [year month day])]] + [:tr + [:td "Article views"] + [:td views]]]]) (status 404 (get-page 404))))) ; New Note Posting @@ -124,7 +154,7 @@ (-> draft (split #"\n") first (sreplace " " "-") lower-case)) trim (fn [s] (apply str (drop-while #(= \- %) s))) title-uncut (-> untrimmed-line trim reverse trim reverse) - proposed-title (apply str (take (get-setting :max-title-length 80 #(Integer/parseInt %)) + proposed-title (apply str (take (get-setting :max-title-length #(Integer/parseInt %) 80) title-uncut)) date [year month day] title (first (drop-while #(note-exists? date %) diff --git a/test/NoteHub/test/storage.clj b/test/NoteHub/test/storage.clj index 046e141..9132fb1 100644 --- a/test/NoteHub/test/storage.clj +++ b/test/NoteHub/test/storage.clj @@ -10,7 +10,12 @@ (is (= (do (set-note date test-title test-note) (get-note date test-title)) - test-note))) + test-note)) + (is (= "1" (get-views date test-title))) + (is (= (do + (get-note date test-title) + (get-views date test-title)) + "2"))) (testing "of the note access" (is (not= (get-note date test-title) "any text"))) (testing "of note existence"