index.clj 6.39 KB
Newer Older
phil's avatar
phil committed
1
(ns ok.index
2
  "NOTE: this ns still has too much stuff in it"
phil's avatar
phil committed
3
  (:require [clojure.string :as s]
phil's avatar
phil committed
4
            [ok.layout :as layout]
5 6
            [ok.helper :refer [category-link]]
            [ok.helper :as helper]))
phil's avatar
phil committed
7

8
;; Returns the publish date or a fallback if no :date-published is
9 10
;; provided. This, however, is always a lie, because the
;; last-modified-time is whenever the page has been built.
11 12 13
(defn- date-of-post
  "Returns the last-updated-at date of a post"
  [post]
14
  (if (:date-published post)
15
    (.format (java.text.SimpleDateFormat. "yyyy-MM-dd")
16
             (:date-published post))
17 18 19 20 21
    (.format (java.text.SimpleDateFormat. "yyyy-MM-dd")
             (java.util.Date.
              (.lastModified
               (clojure.java.io/file
                (str "resources/" (:path post))))))))
22

23 24 25 26 27 28 29 30 31 32 33 34 35
(defn- ok-metadata
  []
  [:div {:itemscope true
         :itemprop "publisher"
         :itemtype "https://schema.org/Organization"}
   [:span
    [:meta {:itemprop "name"
            :content "200ok GmbH"}]]
   [:span {:itemscope true
           :itemprop "logo"
           :itemtype "https://schema.org/ImageObject"
           }
    [:meta {:itemprop "url"
phil's avatar
phil committed
36
            :content "http://200ok.ch/img/logo.png"}]]
37 38 39 40
   [:span
    [:meta {:itemprop "legalName"
            :content "200ok GmbH"}]]])

41
(defn- authors
42
  "Get author(s). If there are none, return '200ok'."
43
  [post]
44
  (s/split (or (:authors post) "200ok") #"\s*,\s*"))
45

Alain M. Lafon's avatar
Alain M. Lafon committed
46 47
(defn- author-section
  [post]
48
  (for [author (authors post)]
49 50
    [:section.author {:itemscope true
                      :itemprop "author"
51
                      :itemtype "https://schema.org/Person"}
52
     [:span {:itemprop "name"}
53
      author]]))
54 55 56 57

(defn- category-section
  [post]
  (if (:category post)
58
    [:li.category
59

60
     [:a {:href (category-link (:category post)) }
61
      (:category post)]]))
Alain M. Lafon's avatar
Alain M. Lafon committed
62 63 64

(defn- date-of-post-section
  [post]
65 66
  [:time {:itemprop "datePublished"}
   (date-of-post post)])
Alain M. Lafon's avatar
Alain M. Lafon committed
67 68 69 70

(defn- word-count-section
  [post]
  [:span {:itemprop "wordCount"}
71
   (:word-count post)])
Alain M. Lafon's avatar
Alain M. Lafon committed
72 73 74 75

(defn- time-to-read-section
  [post]
  [:span {:itemprop "timeRequired"}
76
   (:ttr post)])
Alain M. Lafon's avatar
Alain M. Lafon committed
77

78 79 80 81
(defn- subheader-post
  "Returns metadata of a post inside a :div.subheader"
  [post]
  [:div.subheader
phil's avatar
phil committed
82 83 84 85 86 87 88 89 90
   [:p.post-meta
    (date-of-post-section post)
    " - "
    (word-count-section post)
    " words"
    " - "
    (time-to-read-section post)
    " min read"
    (ok-metadata)]
91
   [:div.byline
92
    [:img.author-icon {:src "/img/author.svg"}]
phil's avatar
phil committed
93
    (author-section post)]
phil's avatar
phil committed
94
   ;; (category-section post)
95 96 97 98 99
   ])

(defn- separated-tags
  "Get tag(s). If there are none, return '200ok'."
  [post]
100 101
  (or (:tags post)
      ["200ok"]))
102

103 104 105 106 107
;; TODO: Refactor this to yield a unique keyword list
;; TODO: Find out how to yield the 'category' as focus keyword
(defn- tags
  "Renders tags and category into the footer of a post"
  [post]
108
  [:div.tags
phil's avatar
phil committed
109
   [:img.tag-icon {:src "/img/tag.svg"}]
110 111 112
   [:ul {:itemprop "keywords"}
    (category-section post)
    (for [tag (separated-tags post)]
113 114
      [:li.tag
       [:a {:href (str "/tags/" tag ".html")}
115
        (str "#" tag)]])]])
116

117
(defn full-post [post]
118
  [:div
119
   [:div.article-body {:itemprop "articleBody"} (:content post)]
120
   (tags post)])
121

122
(defn- preview-post
123 124
  "Returns the first 100 words of a post wrapped in a :section.
   Optionally with a link to 'Read more'"
125
  [post]
126
  [:section
127
   [:div.article-section {:itemprop "articleSection"}
phil's avatar
phil committed
128 129 130 131 132 133
    (as-> (:content post) %
      (s/split % #"<pre>")
      (first %)
      (s/split % #" ")
      (take 100 %)
      (s/join " " %))
134
    "..."
135
    (tags post)
136
    [:p
137
     [:a.read-more {:href (:permalink post)
phil's avatar
phil committed
138
                    :itemprop "url"}
139 140
      "Read more..."]]]])

141 142
(defn- image-meta-data
  "Add image meta data for the 200ok logo."
143
  [post]
144 145 146 147 148 149 150 151 152
  [:span {:itemscope true
          :itemprop "image"
          :itemtype "https://schema.org/ImageObject"}

   [:meta {:itemprop "height"
           :content "190"}]
   [:meta {:itemprop "width"
           :content "349"}]
   [:meta {:itemprop "url"
153
           :content (helper/logo-url)}]])
154

155
(defn render-post
156
  "Renders a post as :article"
157
  [post & {:keys [max]}]
158 159
  [:article.blog-post {:itemscope true
                       :itemtype "https://schema.org/BlogPosting"}
160
   [:h3.headline {:itemprop "headline"}
161
    [:a.nunito {:href (:permalink post)
phil's avatar
phil committed
162
                :itemprop "url"}
163
     (:title post)]]
164
   (subheader-post post)
165
   (image-meta-data post)
166
   (let [content (:content post)
167
         words (:word-count post)]
168 169
     (if (or (nil? max) (> max words))
       (full-post post)
170
       (preview-post post)))])
171 172 173

(defn- categories
  "Add categories to sidebar"
174
  [global-meta]
175
  [:div.sidebar
Alain M. Lafon's avatar
Alain M. Lafon committed
176
   [:div
177
    [:h5.categories "Categories"]
178
    [:ul
phil's avatar
phil committed
179 180
     (let [sorted (->> (:categories global-meta)
                       (map :category)
181
                       (map s/lower-case)
phil's avatar
phil committed
182 183 184
                       frequencies
                       (sort-by second)
                       reverse)]
Alain M. Lafon's avatar
Alain M. Lafon committed
185
       (map (fn [[category occurrences]]
186 187
              [:li [:a {:href (category-link category)}
                    (s/capitalize category)
188
                    [:span.badge occurrences]]])
Alain M. Lafon's avatar
Alain M. Lafon committed
189
            sorted))]]])
190

191 192 193 194 195 196 197 198 199 200 201 202


(defn- pagination
  [page]
  ;; TODO: Implement `:pages-count` within the `pagination` task of
  ;; Perun.
  (let [pages-count (if (:last-page page)
                      (Integer/parseInt (first (re-seq #"\d" (:last-page page))))
                      ;; There seems to be a bug in Perun. Sometimes
                      ;; `:last-page` isn't set. For all actually
                      ;; rendered pages is is, though.
                      0)]
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
    (if (> pages-count 0)
      [:div#pagination
       (if-not (= (:page page) 1)
         [:a.pager {:href (:first-page page)}
          "<<"])
       (if (:prev-page page)
         [:a.pager {:href (:prev-page page)}
          "<"])
       [:span#current-page.pager (str (:page page)
                                      " / "
                                      pages-count)]
       (if (:next-page page)
         [:a.pager {:href (:next-page page)}
          ">"])
       (if (not (= (:page page) pages-count))
         [:a.pager {:href (:last-page page)}
          ">>"])])))
220 221

(defn render [{global-meta :meta posts :entries page :entry}]
phil's avatar
phil committed
222
  (layout/blog global-meta
223
               posts
phil's avatar
phil committed
224 225 226
               [:main
                [:div.content {:id "content"}
                 (for [post posts]
227 228
                   (render-post post :max 100))
                 (pagination page)]
phil's avatar
phil committed
229
                (categories global-meta)]))