2015-02-10 13:18:56 +00:00
|
|
|
(ns sitetools.core
|
2014-11-29 17:30:24 +00:00
|
|
|
(:require [clojure.string :as string]
|
|
|
|
[goog.events :as evt]
|
|
|
|
[goog.history.EventType :as hevt]
|
2015-07-31 13:13:27 +00:00
|
|
|
[reagent.core :as r]
|
2015-09-07 20:22:26 +00:00
|
|
|
[secretary.core :as secretary :refer-macros [defroute]]
|
2014-11-29 17:30:24 +00:00
|
|
|
[reagent.debug :refer-macros [dbg log dev?]]
|
|
|
|
[reagent.interop :as i :refer-macros [.' .!]])
|
2015-09-07 22:41:04 +00:00
|
|
|
(:import goog.History
|
|
|
|
goog.history.Html5History))
|
2014-11-29 17:30:24 +00:00
|
|
|
|
|
|
|
(when (exists? js/console)
|
|
|
|
(enable-console-print!))
|
|
|
|
|
2015-09-07 20:22:26 +00:00
|
|
|
(defn rswap! [a f & args]
|
2015-09-07 22:12:41 +00:00
|
|
|
;; Roughly like swap!, except that recursive swaps are ok
|
2015-09-08 06:38:45 +00:00
|
|
|
(let [fs (or (.-rswapfs a)
|
|
|
|
(set! (.-rswapfs a) (array)))]
|
2015-09-07 20:22:26 +00:00
|
|
|
(.push fs #(apply f % args))
|
2015-09-08 11:08:09 +00:00
|
|
|
(if (< 1 (alength fs))
|
2015-09-07 22:12:41 +00:00
|
|
|
nil
|
|
|
|
(let [f' (fn [state]
|
2015-09-08 11:08:09 +00:00
|
|
|
;;;; TODO: This could throw
|
2015-09-07 22:12:41 +00:00
|
|
|
(let [s ((aget fs 0) state)]
|
|
|
|
(.shift fs)
|
2015-09-08 11:08:09 +00:00
|
|
|
(if (-> fs alength pos?)
|
2015-09-07 22:12:41 +00:00
|
|
|
(recur s)
|
|
|
|
s)))]
|
|
|
|
(swap! a f')))))
|
2015-09-07 20:22:26 +00:00
|
|
|
|
2014-11-29 17:30:24 +00:00
|
|
|
|
|
|
|
;;; Configuration
|
|
|
|
|
2015-09-07 22:29:06 +00:00
|
|
|
(declare page-content)
|
|
|
|
|
2015-09-08 16:42:32 +00:00
|
|
|
(defonce config (r/atom {:body [#'page-content]
|
2015-09-07 20:22:26 +00:00
|
|
|
:main-content [:div]
|
|
|
|
:pages #{}
|
2015-07-31 13:13:27 +00:00
|
|
|
:site-dir "outsite/public"
|
|
|
|
:css-infiles ["site/public/css/main.css"]
|
|
|
|
:css-file "css/built.css"
|
|
|
|
:js-file "js/main.js"
|
|
|
|
:main-div "main-content"
|
2015-09-08 11:08:09 +00:00
|
|
|
:default-title ""}))
|
2014-11-29 17:30:24 +00:00
|
|
|
|
2015-09-07 20:22:26 +00:00
|
|
|
(defonce history nil)
|
|
|
|
|
|
|
|
(defn demo-handler [state [id v1 v2 :as event]]
|
|
|
|
(case id
|
2015-09-08 16:07:29 +00:00
|
|
|
:set-content (let [title (if v2
|
|
|
|
(str (:title-prefix state) v2)
|
|
|
|
(str (:default-title state)))]
|
2015-09-08 18:18:27 +00:00
|
|
|
(assert (vector? v1))
|
2015-09-08 16:07:29 +00:00
|
|
|
(when r/is-client
|
|
|
|
(r/next-tick #(set! js/document.title title)))
|
|
|
|
(assoc state :main-content v1 :title title))
|
2015-09-08 18:18:27 +00:00
|
|
|
:set-page (do (assert (string? v1))
|
|
|
|
(secretary/dispatch! v1)
|
2015-09-08 07:35:55 +00:00
|
|
|
(assoc state :page-name v1))
|
2015-09-07 20:22:26 +00:00
|
|
|
:goto-page (do
|
2015-09-08 18:18:27 +00:00
|
|
|
(assert (string? v1))
|
2015-09-07 22:12:41 +00:00
|
|
|
(when r/is-client
|
|
|
|
(.setToken history v1 false)
|
|
|
|
(r/next-tick #(set! js/document.body.scrollTop 0)))
|
2015-09-08 11:08:09 +00:00
|
|
|
(recur state [:set-page v1]))
|
2015-09-07 20:22:26 +00:00
|
|
|
state))
|
|
|
|
|
|
|
|
(defn dispatch [event]
|
2015-09-08 11:08:09 +00:00
|
|
|
;; (dbg event)
|
2015-09-07 20:22:26 +00:00
|
|
|
(rswap! config demo-handler event)
|
|
|
|
nil)
|
|
|
|
|
|
|
|
(defn reg-page [url]
|
2015-09-08 18:18:27 +00:00
|
|
|
{:pre [(string? url)]
|
|
|
|
:post [(map? %)]}
|
2015-09-07 20:22:26 +00:00
|
|
|
(swap! config update-in [:pages] conj url))
|
|
|
|
|
2015-09-08 18:18:27 +00:00
|
|
|
(defn register-page [url comp title]
|
|
|
|
{:pre [(re-matches #"/.*[.]html" url)
|
|
|
|
(vector? comp)]}
|
|
|
|
(secretary/add-route! url #(dispatch [:set-content comp title]))
|
|
|
|
(reg-page url))
|
|
|
|
|
2015-09-08 11:08:09 +00:00
|
|
|
|
|
|
|
;;; History
|
|
|
|
|
2015-09-07 20:22:26 +00:00
|
|
|
(defn init-history []
|
|
|
|
(when-not history
|
2015-09-08 07:35:55 +00:00
|
|
|
(let [page (:page-name @config)
|
2015-09-08 11:08:09 +00:00
|
|
|
html5 (and page
|
|
|
|
(.isSupported Html5History)
|
|
|
|
(#{"http:" "https:"} js/location.protocol))]
|
2015-09-08 07:35:55 +00:00
|
|
|
(doto (set! history
|
|
|
|
(if html5
|
|
|
|
(doto (Html5History.)
|
|
|
|
(.setUseFragment false)
|
2015-09-08 11:08:09 +00:00
|
|
|
(.setPathPrefix (-> js/location.pathname
|
|
|
|
(string/replace
|
|
|
|
(re-pattern (str page "$")) "")
|
|
|
|
(string/replace #"/*$" ""))))
|
2015-09-08 07:35:55 +00:00
|
|
|
(History.)))
|
2015-09-08 11:08:09 +00:00
|
|
|
(evt/listen hevt/NAVIGATE #(dispatch [:set-page (.-token %)]))
|
2015-09-08 07:35:55 +00:00
|
|
|
(.setEnabled true))
|
2015-09-08 17:35:05 +00:00
|
|
|
(when (and page (not html5) (-> history .getToken empty?))
|
|
|
|
(.setToken history page)))))
|
2015-09-07 20:22:26 +00:00
|
|
|
|
2015-09-08 16:00:44 +00:00
|
|
|
(defn to-relative [f]
|
2015-09-08 11:08:09 +00:00
|
|
|
(string/replace f #"^/" ""))
|
2014-11-29 17:30:24 +00:00
|
|
|
|
|
|
|
|
|
|
|
;;; Components
|
|
|
|
|
2015-09-07 20:22:26 +00:00
|
|
|
(defn link [props child]
|
|
|
|
[:a (assoc props
|
2015-09-08 16:00:44 +00:00
|
|
|
:href (-> props :href to-relative)
|
2015-09-07 20:22:26 +00:00
|
|
|
:on-click #(do (.preventDefault %)
|
|
|
|
(dispatch [:goto-page (:href props)])))
|
|
|
|
child])
|
|
|
|
|
2014-11-29 17:30:24 +00:00
|
|
|
(defn page-content []
|
2015-09-08 16:42:32 +00:00
|
|
|
(let [{:keys [main-content]} @config]
|
|
|
|
(assert (vector? main-content))
|
|
|
|
main-content))
|
2014-11-29 17:30:24 +00:00
|
|
|
|
|
|
|
|
2015-09-08 11:08:09 +00:00
|
|
|
;;; Static site generation
|
2014-11-29 17:30:24 +00:00
|
|
|
|
2015-09-08 06:52:10 +00:00
|
|
|
(defn prefix [href page]
|
2015-09-08 16:00:44 +00:00
|
|
|
(let [depth (-> #"/" (re-seq (to-relative page)) count)]
|
2014-11-29 17:30:24 +00:00
|
|
|
(str (->> "../" (repeat depth) (apply str)) href)))
|
|
|
|
|
|
|
|
(defn danger [t s]
|
|
|
|
[t {:dangerouslySetInnerHTML {:__html s}}])
|
|
|
|
|
2015-09-08 15:50:50 +00:00
|
|
|
(defn html-template [{:keys [title body-html timestamp page-conf
|
|
|
|
js-file css-file main-div]}]
|
|
|
|
(let [main (str js-file timestamp)]
|
2015-07-31 13:13:27 +00:00
|
|
|
(r/render-to-static-markup
|
2014-11-29 17:30:24 +00:00
|
|
|
[:html
|
|
|
|
[:head
|
|
|
|
[:meta {:charset "utf-8"}]
|
|
|
|
[:meta {:name 'viewport
|
|
|
|
:content "width=device-width, initial-scale=1.0"}]
|
2015-09-08 06:52:10 +00:00
|
|
|
[:base {:href (prefix "" (:page-name page-conf))}]
|
2014-11-29 17:30:24 +00:00
|
|
|
[:link {:href (str css-file timestamp) :rel 'stylesheet}]
|
|
|
|
[:title title]]
|
|
|
|
[:body
|
2015-09-08 15:50:50 +00:00
|
|
|
[:div {:id main-div} (danger :div body-html)]
|
2015-09-08 11:18:21 +00:00
|
|
|
(danger :script (str "var pageConfig = "
|
2015-09-08 16:00:44 +00:00
|
|
|
(-> page-conf clj->js js/JSON.stringify) ";"))
|
2015-01-29 13:49:01 +00:00
|
|
|
[:script {:src main :type "text/javascript"}]]])))
|
2014-11-29 17:30:24 +00:00
|
|
|
|
2015-09-08 15:50:50 +00:00
|
|
|
(defn gen-page [page-name conf]
|
2015-09-08 06:52:10 +00:00
|
|
|
(dispatch [:set-page page-name])
|
2015-09-08 15:50:50 +00:00
|
|
|
(let [b (:body conf)
|
|
|
|
_ (assert (vector? b))
|
|
|
|
bhtml (r/render-component-to-string b)]
|
2014-11-29 17:30:24 +00:00
|
|
|
(str "<!doctype html>"
|
2015-09-08 15:50:50 +00:00
|
|
|
(html-template (assoc conf
|
|
|
|
:page-conf {:page-name page-name}
|
|
|
|
:body-html bhtml)))))
|
2014-11-29 17:30:24 +00:00
|
|
|
|
|
|
|
(defn mkdirs [f]
|
|
|
|
(let [fs (js/require "fs")
|
|
|
|
path (js/require "path")
|
|
|
|
items (as-> f _
|
2015-09-07 20:22:26 +00:00
|
|
|
(.' path dirname _)
|
|
|
|
(.' path normalize _)
|
2015-09-08 11:08:09 +00:00
|
|
|
(string/split _ #"/"))]
|
|
|
|
(doseq [d (reductions #(str %1 "/" %2) items)]
|
2014-11-29 17:30:24 +00:00
|
|
|
(when-not (.' fs existsSync d)
|
|
|
|
(.' fs mkdirSync d)))))
|
|
|
|
|
|
|
|
(defn write-file [f content]
|
2015-09-08 11:23:49 +00:00
|
|
|
(mkdirs f)
|
|
|
|
(.' (js/require "fs") writeFileSync f content))
|
2014-11-29 17:30:24 +00:00
|
|
|
|
|
|
|
(defn read-file [f]
|
2015-09-08 11:23:49 +00:00
|
|
|
(.' (js/require "fs") readFileSync f))
|
2014-11-29 17:30:24 +00:00
|
|
|
|
|
|
|
(defn path-join [& paths]
|
2015-09-08 11:23:49 +00:00
|
|
|
(apply (.' (js/require "path") :join) paths))
|
2014-11-29 17:30:24 +00:00
|
|
|
|
2015-09-08 16:00:44 +00:00
|
|
|
(defn read-files [files]
|
|
|
|
(string/join "\n" (map read-file files)))
|
2014-11-29 17:30:24 +00:00
|
|
|
|
2015-09-08 16:00:44 +00:00
|
|
|
(defn write-resources [dir {:keys [css-file css-infiles]}]
|
|
|
|
(write-file (path-join dir css-file)
|
|
|
|
(read-files css-infiles)))
|
2014-11-29 17:30:24 +00:00
|
|
|
|
|
|
|
|
|
|
|
;;; Main entry points
|
|
|
|
|
|
|
|
(defn ^:export genpages [opts]
|
|
|
|
(log "Generating site")
|
|
|
|
(swap! config merge (js->clj opts :keywordize-keys true))
|
2015-09-08 16:00:44 +00:00
|
|
|
(let [{:keys [site-dir pages] :as state} @config
|
|
|
|
conf (assoc state :timestamp (str "?" (js/Date.now)))]
|
2015-09-08 15:50:50 +00:00
|
|
|
(doseq [f pages]
|
2015-09-08 16:00:44 +00:00
|
|
|
(write-file (->> f to-relative (path-join site-dir))
|
2015-09-08 15:50:50 +00:00
|
|
|
(gen-page f conf)))
|
|
|
|
(write-resources site-dir conf))
|
2014-11-29 17:30:24 +00:00
|
|
|
(log "Wrote site"))
|
|
|
|
|
|
|
|
(defn start! [site-config]
|
|
|
|
(swap! config merge site-config)
|
2015-07-31 13:13:27 +00:00
|
|
|
(when r/is-client
|
2014-11-29 17:30:24 +00:00
|
|
|
(let [conf (when (exists? js/pageConfig)
|
2015-09-08 11:08:09 +00:00
|
|
|
(js->clj js/pageConfig :keywordize-keys true))]
|
2014-11-30 08:40:12 +00:00
|
|
|
(swap! config merge conf)
|
2015-09-08 07:35:55 +00:00
|
|
|
(init-history)
|
2015-09-08 15:50:50 +00:00
|
|
|
(r/render-component (:body @config)
|
2015-09-08 11:08:09 +00:00
|
|
|
(js/document.getElementById (:main-div @config))))))
|