Skip to content

Adds Query Components like Top Author and Recently Updated #49

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion shadow-cljs.edn
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
:closure-defines {codes.clj.docs.frontend.config/BASE_URL #shadow/env ["BASE_URL"]
codes.clj.docs.frontend.config/CLIENT_ID #shadow/env ["CLIENT_ID"]
codes.clj.docs.frontend.config/REDIRECT_URI #shadow/env ["REDIRECT_URI"]
codes.clj.docs.frontend.config/GA_TAG_ID #shadow/env ["GA_TAG_ID"]}}
codes.clj.docs.frontend.config/GA_TAG_ID #shadow/env ["GA_TAG_ID"]
codes.clj.docs.frontend.config/SHOW_DASHBOARD #shadow/env ["SHOW_DASHBOARD"]}}
:build-hooks [(codes.clj.docs.frontend.dev.shadow.hooks/hashed-files
["resources/public/css/app.css"
"resources/public/js/core.js"])
Expand Down
25 changes: 25 additions & 0 deletions src/codes/clj/docs/frontend/adapters/time.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
(ns codes.clj.docs.frontend.adapters.time)

(def MINUTE 60)
(def HOUR (* MINUTE 60))
(def DAY (* HOUR 24))
(def WEEK (* DAY 7))
(def MONTH (* DAY 30))
(def YEAR (* DAY 365))

(defn time-since [date now]
(let [seconds-ago (-> (- now date)
(/ 1000)
Math/round)
{:keys [divisor unit]} (cond
(< seconds-ago MINUTE) {:divisor 1 :unit "second"}
(< seconds-ago HOUR) {:divisor MINUTE :unit "minute"}
(< seconds-ago DAY) {:divisor HOUR :unit "hour"}
(< seconds-ago WEEK) {:divisor DAY :unit "day"}
(< seconds-ago MONTH) {:divisor WEEK :unit "week"}
(< seconds-ago YEAR) {:divisor MONTH :unit "month"}
:else {:divisor YEAR :unit "year"})
value (-> (/ seconds-ago divisor)
Math/floor)
plural? (if (> value 1) "s" "")]
(str value " " unit plural? " ago")))
2 changes: 2 additions & 0 deletions src/codes/clj/docs/frontend/config.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@
(goog-define CLIENT_ID "46d86692f00ed9c613a1")
(goog-define REDIRECT_URI "https://docs.clj.codes/github-callback")
(goog-define GA_TAG_ID "")
(goog-define SHOW_DASHBOARD "true")

(def config
(let [debug? goog.DEBUG]
{:debug? debug?
:show-dashboard (= SHOW_DASHBOARD "true")
:ga-tag-id (if debug?
""
GA_TAG_ID)
Expand Down
69 changes: 69 additions & 0 deletions src/codes/clj/docs/frontend/panels/dashboards/components.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
(ns codes.clj.docs.frontend.panels.dashboards.components
(:require ["@mantine/core" :refer [Alert Anchor Avatar Box Grid Group
Indicator LoadingOverlay SimpleGrid Text
Title Tooltip]]
["@tabler/icons-react" :refer [IconInfoCircle]]
[clojure.string :as str]
[codes.clj.docs.frontend.adapters.time :as adapters.time]
[codes.clj.docs.frontend.infra.helix :refer [defnc]]
[helix.core :refer [$]]))

(defnc latest-interactions-list [{:keys [value loading? error]}]
($ SimpleGrid {:cols 1 :data-testid "latest-interactions-list"}
($ Title {:order 1} "Recently Updated")

(if error
($ Alert {:variant "light" :color "red" :radius "md" :title "Error" :icon ($ IconInfoCircle)}
(str error))

($ Box {:pos "relative"}
($ LoadingOverlay {:visible loading? :zIndex 1000
:loaderProps #js {:type "dots"}
:overlayProps #js {:radius "sm" :blur 2}})
($ SimpleGrid {:cols 2}
(map
(fn [{:keys [note-id example-id see-also-id definition-id author created-at]}]
(let [id (str "latest" (or note-id example-id see-also-id))
action (cond
note-id "authored a note for"
example-id "authored an example for"
see-also-id "added a see also on")
definition (str/replace definition-id #"/0$" "")
ago (adapters.time/time-since created-at (.now js/Date))
{:keys [login account-source avatar-url]} author]
($ Group {:key id :id id
:wrap "nowrap"
:className "interaction-text"}
($ Anchor {:href (str "/author/" login "/" account-source)}
($ Avatar {:src avatar-url}))
($ Text {:size "sm"} login " "
($ Text {:component "span"}
action " "
($ Anchor {:href definition-id} definition)
" " ago ".")))))
value))))))

(defnc top-authors-list [{:keys [value loading? error]}]
($ SimpleGrid {:cols 1 :data-testid "top-authors-list"}
($ Title {:order 1} "Top Authors")

(if error
($ Alert {:variant "light" :color "red" :radius "md" :title "Error" :icon ($ IconInfoCircle)}
(str error))

($ Group {:pos "relative"}
($ LoadingOverlay {:visible loading? :zIndex 1000
:loaderProps #js {:type "dots"}
:overlayProps #js {:radius "sm" :blur 2}})
($ Grid {:grow false :gutter "lg"}
(map
(fn [{:keys [author-id login account-source interactions avatar-url]}]
($ (.-Col Grid) {:key (str "top" author-id)
:span "lg"}
($ Indicator {:withBorder true :inline true :label interactions :size 16 :position "bottom-end"}
($ Anchor {:href (str "/author/" login "/" account-source)
:className "author-interaction-anchor"}
($ Tooltip {:label (str login " with " interactions " interactions")
:withArrow true}
($ Avatar {:size "md" :src avatar-url}))))))
value))))))
41 changes: 41 additions & 0 deletions src/codes/clj/docs/frontend/panels/dashboards/state.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
(ns codes.clj.docs.frontend.panels.dashboards.state
(:require [codes.clj.docs.frontend.infra.http :as http]
[town.lilac.flex :as flex]
[town.lilac.flex.promise :as flex.promise]))

(def latest-interactions-fetch
(flex.promise/resource
#(-> (http/request! {:path "social/query/latest-interactions"
:method :get
:query-params {:limit 15}})
(.then (fn [response]
(-> response
:body
(subvec 0 10))))
(.catch (fn [error]
(js/console.error error)
(throw error))))))

(def latest-interactions-response
(flex/signal {:state @(:state latest-interactions-fetch)
:value @(:value latest-interactions-fetch)
:error @(:error latest-interactions-fetch)
:loading? @(:loading? latest-interactions-fetch)}))

(def top-authors-fetch
(flex.promise/resource
#(-> (http/request! {:path "social/query/top-authors"
:method :get
:query-params {:limit 100}})
(.then (fn [response]
(:body response)))
(.catch (fn [error]
(js/console.error error)
(throw error))))))

(def top-authors-response
(flex/signal {:state @(:state top-authors-fetch)
:value @(:value top-authors-fetch)
:error @(:error top-authors-fetch)
:loading? @(:loading? top-authors-fetch)}))

25 changes: 25 additions & 0 deletions src/codes/clj/docs/frontend/panels/dashboards/view.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
(ns codes.clj.docs.frontend.panels.dashboards.view
(:require ["@mantine/core" :refer [Container Space]]
[codes.clj.docs.frontend.infra.flex.hook :refer [use-flex]]
[codes.clj.docs.frontend.infra.helix :refer [defnc]]
[codes.clj.docs.frontend.panels.dashboards.components :as components]
[codes.clj.docs.frontend.panels.dashboards.state :as state]
[helix.core :refer [$]]
[helix.hooks :refer [use-effect]]))

(defnc all []
(let [latest-interactions-response (use-flex state/latest-interactions-response)
top-authors-response (use-flex state/top-authors-response)]

(use-effect
:once
(state/latest-interactions-fetch)
(state/top-authors-fetch))

($ Container {:p "sm"}

($ components/top-authors-list {:& top-authors-response})

($ Space {:h "xl"})

($ components/latest-interactions-list {:& latest-interactions-response}))))
116 changes: 62 additions & 54 deletions src/codes/clj/docs/frontend/panels/home/view.cljs
Original file line number Diff line number Diff line change
@@ -1,62 +1,70 @@
(ns codes.clj.docs.frontend.panels.home.view
(:require ["@mantine/core" :refer [Anchor Card Container Grid Text Title]]
[codes.clj.docs.frontend.infra.helix :refer [defnc]]
[codes.clj.docs.frontend.infra.system.state :as system.state]
[codes.clj.docs.frontend.panels.dashboards.view :as dashboards.view]
[helix.core :refer [$]]
[helix.dom :as dom]))

(defnc home []
($ Container {:p "sm"}
($ Grid {:id "why"}
($ (-> Grid .-Col) {:span 12}
(dom/section
($ Title {:order 1}
($ Anchor {:component "a"
:variant "gradient"
:gradient #js {:from "blue" :to "cyan"}
:inherit true
:underline "never"
:href "https://docs.clj.codes"}
"Docs.clj.codes")
" is a modern community-powered documentation and examples repository for the "
($ Anchor {:component "a"
:variant "gradient"
:gradient #js {:from "cyan" :to "green"}
:inherit true
:underline "never"
:href "https://clojure.org"}
"Clojure programming language"))))
($ (-> Grid .-Col) {:span 12}
($ Text {:size "xl" :style #js {:paddingTop "1rem"}}
"Built on a tech stack featuring "
($ Text {:component "a" :href "https://github.com/lilactown/helix" :inherit true :fw 700} "helix")
" and "
($ Text {:component "a" :href "https://github.com/lilactown/flex" :inherit true :fw 700} "flex")
" on the frontend and powered by postgres and "
($ Text {:component "a" :href "https://github.com/juji-io/datalevin" :inherit true :fw 700} "datalevin")
" on the backend.")))
(let [show-dashboard (-> @system.state/components :config :show-dashboard)]
($ Container {:p "sm"}
($ Grid {:id "why"}
($ (-> Grid .-Col) {:span 12}
(dom/section
($ Title {:order 1}
($ Anchor {:component "a"
:variant "gradient"
:gradient #js {:from "blue" :to "cyan"}
:inherit true
:underline "never"
:href "https://docs.clj.codes"}
"Docs.clj.codes")
" is a modern community-powered documentation and examples repository for the "
($ Anchor {:component "a"
:variant "gradient"
:gradient #js {:from "cyan" :to "green"}
:inherit true
:underline "never"
:href "https://clojure.org"}
"Clojure programming language"))))

($ Grid {:id "features"}
($ (-> Grid .-Col) {:span 12}
(dom/section
(dom/h2 "Key Features")
($ Grid {:grow true :gutter "lg"}
($ (-> Grid .-Col) {:span 4}
($ Card {:shadow "sm" :padding "lg" :radius "lg" :style #js {:minHeight "14rem"}}
(dom/h3 "Comprehensive Documentation")
(dom/p "Explore in-depth documentation for a vast array of Clojure libraries.")))
($ (-> Grid .-Col) {:span 4}
($ Card {:shadow "sm" :padding "lg" :radius "lg" :style #js {:minHeight "14rem"}}
(dom/h3 "Seamless Git Integration")
(dom/p "Documentation is directly parsed from their Git repositories.")))
($ (-> Grid .-Col) {:span 4}
($ Card {:shadow "sm" :padding "lg" :radius "lg" :style #js {:minHeight "14rem"}}
(dom/h3 "Social Interaction")
(dom/p "Join a vibrant community of Clojure enthusiasts.")))
($ (-> Grid .-Col) {:span 4}
($ Card {:shadow "sm" :padding "lg" :radius "lg" :style #js {:minHeight "14rem"}}
(dom/h3 "Extensive Search Capabilities")
(dom/p "Harness the power of Datalevin for lightning-fast full-text search.")))
($ (-> Grid .-Col) {:span 4}
($ Card {:shadow "sm" :padding "lg" :radius "lg" :style #js {:minHeight "14rem"}}
(dom/h3 "Easy Contribution")
(dom/p "Become a part of the documentation ecosystem.")))))))))
(when show-dashboard
($ (-> Grid .-Col) {:span 12}
($ dashboards.view/all)))

($ (-> Grid .-Col) {:span 12}
($ Text {:size "xl" :style #js {:paddingTop "1rem"}}
"Built on a tech stack featuring "
($ Text {:component "a" :href "https://github.com/lilactown/helix" :inherit true :fw 700} "helix")
" and "
($ Text {:component "a" :href "https://github.com/lilactown/flex" :inherit true :fw 700} "flex")
" on the frontend and powered by postgres and "
($ Text {:component "a" :href "https://github.com/juji-io/datalevin" :inherit true :fw 700} "datalevin")
" on the backend.")))

($ Grid {:id "features"}
($ (-> Grid .-Col) {:span 12}
(dom/section
(dom/h2 "Key Features")
($ Grid {:grow true :gutter "lg"}
($ (-> Grid .-Col) {:span 4}
($ Card {:shadow "sm" :padding "lg" :radius "lg" :style #js {:minHeight "14rem"}}
(dom/h3 "Comprehensive Documentation")
(dom/p "Explore in-depth documentation for a vast array of Clojure libraries.")))
($ (-> Grid .-Col) {:span 4}
($ Card {:shadow "sm" :padding "lg" :radius "lg" :style #js {:minHeight "14rem"}}
(dom/h3 "Seamless Git Integration")
(dom/p "Documentation is directly parsed from their Git repositories.")))
($ (-> Grid .-Col) {:span 4}
($ Card {:shadow "sm" :padding "lg" :radius "lg" :style #js {:minHeight "14rem"}}
(dom/h3 "Social Interaction")
(dom/p "Join a vibrant community of Clojure enthusiasts.")))
($ (-> Grid .-Col) {:span 4}
($ Card {:shadow "sm" :padding "lg" :radius "lg" :style #js {:minHeight "14rem"}}
(dom/h3 "Extensive Search Capabilities")
(dom/p "Harness the power of Datalevin for lightning-fast full-text search.")))
($ (-> Grid .-Col) {:span 4}
($ Card {:shadow "sm" :padding "lg" :radius "lg" :style #js {:minHeight "14rem"}}
(dom/h3 "Easy Contribution")
(dom/p "Become a part of the documentation ecosystem."))))))))))
8 changes: 8 additions & 0 deletions src/codes/clj/docs/frontend/routes.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
(:require [codes.clj.docs.frontend.infra.auth.github.view :as auth.github.view]
[codes.clj.docs.frontend.panels.author.state :as author.state]
[codes.clj.docs.frontend.panels.author.view :as author.view]
[codes.clj.docs.frontend.panels.dashboards.view :as dashboards.view]
[codes.clj.docs.frontend.panels.definition.state :as definition.state]
[codes.clj.docs.frontend.panels.definition.view :as definition.view]
[codes.clj.docs.frontend.panels.definitions.state :as definitions.state]
Expand Down Expand Up @@ -56,6 +57,13 @@
(set-title! "Projects - docs.clj.codes")
(projects.state/document-projects-fetch))}]}]

["dashboards"
{:name :dashboards
:view dashboards.view/all
:link-text "dashboards"
:controllers [{:start (fn [& _params]
(set-title! "Dashboards - docs.clj.codes"))}]}]

["search"
{:name :search
:view search.view/search-page
Expand Down
49 changes: 49 additions & 0 deletions test/codes/clj/docs/frontend/test/adapters/time_test.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
(ns codes.clj.docs.frontend.test.adapters.time-test
(:require [cljs.test :refer [deftest is use-fixtures]]
[codes.clj.docs.frontend.adapters.time :as adapters]
[codes.clj.docs.frontend.test.aux.init :refer [sync-setup]]
[matcher-combinators.test :refer [match?]]))

(use-fixtures :each sync-setup)

(deftest time-since-test
(let [created-at #inst "2024-03-09T00:00:00.000000000-00:00"]
(is (match? "0 second ago"
(adapters/time-since created-at #inst "2024-03-09T00:00:00.000000000-00:00")))

(is (match? "1 second ago"
(adapters/time-since created-at #inst "2024-03-09T00:00:01.000000000-00:00")))
(is (match? "2 seconds ago"
(adapters/time-since created-at #inst "2024-03-09T00:00:02.000000000-00:00")))

(is (match? "1 minute ago"
(adapters/time-since created-at #inst "2024-03-09T00:01:00.000000000-00:00")))
(is (match? "2 minutes ago"
(adapters/time-since created-at #inst "2024-03-09T00:02:00.000000000-00:00")))

(is (match? "1 hour ago"
(adapters/time-since created-at #inst "2024-03-09T01:00:00.000000000-00:00")))
(is (match? "2 hours ago"
(adapters/time-since created-at #inst "2024-03-09T02:00:00.000000000-00:00")))

(is (match? "1 day ago"
(adapters/time-since created-at #inst "2024-03-10T00:00:00.000000000-00:00")))
(is (match? "2 days ago"
(adapters/time-since created-at #inst "2024-03-11T00:00:00.000000000-00:00")))

(is (match? "1 week ago"
(adapters/time-since created-at #inst "2024-03-16T00:00:00.000000000-00:00")))
(is (match? "2 weeks ago"
(adapters/time-since created-at #inst "2024-03-26T00:00:00.000000000-00:00")))

(is (match? "1 month ago"
(adapters/time-since created-at #inst "2024-04-09T00:00:00.000000000-00:00")))
(is (match? "2 months ago"
(adapters/time-since created-at #inst "2024-05-09T00:00:00.000000000-00:00")))

(is (match? "1 year ago"
(adapters/time-since created-at #inst "2025-03-09T00:00:00.000000000-00:00")))
(is (match? "2 years ago"
(adapters/time-since created-at #inst "2026-03-09T00:00:00.000000000-00:00")))))


Loading