diff --git a/Makefile b/Makefile index a2aa0e1..d55340c 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,7 @@ repl: clj +# TODO I'm confused by why the action has to be in different order build-action: clojure -X:build npm install @@ -17,9 +18,10 @@ build: cp -r resources/* _site/ touch _site/.nojekyll -watch: build +# TODO idk what to change about this one too +watch: while true; do \ - BUILD_ENV=development make build ; \ + BUILD_ENV=development npm run release; \ inotifywait -r -e modify src/gaiwan resources; \ done @@ -36,4 +38,7 @@ deploy: build git push origin gh-pages serve: - cd _site && python3 -m http.server --bin 0.0.0.0 8001 + clj -X:serve + +develop: + clj -X:develop diff --git a/blog/delivering-devops-enterprise-summit.md b/blog/delivering-devops-enterprise-summit.md index c8b17fa..7e6af27 100644 --- a/blog/delivering-devops-enterprise-summit.md +++ b/blog/delivering-devops-enterprise-summit.md @@ -1,10 +1,3 @@ ---- -date: 2021-06-01 -title: "Delivering devops enterprise summit" -slug: "itrevolution-devops-enterprise-summit" -author: "Mitesh (@oxalorg)" ---- - We have worked closely with Gene Kim and his team at [ITRevolution](https://itrevolution.com) to build and deliver the Dev Ops Enterprise Summit online conference. Up until 2019 these conferences amassed tech leaders from across the globe for an in-person conference. After the pandemic though, there was an urgent need to take this to an online venue without detracting from the experience. diff --git a/blog/growing-our-team.md b/blog/growing-our-team.md index 0b922d7..9984dec 100644 --- a/blog/growing-our-team.md +++ b/blog/growing-our-team.md @@ -1,10 +1,3 @@ ---- -date: 2020-11-01 -title: "Growing the Gaiwan team" -slug: "growing-our-team" -author: "Mitesh (@oxalorg)" ---- - We have some great news to share today. Our Gaiwan team has officially expanded to **5 team members** now! diff --git a/blog/still_beating_the_averages.md b/blog/still_beating_the_averages.md index 31cff8b..91f3bb4 100644 --- a/blog/still_beating_the_averages.md +++ b/blog/still_beating_the_averages.md @@ -1,10 +1,3 @@ ---- -date: 2022-06-08 -title: "Still Beating the Averages" -slug: "still-beating-the-averages" -author: "Joshua Ballanco" ---- - It has been just over 20 years since Paul Graham published ["Beating the Averages"](http://www.paulgraham.com/avg.html), one of his more famous articles. While it is perhaps most recognized for its emphatic endorsement of Lisp, Paul Graham's larger point in the article (and source of the title) is that the "average" startup goes out of business. To be successful, he argues, you need to do something to set yourself apart -- to be "above average" -- and using Lisp is one way a startup can be above average. At Gaiwan, we definitely agree that Lisp (or, in our case, Clojure) is a super-powered programming language that can empower a team of developers to beat the average. Indeed, some of us have been using Clojure for over a decade to help both startups and large organizations succeed. That said, upon re-reading "Beating the Averages", one notices a flaw in Paul Graham's argument for choosing Lisp. diff --git a/deps.edn b/deps.edn index 33afc33..15e2f37 100644 --- a/deps.edn +++ b/deps.edn @@ -1,34 +1,21 @@ {:paths ["src" "resources"] :deps {org.clojure/clojure {:mvn/version "1.11.1"} - lambdaisland/data-printers {:mvn/version "0.7.47"} ;; Logging com.lambdaisland/glogi {:mvn/version "1.1.144"} io.pedestal/pedestal.log {:mvn/version "0.5.10"} ch.qos.logback/logback-classic {:mvn/version "1.2.11"} - ;; HTTP - ring/ring {:mvn/version "1.9.5"} - ring/ring-defaults {:mvn/version "0.3.3"} - metosin/reitit-ring {:mvn/version "0.5.18"} - metosin/reitit-http {:mvn/version "0.5.18"} - metosin/reitit-interceptors {:mvn/version "0.5.18"} - ring/ring-jetty-adapter {:mvn/version "1.9.5"} - ;; HTTP / CSS (Server-side) - lambdaisland/webstuff {:git/url "https://github.com/lambdaisland/webstuff" - :sha "5d5669e7f7829c65fdd00b19ef826565ce41b7ea"} com.lambdaisland/ornament {:mvn/version "0.4.34"} - clj-rss/clj-rss {:mvn/version "0.3.0"} ;; Data - markdown-clj/markdown-clj {:mvn/version "1.11.0"} - org.clojure/data.json {:mvn/version "2.4.0"} io.github.nextjournal/markdown {:mvn/version "0.4.109"} - ;; Static site generation - com.lambdaisland/reitit-jaatya {:mvn/version "0.0.24"}} + codes.stel/nuzzle {:mvn/version "0.6.502"} + } :aliases - {:build - {:exec-fn co.gaiwan.site/build}}} + {:build {:exec-fn co.gaiwan.site/publish} + :serve {:exec-fn co.gaiwan.site/serve} + :develop {:exec-fn co.gaiwan.site/develop}}} diff --git a/package-lock.json b/package-lock.json index 43ad07e..a87bbc5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "gaiwan_co", + "name": "nuzzlify", "lockfileVersion": 2, "requires": true, "packages": { @@ -944,7 +944,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -4222,7 +4221,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, "optional": true }, "function-bind": { diff --git a/pages/dev_checkup.md b/pages/dev_checkup.md index c4b1dc7..b4c50e5 100644 --- a/pages/dev_checkup.md +++ b/pages/dev_checkup.md @@ -1,13 +1,3 @@ ---- -title: "Gaiwan's Clojure Dev Tooling & Docs Week" -slug: clojure-dev-tooling-docs-checkup -description: |- - Invite the Gaiwan Team over for a week improve your setup, and to help boost - your team's productivity. Everything from having a good REPL experience, a solid test - setup, and a reloadable workflow, up to having a kick-ass onboarding experience for - the next person to join. ---- - # Gaiwan's Clojure Dev and Docs Week As a consultancy we get to see a lot of code bases, and it's always interesting diff --git a/src/co/gaiwan/site.clj b/src/co/gaiwan/site.clj index 1c8c5e3..6f710a0 100644 --- a/src/co/gaiwan/site.clj +++ b/src/co/gaiwan/site.clj @@ -1,22 +1,72 @@ (ns co.gaiwan.site - (:require [clojure.java.io :as io] - [co.gaiwan.site.http :as http] - [lambdaisland.reitit-jaatya.freeze :as freeze] - [lambdaisland.webstuff.bootstrap :as bootstrap])) - -(defn build [& args] - (http/spit-ornament) - (freeze/iced - (http/build-handler) - {:sitemap-path "/sitemap.xml" - :sitemap-trailing-slash true - :base-url "https://gaiwan.co"})) - -(defn serve [] - (bootstrap/go :dev)) - -(comment - ;; this will create a `_site` folder in `does-library` - ;; to browse it locally run - ;; cd _site && python3 -m http.server - (build)) + (:require [co.gaiwan.site.blog :as blog] + [co.gaiwan.site.data :as data] + [co.gaiwan.site.home :as home] + [co.gaiwan.site.markdown :as md] + [co.gaiwan.site.pages :as pages] + [co.gaiwan.site.work :as work] + [nuzzle.core :as nuzz])) + +(defn pages [] + {[] + {:nuzzle/title data/site-title + :nuzzle/render-page home/get-home} + + [:work] + {:nuzzle/title "Our Work - Gaiwan Team" + :nuzzle/summary "Projects built by the Gaiwan team. We use clojure and clojurescript to solve critical problems for our clients." + :nuzzle/render-page work/get-work} + + [:clojure-dev-tooling-docs-checkup] + {:nuzzle/title "Gaiwan's Clojure Dev Tooling & Docs Week" + :nuzzle/render-page pages/get-generic + :nuzzle/render-content #(md/md-content "pages/dev_checkup.md") + :nuzzle/summary "Invite the Gaiwan Team over for a week improve your setup, and to help boost your team's productivity. Everything from having a good REPL experience, a solid test setup, and a reloadable workflow, up to having a kick-ass onboarding experience for the next person to join."} + + [:blog] + {:nuzzle/title "Gaiwan Blog" + :nuzzle/summary "The Gaiwan Blog" + :nuzzle/render-page blog/get-blog} + + [:blog :itrevolution-devops-enterprise-summit] + {:nuzzle/title "Delivering devops enterprise summit" + :nuzzle/render-page blog/get-blog-item + :nuzzle/render-content #(md/md-content "blog/delivering-devops-enterprise-summit.md") + :nuzzle/published #inst "2021-06-01" + :nuzzle/feed true + :nuzzle/author (data/authors :mitesh)} + + [:blog :growing-our-team] + {:nuzzle/title "Growing the Gaiwan team" + :nuzzle/render-page blog/get-blog-item + :nuzzle/render-content #(md/md-content "blog/growing-our-team.md") + :nuzzle/published #inst "2020-11-01" + :nuzzle/feed true + :nuzzle/author (data/authors :mitesh)} + + [:blog :still-beating-the-averages] + {:nuzzle/title "Still Beating the Averages" + :nuzzle/render-page blog/get-blog-item + :nuzzle/render-content #(md/md-content "blog/still_beating_the_averages.md") + :nuzzle/published #inst "2022-06-08" + :nuzzle/author (data/authors :josh) + :nuzzle/feed true} + + [:version] + {:nuzzle/title "Site Version" + :nuzzle/render-page pages/get-version}}) + +(defn develop [& _] + (nuzz/develop #'pages {:overlay-dir "resources" + :refresh-interval 2500})) + +(defn serve [& _] + (nuzz/serve #'pages {:overlay-dir "resources" + :refresh-interval 2500})) + +(defn publish [& _] + (nuzz/publish pages {:overlay-dir "resources" + :base-url data/base-url + :atom-feed {:title "Gaiwan Blog" + :description "The Gaiwan Blog"} + :publish-dir "_site"})) diff --git a/src/co/gaiwan/site/blog.clj b/src/co/gaiwan/site/blog.clj index 91ba6b0..a4ac151 100644 --- a/src/co/gaiwan/site/blog.clj +++ b/src/co/gaiwan/site/blog.clj @@ -1,30 +1,27 @@ (ns co.gaiwan.site.blog - (:require [clj-rss.core :as rss] - [co.gaiwan.site.layout :as layout] - [co.gaiwan.site.md-files :as md-files] - [co.gaiwan.site.open-graph :as og])) + (:require [co.gaiwan.site.layout :as layout] + [co.gaiwan.site.open-graph :as og] + [co.gaiwan.site.utils :as utils])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Components -(defn list-item [{:keys [html] - {:keys [title slug author date] :or {slug ""}} :meta - :as post}] +(defn list-item [{:nuzzle/keys [title url author published]}] [:article {:class "flex items-center py-4 border-b border-gray-200"} [:div [:header [:h2 {:class "h4 mb-2"} - [:a {:class "hover:underline" :href (str "/blog/" slug "/")} + [:a {:class "hover:underline" :href url} title]]] [:div {:class "text-lg text-gray-600 mb-4"} ""] [:footer {:class "text-sm"} [:div {:class "flex items-center"} [:div [:span {:class "text-gray-600"} "By "] [:span {:class "text-gray-600 font-medium hover:underline2"} - author] + (author :name)] " " - [:span {:class "text-gray-600"} (.format (java.text.SimpleDateFormat. "MMM dd, yyyy") date)]]]]] - [:a {:class "block flex-shrink-0 ml-6" :href (str "/blog/" slug "/")} + [:span {:class "text-gray-600"} (utils/format-inst published)]]]]] + [:a {:class "block flex-shrink-0 ml-6" :href url} [:span {:class "sr-only"} "Read more"] [:svg {:class "w-4 h-4 fill-current text-blue-600", @@ -38,7 +35,7 @@ {:class "relative mt-12 md:mt-0 md:w-64 md:ml-12 lg:ml-20 md:flex-shrink-0"}]) -(defn section [posts] +(defn section [{:nuzzle/keys [get-pages] :as _page}] [:section [:div {:class "max-w-6xl mx-auto px-4 sm:px-6"} [:div {:class "pt-24 pb-12 md:pt-32 md:pb-20"} @@ -55,55 +52,29 @@ [:div {:class "md:flex md:justify-between"} #_[:comment " Articles container "] [:div {:class "md:flex-grow -mt-4"} - (for [post posts] - [list-item post])] - [sidebar]]]]]) + (for [post (->> (get-pages [:blog] {:children true}) + (sort-by :nuzzle/published) + reverse)] + (list-item post))] + (sidebar)]]]]) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Routes - -(def posts (delay (md-files/slurp-dir "blog"))) - -(defn get-blog-rss [_] - {:status 200 - :body (apply rss/channel-xml - {:title "Gaiwan Blog" :link "https://gaiwan.co/blog" :description "The Gaiwan Blog"} - (for [{:keys [meta html]} (vals @posts) - :let [{:keys [title slug author date]} meta]] - {:title title - :link (str "https://gaiwan.co/blog/" slug) - :description html - :author author - :pubDate (.toInstant date)}))}) +(defn get-blog-item + [{:nuzzle/keys [author render-content published] :as page}] + (layout/layout + (og/social-tags page) + [:div {:class "post my-8 mx-auto px-2 container prose lg:prose-lg"} + [:h1 (:title meta)] + [:div.post-meta + "Posted on " + [:span (utils/format-inst published)] + " by " + [:span (let [{:keys [name twitter]} author] + (str name (when twitter (str " (" twitter ")"))))]] + (render-content)])) -(defn get-blog-item [request] - (let [{:keys [slug]} (:path-params request) - post (get @posts slug)] - {:status 200 - :body {:slug slug - :post post} - :view (fn [{:keys [slug post] :as data}] - (let [{:keys [meta hiccup]} post] - [layout/layout - (og/social-tags {:title (:title meta) - :description (:description meta "")}) - [:div {:class "post my-8 mx-auto px-2 container prose lg:prose-lg"} - [:h1 (:title meta)] - [:div.post-meta - "Posted on " - [:span (.format (java.text.SimpleDateFormat. "MMM dd, yyyy") (:date meta))] - " by " - [:span (:author meta)]] - hiccup]]))})) - -(defn get-blog [_] - {:status 200 - :body {:posts (reverse (sort-by (comp :date :meta) (vals @posts)))} - :view (fn [{:keys [posts]}] - [layout/layout - (og/social-tags {:image ""}) - [:div - (section posts)]])}) +(defn get-blog [page] + (layout/layout + (og/social-tags page) + [:div + (section page)])) -(defn freeze-data [] - (map #(do {:slug %}) (keys @posts))) diff --git a/src/co/gaiwan/site/components/header.cljc b/src/co/gaiwan/site/components/header.cljc index d1bc5d3..88e2a87 100755 --- a/src/co/gaiwan/site/components/header.cljc +++ b/src/co/gaiwan/site/components/header.cljc @@ -23,12 +23,12 @@ [:a {:class "text-gray-800 font-bold hover:text-gray-900 px-3 lg:px-5 py-2 flex items-center transition duration-150 ease-in-out", - :href "/blog/"} "Blog"]] + :href [:blog]} "Blog"]] [:li [:a {:class "text-gray-800 font-bold hover:text-gray-900 px-3 lg:px-5 py-2 flex items-center transition duration-150 ease-in-out", - :href "/work/"} "Work"]] + :href [:work]} "Work"]] [:li [:a {:class @@ -81,7 +81,7 @@ [:li [:a {:class "flex text-gray-600 hover:text-gray-900 py-2", - :href "/about/"} "About us"]] + :href [:about]} "About us"]] #_[:li [:a {:class "flex text-gray-600 hover:text-gray-900 py-2", @@ -89,7 +89,7 @@ [:li [:a {:class "flex text-gray-600 hover:text-gray-900 py-2", - :href "/blog/"} "Blog"]] + :href [:blog]} "Blog"]] #_[:li {:class "py-2 my-2 border-t border-b border-gray-200"} [:span {:class "flex text-gray-600 hover:text-gray-900 py-2"} "Resources"] diff --git a/src/co/gaiwan/site/data.clj b/src/co/gaiwan/site/data.clj index f983e1e..2ca1c24 100644 --- a/src/co/gaiwan/site/data.clj +++ b/src/co/gaiwan/site/data.clj @@ -1,6 +1,11 @@ (ns co.gaiwan.site.data (:require [co.gaiwan.site.utils :as utils])) +(def homepage-cta {:title "Get a free 20 minute consultation!" + :subtitle "Let's grab a cup of e-tea and figure out how to relieve you of your business stress!" + :link "mailto:contact@gaiwan.co" + :button-text "Contact us!"}) + (def team-members [{:title "Arne Brasseur (Berlin)" :subtitle "@plexus" @@ -78,4 +83,73 @@ (def faqs [{:question ""}]) -(def projects []) +(def tw-site "@GaiwanTeam") +(def tw-creator "@GaiwanTeam") +(def site-title "Gaiwan - A Clojure Consultancy") +(def site-description "Home for Gaiwan, a provider of technological expertise grown out of the consulting and development work of Lambda Island's Arne Brasseur") +(def base-url "https://gaiwan.co") + +(def projects + [{:client "IT Revolution" + :title "Bespoke conference video editor" + :description + "ITRevolution hosts the biggest Devops conference - the DevOps + Enterprise Summit. During the pandemic they shifted from + in-person to a completely online conference which came with a + host of problems. We helped them transition their entire + operations to a cloud native solution. We also helped their + team to be able to edit over 100 videos per conference + fulfilling all of their custom operations using our in-house + video editor frontend which uses FFMPEG DSL behind the scenes." + :cta-text "Read in-depth of how we built it" + :cta-url "https://itrevolution.com" + :image-url (utils/img "work/itrev-video-editor.png")} + {:client "re:Clojure Conf" + :title "Built & Redesigned the reclojure.org website" + :description + "Gaiwan loves Clojure, which is why we love supporting + community events like re:Clojure. You may have seen the amazing + event website? That was us. A lightweight design, leveraging + web standards for maximum accessibility, and a responsive + experience across devices, all in an appealing package." + :cta-text nil + :cta-url nil + :image-url (utils/img "work/reclojure.png")} + {:client "Pitch" + :title "Integration with a popular chatting service" + :description + "We helped Pitch in building a complex ClojureScript test + runner suited to their codebase. We also helped in integrating + the Pitch app into an extremely popular chatting service." + :cta-text nil + :cta-url nil + :image-url (utils/img "work/pitch.png")} + {:client "Eleven" + :title "Metabase integration and financial queries" + :description + "We created the metabase driver for Datomic for Eleven and also + open-sourced it. We then helped create and integrate their + complicated financial reporting with metabase resulting in some + pretty sweet graphs for their clients!." + :cta-text nil + :cta-url nil + :image-url (utils/img "work/eleven.png")} + {:client "Tidy" + :title "Event sourcing features, major refactoring, and test setup" + :description + "Our team was introduced to help Tidy improve their codebase + with Clojure best practices, to help them do Code Reviews, and + to holistically work alongside their team. This included + working on additional features on their Event Sourcing system + using Postgres and XTDB. We also worked to add integration + tests using Kaocha, setup CI testing, and add authentication + and authorization to their GraphQL/Lacinia based stack. This + involved refactoring of major parts of their system." + :cta-text nil + :cta-url nil + :image-url (utils/img "work/tidy.png")}]) + +(def authors + {:mitesh {:name "Mitesh" + :twitter "@oxalorg"} + :josh {:name "Joshua Ballanco"}}) diff --git a/src/co/gaiwan/site/home.clj b/src/co/gaiwan/site/home.clj index 33511fc..03b3fc7 100644 --- a/src/co/gaiwan/site/home.clj +++ b/src/co/gaiwan/site/home.clj @@ -1,5 +1,7 @@ (ns co.gaiwan.site.home (:require [co.gaiwan.site.data :as data] + [co.gaiwan.site.layout :as layout] + [co.gaiwan.site.open-graph :as og] [co.gaiwan.site.components.hero-home :as hero-home] [co.gaiwan.site.components.projects :as projects] [co.gaiwan.site.components.clients :as clients] @@ -24,7 +26,7 @@ [:p "Our job is to find a path from a business need to a technical solution that ensures we can deliver quality and reliability within time and budget. To do so we are highly opinionated when it comes to choosing the software components we build on, and we work closely with customers to come up with pragmatic solutions that work."] [:div.hero-nav "Learn more about " - [:a {:href "/about"} "our team."]]]]]]) + [:a {:href [:about]} "our team."]]]]]]) (def section-client-work [:section {:id "client-work"} @@ -87,9 +89,11 @@ (projects/section) (team/section data/team-members) [:div {:class "mt-12 lg:mt-32"} - [cta/section {:title "Get a free 20 minute consultation!" - :subtitle "Let's grab a cup of e-tea and figure out how to relieve you of your business stress!" - :link "mailto:contact@gaiwan.co" - :button-text "Contact us!"}]] + (cta/section data/homepage-cta)] #_(features-home/features-home) #_(features-world/features-world)]) + +(defn get-home [_page] + (layout/layout + (og/social-tags {:image ""}) + (body))) diff --git a/src/co/gaiwan/site/http.clj b/src/co/gaiwan/site/http.clj deleted file mode 100644 index 47f0fad..0000000 --- a/src/co/gaiwan/site/http.clj +++ /dev/null @@ -1,43 +0,0 @@ -(ns co.gaiwan.site.http - (:require [co.gaiwan.site.routes :as routes] - [clojure.java.io :as io] - [integrant.core :as ig] - [lambdaisland.ornament :as ornament] - [lambdaisland.webstuff.bootstrap :as bootstrap] - [lambdaisland.webstuff.http :as http] - [reitit.ring :as ring] - [ring.middleware.resource :as resource])) - -(bootstrap/set-config! "config.edn") - -(defn default-handler [] - (ring/routes - (ring/create-resource-handler {:path "/assets" :root "assets"}) - (ring/redirect-trailing-slash-handler {:method :strip}) - (ring/create-default-handler nil))) - -(defn spit-ornament [] - (let [css-file "resources/assets/css/ornament.css"] - (io/make-parents css-file) - (spit css-file (ornament/defined-styles #_{:preflight? true})))) - -(defn wrap-spit-ornament [f] - (fn [req] - (spit-ornament) - (f req))) - -(defn build-handler [] - (http/ring-handler {:routes (routes/routes) - :middleware [[resource/wrap-resource ""]] - :default-handler (default-handler)})) - -(defmethod ig/init-key ::server [_ {:keys [port rebuild-on-request?] :as config}] - (http/start-jetty! (assoc config :build-handler (comp wrap-spit-ornament #'build-handler)))) - -(defmethod ig/halt-key! ::server [_ jetty] - (http/stop-jetty! jetty)) - -(defn -main [& [profile]] - (let [profile (or profile "dev")] - (bootstrap/go (keyword profile))) - @(promise)) diff --git a/src/co/gaiwan/site/layout.clj b/src/co/gaiwan/site/layout.clj index f57e81c..62ab241 100644 --- a/src/co/gaiwan/site/layout.clj +++ b/src/co/gaiwan/site/layout.clj @@ -1,6 +1,5 @@ (ns co.gaiwan.site.layout (:require [clojure.java.io :as io] - [clojure.data.json :as json] [co.gaiwan.site.components.header :as header] [co.gaiwan.site.components.footer :as footer] [lambdaisland.ornament :as ornament])) @@ -31,7 +30,7 @@ (def navbar [:header.top-navbar - [:a.secondary-font.top-navbar__brand {:href "/"} "Gaiwan"]]) + [:a.secondary-font.top-navbar__brand {:href []} "Gaiwan"]]) (defn layout ([body] @@ -56,7 +55,8 @@ [:link {:rel "stylesheet" :href "/assets/css/ornament.css"}] [:style {:type "text/css" :id "ornament"} (ornament/defined-styles {:preflight? true})]) - [:link {:rel "alternate" :type "application/rss+xml" :title "RSS Feed for the Gaiwan Blog" :href "/blog.xml"}] + ;; TODO change to atom + [:link {:rel "alternate" :type "application/atom+xml" :title "Atom Feed for the Gaiwan Blog" :href "/feed.xml"}] head] [:body {:class "font-inter antialiased bg-white text-gray-900 tracking-tight"} diff --git a/src/co/gaiwan/site/markdown.clj b/src/co/gaiwan/site/markdown.clj new file mode 100644 index 0000000..757cce8 --- /dev/null +++ b/src/co/gaiwan/site/markdown.clj @@ -0,0 +1,25 @@ +(ns co.gaiwan.site.markdown + (:require [clojure.string :as str] + [nextjournal.markdown :as md] + [nextjournal.markdown.transform :as md-transform])) + +;;; aliases which you can use inside markdown hiccup blocks +(require '[co.gaiwan.site.components.cta :as cta]) + +(def graal-js-sync-object (Object.)) + +(defn md-content + "Given a markdown file, return a map with metadata and rendered HTML." + [file] + (let [markdown (str/join "\n" (str/split (slurp file) #"\R"))] + (locking graal-js-sync-object + (md-transform/->hiccup + (assoc md-transform/default-hiccup-renderers + :code (fn [ctx {:keys [language content] :as node}] + (if (= "hiccup" language) + (let [[{:keys [text]}] content] + (binding [*ns* (the-ns (symbol (namespace `_)))] + (eval (read-string text)))) + (md-transform/into-markup [:pre] ctx node)))) + (md/parse markdown))))) + diff --git a/src/co/gaiwan/site/md_files.clj b/src/co/gaiwan/site/md_files.clj deleted file mode 100644 index fc9840e..0000000 --- a/src/co/gaiwan/site/md_files.clj +++ /dev/null @@ -1,54 +0,0 @@ -(ns co.gaiwan.site.md-files - (:require [clojure.java.io :as io] - [clojure.string :as str] - [co.gaiwan.site.utils :as utils] - [markdown.core :as m] - [nextjournal.markdown :as md] - [nextjournal.markdown.transform :as md-transform]) - (:import (java.io StringWriter))) - -;;; aliases which you can use inside markdown hiccup blocks -(require '[co.gaiwan.site.components.cta :as cta]) - -;;; /aliases - -(defn collect-files - "File all .md files in the given directory" - [dir] - (filter - #(str/ends-with? (.getName %) ".md") - (file-seq (io/file dir)))) - -(def graal-js-sync-object (Object.)) - -(defn read-md+meta - "Given a markdown file/reader, return a map with metadata and rendered HTML." - [file] - (let [[metadata num-lines] (m/parse-metadata file) - markdown (str/join "\n" (drop num-lines (str/split (slurp file) #"\R")))] - {:meta metadata - :hiccup - (locking graal-js-sync-object - (md-transform/->hiccup - (assoc md-transform/default-hiccup-renderers - :code (fn [ctx {:keys [language content] :as node}] - (if (= "hiccup" language) - (let [[{:keys [text]}] content] - (binding [*ns* (the-ns (symbol (namespace `_)))] - (eval (read-string text)))) - (md-transform/into-markup [:pre] ctx node)))) - (md/parse markdown)))})) - -(defn slurp-files - "Read in a sequence of markdown files, returns a map from slug to meta/html map." - [files] - (into {} - (comp - (map read-md+meta) - (map (juxt #(get-in % [:meta :slug]) identity))) - files)) - -(defn slurp-dir - "Read in a directory of md files, returns a map from slug to meta/html map." - [dir] - (slurp-files (collect-files dir))) diff --git a/src/co/gaiwan/site/open_graph.clj b/src/co/gaiwan/site/open_graph.clj index 0078ff5..6fb0700 100644 --- a/src/co/gaiwan/site/open_graph.clj +++ b/src/co/gaiwan/site/open_graph.clj @@ -1,21 +1,15 @@ (ns co.gaiwan.site.open-graph - (:require [clojure.data.json :as json])) - -;; defaults -(def twitter-site "@GaiwanTeam") -(def twitter-creator "@GaiwanTeam") -(def title "Gaiwan - A Clojure Consultancy") -(def description "Home for Gaiwan, a provider of technological expertise grown out of the consulting and development work of Lambda Island's Arne Brasseur") -(def url "https://gaiwan.co") + (:require [clojure.data.json :as json] + [co.gaiwan.site.data :as data])) (defn twitter-card-tags [{:keys [tw-type tw-site tw-creator url title description image] :or {tw-type "summary" - tw-site twitter-site - tw-creator twitter-creator - title title - description description - url url + tw-site data/tw-site + tw-creator data/tw-creator + title data/site-title + description data/site-description + url data/base-url #_#_image logo-image}}] {"twitter:card" tw-type "twitter:site" tw-site @@ -29,9 +23,9 @@ published-time site-name description section image video-duration video-release-date] :or {og-type "article" - site-name title - description description - title title + site-name data/site-title + description data/site-description + title data/site-title #_#_image logo-image}}] {"og:url" url "og:type" og-type @@ -46,7 +40,7 @@ "video:release_date" video-release-date "video:duration" video-duration}) -(defn video-structured-schema [{:keys [name description upload-date thumbnail-url] :as video}] +(defn video-structured-schema [{:keys [name description upload-date thumbnail-url] :as _video}] (let [schema {"@context" "https://schema.org" "@type" "VideoObject" "name" name @@ -56,11 +50,11 @@ [:script {:type "application/ld+json"} (json/write-str schema :escape-slash false)])) (defn generic-meta-tags [tags] - {"description" (:description tags description)}) + {"description" (:description tags data/site-description)}) (defn social-tags [tags] (into [:<> - [:title (:title tags title)] + [:title (:title tags data/site-title)] (when (:video-schema tags) (video-structured-schema (:video-schema tags)))] (comp diff --git a/src/co/gaiwan/site/pages.clj b/src/co/gaiwan/site/pages.clj index c0b637e..d4808be 100644 --- a/src/co/gaiwan/site/pages.clj +++ b/src/co/gaiwan/site/pages.clj @@ -1,29 +1,19 @@ (ns co.gaiwan.site.pages - (:require [co.gaiwan.site.layout :as layout] - [co.gaiwan.site.md-files :as md-files] + (:require [clojure.java.shell :as sh] + [clojure.string :as str] + [co.gaiwan.site.layout :as layout] [co.gaiwan.site.open-graph :as og] [lambdaisland.ornament :as o])) -(def pages (delay (md-files/slurp-dir "pages"))) +;; (def pages (delay (md-files/slurp-dir "pages"))) (o/defstyled page-wrapper :div :my-8 :px-2 :mx-auto) -(defn get-page [slug _] - (let [page (get @pages slug)] - {:status 200 - :body {:slug slug - :page page} - :view (fn [{:keys [slug page] :as data}] - (let [{:keys [meta hiccup]} page] - [layout/layout (og/social-tags {:title (:title meta) - :description (:description meta "")}) +(defn get-generic [{:nuzzle/keys [render-content] :as page}] + (layout/layout + (og/social-tags page) + (page-wrapper {:class "container prose lg:prose-lg"} (render-content)))) - [page-wrapper {:class "container prose lg:prose-lg"} - hiccup]]))})) - -(defn routes [] - (for [slug (keys (md-files/slurp-dir "pages"))] - {(str "/" slug) - {:name (keyword slug) - :get {:handler (partial get-page slug)}}})) +(defn get-version [_page] + [:html (-> (sh/sh "git" "rev-parse" "HEAD") :out str/trim)]) diff --git a/src/co/gaiwan/site/routes.clj b/src/co/gaiwan/site/routes.clj deleted file mode 100644 index 80c2045..0000000 --- a/src/co/gaiwan/site/routes.clj +++ /dev/null @@ -1,60 +0,0 @@ -(ns co.gaiwan.site.routes - (:require [clojure.java.shell :as sh] - [clojure.string :as str] - [co.gaiwan.site.blog :as blog] - [co.gaiwan.site.home :as home] - [co.gaiwan.site.pages :as pages] - [co.gaiwan.site.layout :as layout] - [co.gaiwan.site.open-graph :as og] - [co.gaiwan.site.work :as work])) - -(defn get-version [_] - (let [commit (-> (sh/sh "git" "rev-parse" "HEAD") :out str/trim)] - {:status 200 - :body {:commit commit} - :view #(:commit %)})) - -(defn get-home [_] - {:status 200 - ;; Make the body plain EDN, this is what you will get if you request JSON/EDN/etc. - :body {} - ;; View is a function from EDN to Hiccup (receives the body data) - :view (fn [data] - [layout/layout - (og/social-tags {:image ""}) - [home/body]])}) - -(defn get-work [_] - {:status 200 - :body {} - :view (fn [_] - [layout/layout - (og/social-tags {:title "Our Work - Gaiwan Team" - :description "Projects built by the Gaiwan team. - We use clojure and clojurescript to solve critical - problems for our clients."}) - [work/body]])}) - -(defn routes [] - (into - [["/" - {:name :home - :get {:handler get-home}}] - ["/blog" - {:name :blog - :get {:handler blog/get-blog}}] - ["/blog.xml" - {:name :blog-rss - :get {:handler blog/get-blog-rss} - :freeze-content-type :xml}] - ["/blog/:slug" - {:name :blog-item - :get {:handler blog/get-blog-item} - :freeze-data-fn blog/freeze-data}] - ["/work" - {:name :work - :get {:handler get-work}}] - ["/version" - {:name :version - :get {:handler get-version}}]] - (pages/routes))) diff --git a/src/co/gaiwan/site/utils.clj b/src/co/gaiwan/site/utils.clj index d6781bb..4d4554d 100644 --- a/src/co/gaiwan/site/utils.clj +++ b/src/co/gaiwan/site/utils.clj @@ -1,11 +1,31 @@ -(ns co.gaiwan.site.utils) +(ns co.gaiwan.site.utils + (:require [clojure.java.io :as io] + [lambdaisland.ornament :as ornament]) + (:import java.time.format.DateTimeFormatter + java.time.ZoneId + java.util.Locale)) -(defn slugify - [string] - ((comp #(clojure.string/replace % #" " "-") - clojure.string/lower-case - clojure.string/trim) - string)) +;; (defn slugify +;; [string] +;; ((comp #(clojure.string/replace % #" " "-") +;; clojure.string/lower-case +;; clojure.string/trim) +;; string)) (defn img [path] (str "/assets/imgs/" path)) + +(defn format-inst [inst] + (let [formatter (.. DateTimeFormatter + (ofPattern "MMM dd, yyyy") + (withLocale Locale/US) + (withZone (ZoneId/of "Z")))] + (.format formatter inst))) + +(comment (format-inst (. #inst "2022-03-03" toInstant))) + +(defn spit-ornament [] + (let [css-file "resources/assets/css/ornament.css"] + (io/make-parents css-file) + (spit css-file (ornament/defined-styles #_{:preflight? true})))) + diff --git a/src/co/gaiwan/site/work.clj b/src/co/gaiwan/site/work.clj index 4ee632f..7108201 100644 --- a/src/co/gaiwan/site/work.clj +++ b/src/co/gaiwan/site/work.clj @@ -1,5 +1,7 @@ (ns co.gaiwan.site.work - (:require [co.gaiwan.site.utils :as utils])) + (:require [co.gaiwan.site.data :as data] + [co.gaiwan.site.layout :as layout] + [co.gaiwan.site.open-graph :as og])) (defn project [{:keys [client title description cta-text cta-url image-url]}] [:div {:class "w-full"} @@ -37,62 +39,10 @@ together."]] [:div {:class "md:flex md:justify-between"} [:div {:class "md:flex-grow -mt-4"} - [project {:client "IT Revolution" - :title "Bespoke conference video editor" - :description - "ITRevolution hosts the biggest Devops conference - the DevOps - Enterprise Summit. During the pandemic they shifted from - in-person to a completely online conference which came with a - host of problems. We helped them transition their entire - operations to a cloud native solution. We also helped their - team to be able to edit over 100 videos per conference - fulfilling all of their custom operations using our in-house - video editor frontend which uses FFMPEG DSL behind the scenes." - :cta-text "Read in-depth of how we built it" - :cta-url "https://itrevolution.com" - :image-url (utils/img "work/itrev-video-editor.png")}] - [project {:client "re:Clojure Conf" - :title "Built & Redesigned the reclojure.org website" - :description - "Gaiwan loves Clojure, which is why we love supporting - community events like re:Clojure. You may have seen the amazing - event website? That was us. A lightweight design, leveraging - web standards for maximum accessibility, and a responsive - experience across devices, all in an appealing package." - :cta-text nil - :cta-url nil - :image-url (utils/img "work/reclojure.png")}] - [project {:client "Pitch" - :title "Integration with a popular chatting service" - :description - "We helped Pitch in building a complex ClojureScript test - runner suited to their codebase. We also helped in integrating - the Pitch app into an extremely popular chatting service." - :cta-text nil - :cta-url nil - :image-url (utils/img "work/pitch.png")}] - [project {:client "Eleven" - :title "Metabase integration and financial queries" - :description - "We created the metabase driver for Datomic for Eleven and also - open-sourced it. We then helped create and integrate their - complicated financial reporting with metabase resulting in some - pretty sweet graphs for their clients!." - :cta-text nil - :cta-url nil - :image-url (utils/img "work/eleven.png")}] - [project {:client "Tidy" - :title "Event sourcing features, major refactoring, and test setup" - :description - "Our team was introduced to help Tidy improve their codebase - with Clojure best practices, to help them do Code Reviews, and - to holistically work alongside their team. This included - working on additional features on their Event Sourcing system - using Postgres and XTDB. We also worked to add integration - tests using Kaocha, setup CI testing, and add authentication - and authorization to their GraphQL/Lacinia based stack. This - involved refactoring of major parts of their system." - :cta-text nil - :cta-url nil - :image-url (utils/img "work/tidy.png")}] - ]]]]]) + (for [p data/projects] + (project p))]]]]]) + +(defn get-work [page] + (layout/layout + (og/social-tags page) + (body))) diff --git a/src/user.clj b/src/user.clj index 6278380..217425c 100644 --- a/src/user.clj +++ b/src/user.clj @@ -8,11 +8,14 @@ [sym] `(requiring-resolve '~sym)) -(defn build [& args] - ((jit co.gaiwan.site/build))) +(defn develop [& args] + ((jit co.gaiwan.site/develop))) -(defn go [& args] +(defn serve [& args] ((jit co.gaiwan.site/serve))) +(defn publish [& args] + ((jit co.gaiwan.site/publish))) + (defn browse [] - ((jit clojure.java.browse/browse-url) "http://localhost:9000")) + ((jit clojure.java.browse/browse-url) "http://localhost:6899"))