Skip to content

Commit 6758312

Browse files
Merge pull request #49 from clj-codes/feat/adds-social-dashboards-components
Adds Query Components like Top Author and Recently Updated
2 parents 9af62bd + 5fe1d83 commit 6758312

File tree

11 files changed

+381
-55
lines changed

11 files changed

+381
-55
lines changed

shadow-cljs.edn

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
:closure-defines {codes.clj.docs.frontend.config/BASE_URL #shadow/env ["BASE_URL"]
1818
codes.clj.docs.frontend.config/CLIENT_ID #shadow/env ["CLIENT_ID"]
1919
codes.clj.docs.frontend.config/REDIRECT_URI #shadow/env ["REDIRECT_URI"]
20-
codes.clj.docs.frontend.config/GA_TAG_ID #shadow/env ["GA_TAG_ID"]}}
20+
codes.clj.docs.frontend.config/GA_TAG_ID #shadow/env ["GA_TAG_ID"]
21+
codes.clj.docs.frontend.config/SHOW_DASHBOARD #shadow/env ["SHOW_DASHBOARD"]}}
2122
:build-hooks [(codes.clj.docs.frontend.dev.shadow.hooks/hashed-files
2223
["resources/public/css/app.css"
2324
"resources/public/js/core.js"])
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
(ns codes.clj.docs.frontend.adapters.time)
2+
3+
(def MINUTE 60)
4+
(def HOUR (* MINUTE 60))
5+
(def DAY (* HOUR 24))
6+
(def WEEK (* DAY 7))
7+
(def MONTH (* DAY 30))
8+
(def YEAR (* DAY 365))
9+
10+
(defn time-since [date now]
11+
(let [seconds-ago (-> (- now date)
12+
(/ 1000)
13+
Math/round)
14+
{:keys [divisor unit]} (cond
15+
(< seconds-ago MINUTE) {:divisor 1 :unit "second"}
16+
(< seconds-ago HOUR) {:divisor MINUTE :unit "minute"}
17+
(< seconds-ago DAY) {:divisor HOUR :unit "hour"}
18+
(< seconds-ago WEEK) {:divisor DAY :unit "day"}
19+
(< seconds-ago MONTH) {:divisor WEEK :unit "week"}
20+
(< seconds-ago YEAR) {:divisor MONTH :unit "month"}
21+
:else {:divisor YEAR :unit "year"})
22+
value (-> (/ seconds-ago divisor)
23+
Math/floor)
24+
plural? (if (> value 1) "s" "")]
25+
(str value " " unit plural? " ago")))

src/codes/clj/docs/frontend/config.cljs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@
2121
(goog-define CLIENT_ID "46d86692f00ed9c613a1")
2222
(goog-define REDIRECT_URI "https://docs.clj.codes/github-callback")
2323
(goog-define GA_TAG_ID "")
24+
(goog-define SHOW_DASHBOARD "true")
2425

2526
(def config
2627
(let [debug? goog.DEBUG]
2728
{:debug? debug?
29+
:show-dashboard (= SHOW_DASHBOARD "true")
2830
:ga-tag-id (if debug?
2931
""
3032
GA_TAG_ID)
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
(ns codes.clj.docs.frontend.panels.dashboards.components
2+
(:require ["@mantine/core" :refer [Alert Anchor Avatar Box Grid Group
3+
Indicator LoadingOverlay SimpleGrid Text
4+
Title Tooltip]]
5+
["@tabler/icons-react" :refer [IconInfoCircle]]
6+
[clojure.string :as str]
7+
[codes.clj.docs.frontend.adapters.time :as adapters.time]
8+
[codes.clj.docs.frontend.infra.helix :refer [defnc]]
9+
[helix.core :refer [$]]))
10+
11+
(defnc latest-interactions-list [{:keys [value loading? error]}]
12+
($ SimpleGrid {:cols 1 :data-testid "latest-interactions-list"}
13+
($ Title {:order 1} "Recently Updated")
14+
15+
(if error
16+
($ Alert {:variant "light" :color "red" :radius "md" :title "Error" :icon ($ IconInfoCircle)}
17+
(str error))
18+
19+
($ Box {:pos "relative"}
20+
($ LoadingOverlay {:visible loading? :zIndex 1000
21+
:loaderProps #js {:type "dots"}
22+
:overlayProps #js {:radius "sm" :blur 2}})
23+
($ SimpleGrid {:cols 2}
24+
(map
25+
(fn [{:keys [note-id example-id see-also-id definition-id author created-at]}]
26+
(let [id (str "latest" (or note-id example-id see-also-id))
27+
action (cond
28+
note-id "authored a note for"
29+
example-id "authored an example for"
30+
see-also-id "added a see also on")
31+
definition (str/replace definition-id #"/0$" "")
32+
ago (adapters.time/time-since created-at (.now js/Date))
33+
{:keys [login account-source avatar-url]} author]
34+
($ Group {:key id :id id
35+
:wrap "nowrap"
36+
:className "interaction-text"}
37+
($ Anchor {:href (str "/author/" login "/" account-source)}
38+
($ Avatar {:src avatar-url}))
39+
($ Text {:size "sm"} login " "
40+
($ Text {:component "span"}
41+
action " "
42+
($ Anchor {:href definition-id} definition)
43+
" " ago ".")))))
44+
value))))))
45+
46+
(defnc top-authors-list [{:keys [value loading? error]}]
47+
($ SimpleGrid {:cols 1 :data-testid "top-authors-list"}
48+
($ Title {:order 1} "Top Authors")
49+
50+
(if error
51+
($ Alert {:variant "light" :color "red" :radius "md" :title "Error" :icon ($ IconInfoCircle)}
52+
(str error))
53+
54+
($ Group {:pos "relative"}
55+
($ LoadingOverlay {:visible loading? :zIndex 1000
56+
:loaderProps #js {:type "dots"}
57+
:overlayProps #js {:radius "sm" :blur 2}})
58+
($ Grid {:grow false :gutter "lg"}
59+
(map
60+
(fn [{:keys [author-id login account-source interactions avatar-url]}]
61+
($ (.-Col Grid) {:key (str "top" author-id)
62+
:span "lg"}
63+
($ Indicator {:withBorder true :inline true :label interactions :size 16 :position "bottom-end"}
64+
($ Anchor {:href (str "/author/" login "/" account-source)
65+
:className "author-interaction-anchor"}
66+
($ Tooltip {:label (str login " with " interactions " interactions")
67+
:withArrow true}
68+
($ Avatar {:size "md" :src avatar-url}))))))
69+
value))))))
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
(ns codes.clj.docs.frontend.panels.dashboards.state
2+
(:require [codes.clj.docs.frontend.infra.http :as http]
3+
[town.lilac.flex :as flex]
4+
[town.lilac.flex.promise :as flex.promise]))
5+
6+
(def latest-interactions-fetch
7+
(flex.promise/resource
8+
#(-> (http/request! {:path "social/query/latest-interactions"
9+
:method :get
10+
:query-params {:limit 15}})
11+
(.then (fn [response]
12+
(-> response
13+
:body
14+
(subvec 0 10))))
15+
(.catch (fn [error]
16+
(js/console.error error)
17+
(throw error))))))
18+
19+
(def latest-interactions-response
20+
(flex/signal {:state @(:state latest-interactions-fetch)
21+
:value @(:value latest-interactions-fetch)
22+
:error @(:error latest-interactions-fetch)
23+
:loading? @(:loading? latest-interactions-fetch)}))
24+
25+
(def top-authors-fetch
26+
(flex.promise/resource
27+
#(-> (http/request! {:path "social/query/top-authors"
28+
:method :get
29+
:query-params {:limit 100}})
30+
(.then (fn [response]
31+
(:body response)))
32+
(.catch (fn [error]
33+
(js/console.error error)
34+
(throw error))))))
35+
36+
(def top-authors-response
37+
(flex/signal {:state @(:state top-authors-fetch)
38+
:value @(:value top-authors-fetch)
39+
:error @(:error top-authors-fetch)
40+
:loading? @(:loading? top-authors-fetch)}))
41+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
(ns codes.clj.docs.frontend.panels.dashboards.view
2+
(:require ["@mantine/core" :refer [Container Space]]
3+
[codes.clj.docs.frontend.infra.flex.hook :refer [use-flex]]
4+
[codes.clj.docs.frontend.infra.helix :refer [defnc]]
5+
[codes.clj.docs.frontend.panels.dashboards.components :as components]
6+
[codes.clj.docs.frontend.panels.dashboards.state :as state]
7+
[helix.core :refer [$]]
8+
[helix.hooks :refer [use-effect]]))
9+
10+
(defnc all []
11+
(let [latest-interactions-response (use-flex state/latest-interactions-response)
12+
top-authors-response (use-flex state/top-authors-response)]
13+
14+
(use-effect
15+
:once
16+
(state/latest-interactions-fetch)
17+
(state/top-authors-fetch))
18+
19+
($ Container {:p "sm"}
20+
21+
($ components/top-authors-list {:& top-authors-response})
22+
23+
($ Space {:h "xl"})
24+
25+
($ components/latest-interactions-list {:& latest-interactions-response}))))
Lines changed: 62 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,70 @@
11
(ns codes.clj.docs.frontend.panels.home.view
22
(:require ["@mantine/core" :refer [Anchor Card Container Grid Text Title]]
33
[codes.clj.docs.frontend.infra.helix :refer [defnc]]
4+
[codes.clj.docs.frontend.infra.system.state :as system.state]
5+
[codes.clj.docs.frontend.panels.dashboards.view :as dashboards.view]
46
[helix.core :refer [$]]
57
[helix.dom :as dom]))
68

79
(defnc home []
8-
($ Container {:p "sm"}
9-
($ Grid {:id "why"}
10-
($ (-> Grid .-Col) {:span 12}
11-
(dom/section
12-
($ Title {:order 1}
13-
($ Anchor {:component "a"
14-
:variant "gradient"
15-
:gradient #js {:from "blue" :to "cyan"}
16-
:inherit true
17-
:underline "never"
18-
:href "https://docs.clj.codes"}
19-
"Docs.clj.codes")
20-
" is a modern community-powered documentation and examples repository for the "
21-
($ Anchor {:component "a"
22-
:variant "gradient"
23-
:gradient #js {:from "cyan" :to "green"}
24-
:inherit true
25-
:underline "never"
26-
:href "https://clojure.org"}
27-
"Clojure programming language"))))
28-
($ (-> Grid .-Col) {:span 12}
29-
($ Text {:size "xl" :style #js {:paddingTop "1rem"}}
30-
"Built on a tech stack featuring "
31-
($ Text {:component "a" :href "https://github.com/lilactown/helix" :inherit true :fw 700} "helix")
32-
" and "
33-
($ Text {:component "a" :href "https://github.com/lilactown/flex" :inherit true :fw 700} "flex")
34-
" on the frontend and powered by postgres and "
35-
($ Text {:component "a" :href "https://github.com/juji-io/datalevin" :inherit true :fw 700} "datalevin")
36-
" on the backend.")))
10+
(let [show-dashboard (-> @system.state/components :config :show-dashboard)]
11+
($ Container {:p "sm"}
12+
($ Grid {:id "why"}
13+
($ (-> Grid .-Col) {:span 12}
14+
(dom/section
15+
($ Title {:order 1}
16+
($ Anchor {:component "a"
17+
:variant "gradient"
18+
:gradient #js {:from "blue" :to "cyan"}
19+
:inherit true
20+
:underline "never"
21+
:href "https://docs.clj.codes"}
22+
"Docs.clj.codes")
23+
" is a modern community-powered documentation and examples repository for the "
24+
($ Anchor {:component "a"
25+
:variant "gradient"
26+
:gradient #js {:from "cyan" :to "green"}
27+
:inherit true
28+
:underline "never"
29+
:href "https://clojure.org"}
30+
"Clojure programming language"))))
3731

38-
($ Grid {:id "features"}
39-
($ (-> Grid .-Col) {:span 12}
40-
(dom/section
41-
(dom/h2 "Key Features")
42-
($ Grid {:grow true :gutter "lg"}
43-
($ (-> Grid .-Col) {:span 4}
44-
($ Card {:shadow "sm" :padding "lg" :radius "lg" :style #js {:minHeight "14rem"}}
45-
(dom/h3 "Comprehensive Documentation")
46-
(dom/p "Explore in-depth documentation for a vast array of Clojure libraries.")))
47-
($ (-> Grid .-Col) {:span 4}
48-
($ Card {:shadow "sm" :padding "lg" :radius "lg" :style #js {:minHeight "14rem"}}
49-
(dom/h3 "Seamless Git Integration")
50-
(dom/p "Documentation is directly parsed from their Git repositories.")))
51-
($ (-> Grid .-Col) {:span 4}
52-
($ Card {:shadow "sm" :padding "lg" :radius "lg" :style #js {:minHeight "14rem"}}
53-
(dom/h3 "Social Interaction")
54-
(dom/p "Join a vibrant community of Clojure enthusiasts.")))
55-
($ (-> Grid .-Col) {:span 4}
56-
($ Card {:shadow "sm" :padding "lg" :radius "lg" :style #js {:minHeight "14rem"}}
57-
(dom/h3 "Extensive Search Capabilities")
58-
(dom/p "Harness the power of Datalevin for lightning-fast full-text search.")))
59-
($ (-> Grid .-Col) {:span 4}
60-
($ Card {:shadow "sm" :padding "lg" :radius "lg" :style #js {:minHeight "14rem"}}
61-
(dom/h3 "Easy Contribution")
62-
(dom/p "Become a part of the documentation ecosystem.")))))))))
32+
(when show-dashboard
33+
($ (-> Grid .-Col) {:span 12}
34+
($ dashboards.view/all)))
35+
36+
($ (-> Grid .-Col) {:span 12}
37+
($ Text {:size "xl" :style #js {:paddingTop "1rem"}}
38+
"Built on a tech stack featuring "
39+
($ Text {:component "a" :href "https://github.com/lilactown/helix" :inherit true :fw 700} "helix")
40+
" and "
41+
($ Text {:component "a" :href "https://github.com/lilactown/flex" :inherit true :fw 700} "flex")
42+
" on the frontend and powered by postgres and "
43+
($ Text {:component "a" :href "https://github.com/juji-io/datalevin" :inherit true :fw 700} "datalevin")
44+
" on the backend.")))
45+
46+
($ Grid {:id "features"}
47+
($ (-> Grid .-Col) {:span 12}
48+
(dom/section
49+
(dom/h2 "Key Features")
50+
($ Grid {:grow true :gutter "lg"}
51+
($ (-> Grid .-Col) {:span 4}
52+
($ Card {:shadow "sm" :padding "lg" :radius "lg" :style #js {:minHeight "14rem"}}
53+
(dom/h3 "Comprehensive Documentation")
54+
(dom/p "Explore in-depth documentation for a vast array of Clojure libraries.")))
55+
($ (-> Grid .-Col) {:span 4}
56+
($ Card {:shadow "sm" :padding "lg" :radius "lg" :style #js {:minHeight "14rem"}}
57+
(dom/h3 "Seamless Git Integration")
58+
(dom/p "Documentation is directly parsed from their Git repositories.")))
59+
($ (-> Grid .-Col) {:span 4}
60+
($ Card {:shadow "sm" :padding "lg" :radius "lg" :style #js {:minHeight "14rem"}}
61+
(dom/h3 "Social Interaction")
62+
(dom/p "Join a vibrant community of Clojure enthusiasts.")))
63+
($ (-> Grid .-Col) {:span 4}
64+
($ Card {:shadow "sm" :padding "lg" :radius "lg" :style #js {:minHeight "14rem"}}
65+
(dom/h3 "Extensive Search Capabilities")
66+
(dom/p "Harness the power of Datalevin for lightning-fast full-text search.")))
67+
($ (-> Grid .-Col) {:span 4}
68+
($ Card {:shadow "sm" :padding "lg" :radius "lg" :style #js {:minHeight "14rem"}}
69+
(dom/h3 "Easy Contribution")
70+
(dom/p "Become a part of the documentation ecosystem."))))))))))

src/codes/clj/docs/frontend/routes.cljs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
(:require [codes.clj.docs.frontend.infra.auth.github.view :as auth.github.view]
33
[codes.clj.docs.frontend.panels.author.state :as author.state]
44
[codes.clj.docs.frontend.panels.author.view :as author.view]
5+
[codes.clj.docs.frontend.panels.dashboards.view :as dashboards.view]
56
[codes.clj.docs.frontend.panels.definition.state :as definition.state]
67
[codes.clj.docs.frontend.panels.definition.view :as definition.view]
78
[codes.clj.docs.frontend.panels.definitions.state :as definitions.state]
@@ -56,6 +57,13 @@
5657
(set-title! "Projects - docs.clj.codes")
5758
(projects.state/document-projects-fetch))}]}]
5859

60+
["dashboards"
61+
{:name :dashboards
62+
:view dashboards.view/all
63+
:link-text "dashboards"
64+
:controllers [{:start (fn [& _params]
65+
(set-title! "Dashboards - docs.clj.codes"))}]}]
66+
5967
["search"
6068
{:name :search
6169
:view search.view/search-page
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
(ns codes.clj.docs.frontend.test.adapters.time-test
2+
(:require [cljs.test :refer [deftest is use-fixtures]]
3+
[codes.clj.docs.frontend.adapters.time :as adapters]
4+
[codes.clj.docs.frontend.test.aux.init :refer [sync-setup]]
5+
[matcher-combinators.test :refer [match?]]))
6+
7+
(use-fixtures :each sync-setup)
8+
9+
(deftest time-since-test
10+
(let [created-at #inst "2024-03-09T00:00:00.000000000-00:00"]
11+
(is (match? "0 second ago"
12+
(adapters/time-since created-at #inst "2024-03-09T00:00:00.000000000-00:00")))
13+
14+
(is (match? "1 second ago"
15+
(adapters/time-since created-at #inst "2024-03-09T00:00:01.000000000-00:00")))
16+
(is (match? "2 seconds ago"
17+
(adapters/time-since created-at #inst "2024-03-09T00:00:02.000000000-00:00")))
18+
19+
(is (match? "1 minute ago"
20+
(adapters/time-since created-at #inst "2024-03-09T00:01:00.000000000-00:00")))
21+
(is (match? "2 minutes ago"
22+
(adapters/time-since created-at #inst "2024-03-09T00:02:00.000000000-00:00")))
23+
24+
(is (match? "1 hour ago"
25+
(adapters/time-since created-at #inst "2024-03-09T01:00:00.000000000-00:00")))
26+
(is (match? "2 hours ago"
27+
(adapters/time-since created-at #inst "2024-03-09T02:00:00.000000000-00:00")))
28+
29+
(is (match? "1 day ago"
30+
(adapters/time-since created-at #inst "2024-03-10T00:00:00.000000000-00:00")))
31+
(is (match? "2 days ago"
32+
(adapters/time-since created-at #inst "2024-03-11T00:00:00.000000000-00:00")))
33+
34+
(is (match? "1 week ago"
35+
(adapters/time-since created-at #inst "2024-03-16T00:00:00.000000000-00:00")))
36+
(is (match? "2 weeks ago"
37+
(adapters/time-since created-at #inst "2024-03-26T00:00:00.000000000-00:00")))
38+
39+
(is (match? "1 month ago"
40+
(adapters/time-since created-at #inst "2024-04-09T00:00:00.000000000-00:00")))
41+
(is (match? "2 months ago"
42+
(adapters/time-since created-at #inst "2024-05-09T00:00:00.000000000-00:00")))
43+
44+
(is (match? "1 year ago"
45+
(adapters/time-since created-at #inst "2025-03-09T00:00:00.000000000-00:00")))
46+
(is (match? "2 years ago"
47+
(adapters/time-since created-at #inst "2026-03-09T00:00:00.000000000-00:00")))))
48+
49+

0 commit comments

Comments
 (0)