Created
October 6, 2014 19:23
-
-
Save danneu/10280fe5e4079634917c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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