(ns NoteHub.views.pages
(: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]
[noir.session :only [flash-put! flash-get]]
[noir.response :only [redirect status]]
[noir.core :only [defpage render]]
[noir.util.crypt :only [encrypt]]
[noir.statuses]
[noir.fetch.remotes])
(:import
[java.util Calendar]
[org.pegdown PegDownProcessor]))
; Markdown -> HTML mapper
(defn md-to-html [md-text]
(.markdownToHtml (PegDownProcessor.) md-text))
; Creates a random session number
(defn- get-flash-key []
(let [k (encrypt (str (rand-int Integer/MAX_VALUE)))]
(do (flash-put! k true)
(print-str k))))
; Converts given markdwon to html and wraps with layout
(defn- wrap [params md-text]
(if md-text
(let [title (-?> md-text (split #"\n") first (sreplace #"[_\*#]" ""))]
(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
(layout message
[:article
[:h1 message]])))
; Sets a message for each corresponding HTTP status
(page-setter 404 "Nothing Found.")
(page-setter 400 "Bad request.")
(page-setter 500 "OMG, Server Exploded.")
; Routes
; ======
; This function answers to a AJAX request: it gets a sesion key and markdown text.
; IT return html version of the provided markdown and a new session key
(defremote get-preview-md [session-key md]
(when (flash-get session-key)
{:session-key (get-flash-key)
:preview (md-to-html md)}))
; Landing Page
(defpage "/" {}
(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/statsIf you want to export a note in the original Markdown format, append
/export
notehub.org/.../title/exportAnd 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" {} (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} (wrap (select-keys params [:theme :header-font :text-font]) (get-note [year month day] title))) ; Provides Markdown of the specified note (defpage "/:year/:month/:day/:title/export" {:keys [year month day title]} (let [md-text (get-note [year month day] title)] (if md-text md-text (status 404 (get-page 404))))) ; Provides the number of views of the specified note (defpage "/:year/:month/:day/:title/stat" {:keys [year month day title]} (let [views (get-views [year month day] title)] (if 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 (defpage [:post "/post-note"] {:keys [draft session-key session-value]} (let [valid-session (flash-get session-key) ; it was posted from a newly generated form valid-draft (not (empty? draft)) ; the note is non-empty valid-hash (try (= (Short/parseShort session-value) ; the hash code is correct (lib/hash #(.codePointAt % 0) (str draft session-key))) (catch Exception e nil))] ; check whether the new note can be added (if (and valid-session valid-draft valid-hash) (let [[year month day] (map #(+ (second %) (.get (Calendar/getInstance) (first %))) {Calendar/YEAR 0, Calendar/MONTH 1, Calendar/DAY_OF_MONTH 0}) untrimmed-line (filter #(or (= \- %) (Character/isLetterOrDigit %)) (-> 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 #(Integer/parseInt %) 80) title-uncut)) date [year month day] title (first (drop-while #(note-exists? date %) (cons proposed-title (map #(str proposed-title "-" (+ 2 %)) (range)))))] (do (set-note date title draft) ; TODO: the redirect is broken if title contains UTF chars (redirect (apply str (interpose "/" ["" year month day title]))))) (status 400 ""))))