Skip to content

Instantly share code, notes, and snippets.

@danneu
Created October 6, 2014 19:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save danneu/10280fe5e4079634917c to your computer and use it in GitHub Desktop.
Save danneu/10280fe5e4079634917c to your computer and use it in GitHub Desktop.
(ns x.timeago
(:require-macros
[cljs.core.async.macros :refer [go-loop]])
(:require
[om.core :as om :include-macros true]
[om.dom :as dom :include-macros true]
[clojure.string :as str]
[goog.string :as gstr]
[cljs.core.async :refer (<! >! chan timeout put!)]
[goog.string.format]))
;;;; A quick impl of http://timeago.yarp.com/
;;;;
;;;; om/build a timeago-component initialized with a js/Date. it'll create
;;;; a span with a distance-in-words that ages in real time.
;;;;
;;;; Ex: "<1 min ago", "4 hrs ago", ...
(def opts
{:suffix-ago "ago"
:seconds "<1 min"
:minute "1 min"
:minutes "%d mins"
:hour "1 hr"
:hours "%d hrs"
:day "1 day"
:days "%d days"
:month "1 month"
:months "%d months"
:year "1 year"
:years "%d years"})
(defn in-words [ms-delta]
(let [seconds (-> (Math/abs ms-delta) (/ 1000) (.toFixed 2))
minutes (/ seconds 60)
hours (/ minutes 60)
days (/ hours 24)
years (/ days 365)]
(let [words
(cond
(< seconds 45) (gstr/format (:seconds opts) seconds)
(< seconds 90) (gstr/format (:minute opts) 1)
(< minutes 45) (gstr/format (:minutes opts) (Math/round minutes))
(< minutes 90) (gstr/format (:hour opts) 1)
(< hours 24) (gstr/format (:hours opts) (Math/round hours))
(< hours 42) (gstr/format (:day opts) 1)
(< days 30) (gstr/format (:days opts) (Math/round days))
(< days 45) (gstr/format (:month opts) 1)
(< days 365) (gstr/format (:months opts) (Math/round (/ days 30)))
(< years 1.5) (gstr/format (:year opts) 1)
:else (gstr/format (:years opts) (Math/round years)))]
(str words " " (:suffix-ago opts)))))
(defn parse [iso8601]
(-> iso8601
(str/replace #"\.\d+" "") ;; remove milliseconds
(str/replace #"-" "/")
(str/replace #"-" "/")
(str/replace #"T" " ")
(str/replace #"Z " "UTC")
(str/replace #"([\+\-]\d\d)\:?(\d\d)" "$1$2") ; -04:00 -> -0400
(str/replace #"([\+\-]\d\d)$" " $100") ; +09 -> +0900
))
;; om/build it with a no-op cursor and initialize its state with
;; {:date <js/Date object>}
;; - Each component needs a key
;;
;; Ex:
;; (apply dom/ul nil
;; (for [msg msgs]
;; (dom/li #js{:key (:msg/id msg)}
;; (om/build timeago-component {}
;; (:init-state {:date (:msg/created msg)})))))
(defn timeago-component [app owner]
(reify
om/IInitState
(init-state [_]
{:poison-ch (chan)})
om/IWillMount
(will-mount [_]
(let [poison-ch (om/get-state owner :poison-ch)]
(go-loop [delta-ms (- (.getTime (js/Date.))
(.getTime (om/get-state owner :date)))]
(om/set-state! owner :words (in-words delta-ms))
(let [[ch v] [(<! (timeout 1000)) poison-ch]]
(if (= ch poison-ch)
:done
(recur (+ 1000 delta-ms)))))))
om/IWillUnmount
(will-unmount [_]
(put! (om/get-state owner :poison-ch) :stop))
om/IRenderState
(render-state [_ {:keys [words date] :as state}]
(dom/span #js{:title (parse (.toISOString date))} words))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment