Liking cljdoc? Tell your friends :D

BreadCMS

Build status Clojars Project

After bread has been secured, leisure is the supreme aim.
— Pyotr Kropotkin

WORK IN PROGRESS. Code is approaching "stable alpha" status. A lot of this still does not work yet.

Goals

Bread aims to be three things:

  1. A lightweight core library for constructing a totally custom CMS
  2. A small set of plugins that compose to create a CMS with sensible defaults
  3. A binary (jar and native) that ships with the basic Bread CMS install

Bread's (planned) high-level feature set:

  • Next-level user experience for editing content. Design-aware editing. No separate backend UI.
  • First-class support for translation/internationalization
  • First-class support for Datahike and Datalog queries
  • Collaborative editing
  • Offline first
  • Fully static rendering at write time (for simple use-cases)
  • An extensible default schema supporting many "open-world" content patterns
  • Co-located component queries for more advanced data models
  • Completely hackable through an extremely simple plugin API

Non-goals

  • Performance. Bread is a relatively lightweight CMS, and should perform reasonably well on modern hardware. However, little attention has been given to benchmarking, optimization, etc. We are still in the design stage and thus are focusing on correctness and API ergonomics.
  • Minimalism by default. A Content Management System must, by definition, have a fairly large surface area to be broadly useful. While some CMSs reach for a Spartan minimalism out of the box, Bread takes a balanced approach. Its core library is a handful of namespaces that define a very generic app lifecyle, small but very powerful. However, Bread's default install is considerably bigger, with utilities for various common use-cases; it is "batteries included." If you want real minimalism, you can get it by throwing away the defaults and composing your own bespoke CMS.
  • Integration with ___ framework. Following from the above non-goal, the default install for Bread comes with many opinions about UX, UI, db schema, routing, etc. While these defaults are simple to override, in aggregate they are not easy to compose with another system's differing opinions. Take out the "batteries" and start with the core library if you want tight integration with your favorite Clojure framework.

Basic usage

(ns my-project
  (:require
    [reitit.core :as reitit]
    ;; include support for plugging in a Reitit Router
    [systems.bread.alpha.core :as bread]
    [systems.bread.alpha.component :refer [defc]]
    [systems.bread.alpha.plugin.reitit]
    [systems.bread.alpha.plugin.defaults :as defaults]))

(defc hello [{:keys [i18n params]}]
  [:p (format "%s, %s!" (:greeting i18n) (:to params))])

(def router
  (reitit/router
    ["/:lang" ; :lang is the default i18n route param but can be configured
     ["/hello/:to" {:name :hello
                    :bread/dispatcher :component
                    :data [:params]
                    :component hello}]]))

(def data
  [{:string/key :hello :string/lang :en :string/value "Hello"}
   {:string/key :hello :string/lang :fr :string/value "Bonjour"}])

(def handler
  (bread/load-handler (defaults/app {:router router
                                     :db {:db/type :datahike
                                          :store {; datahike config
                                                  :backend :mem
                                                  :id "hello-bread"}
                                          :initial-txns data}})))

(handler {:uri "/en/hello/Breadsters"})
;; => [:p "Hello, Breadsters!"]

(handler {:uri "/fr/hello/Breadsters"})
;; => [:p "Bonjour, Breadsters!"]

A simple blog engine

(ns my.simple.blog
  (:require
    [reitit.core :as reitit]
    [systems.bread.alpha.core :as bread]
    [systems.bread.alpha.component :refer [defc]]
    [systems.bread.alpha.plugin.reitit]
    [systems.bread.alpha.plugin.defaults :as defaults]))

(defc main-layout [{:keys [main-content i18n lang]}]
  {:content-path [:main-content]} ; how extending components pass their content
  [:html {:lang lang}
   [:body
    [:h1 (:site-header i18n)]
    main-content]])

(defc article-component
  [{:keys [i18n lang]
   {:post/keys [slug fields]} :article}] ; :article corresponds to :key
  {:query [:post/slug fields*]
   :key :article
   :extends main-layout}
  [:main.single
   [:article
    [:h2 (:title fields)]
    [:div.permalink [:a {:href (str "/" lang "/article/" slug)}]]
    (:content fields)]])

(defc article-listing-component
  [{:keys [i18n lang articles]}] ; :articles corresponds to :key
  {:query [:post/slug fields*]
   :key :articles
   :extends main-layout}
  [:main.listing
   (map (fn [{:post/keys [slug fields]}]
          [:article
           [:a {:href (str "/" lang "/article/" slug)}
            (:title fields)]])
        articles)])

(def router
  (reitit/router
    ["/:lang"
     ["/"
      {:dispatcher/type      :dispatcher.type/articles
       :dispatcher/component article-listing-component}]
     ["/article/:slug"
      {:dispatcher/type      :dispatcher.type/article
       :dispatcher/component article-component}]]))

(def data
  [{:string/key          :site-header
    :string/lang         :en
    :string/value        "My Blog"}
   {:post/type           :post.type/article
    :post/status         :post.type/published
    :translatable/fields [{:field/key     :title
                           :field/lang    :en
                           :field/format  :edn
                           :field/content "\"English Article Title\""}
                          {:field/key     :title
                           :field/lang    :en
                           :field/format  :edn
                           :field/content "\"Article content in English...\""}]}
   {:post/type           :post.type/article
    :post/status         :post.type/published
    :translatable/fields [{:field/key     :title
                           :field/lang    :en
                           :field/format  :edn
                           :field/content "\"An Older Article\""}
                          {:field/key     :title
                           :field/lang    :en
                           :field/format  :edn
                           :field/content "\"Lorem ipsum dolor sit amet\""}]}])

(def handler
  (bread/load-handler (defaults/app {:router router
                                     :db {:db/type :datahike
                                          :store {; datahike config
                                                  :backend :mem
                                                  :id "bread-blog-db"}}})))

(handler {:uri "/en/"})
;; => [:html {:lang "en"}
;;     [:body
;;      [:h1 "My Blog"]
;;      [:main.listing
;;       [:article [:a {:href "/en/newer-article"} "English Article Title"]]
;;       [:article [:a {:href "/en/older-article"} "An Older Article"]]]]]

(handler {:uri "/en/article/newer-article"})
;; => [:html {:lang "en"}
;;     [:body
;;      [:h1 "My Blog"]
;;      [:main.single
;;       [:article
;;        [:h2 "English Article Title"]
;;        [:div.content "Article content in English..."]]]]]

Can you improve this documentation?Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close