|
| 1 | +;; # HTML String Rendering |
| 2 | + |
| 3 | +;; This is an experiment to find out what's required to support |
| 4 | +;; rendering to HTML strings via hiccup, using an alternative viewer |
| 5 | +;; stack. If this is feasible, it could be the basis for a much |
| 6 | +;; smaller client-side bundle in combination with a morphing lib while |
| 7 | +;; moving some of the viewers implementation to custom elements. |
| 8 | + |
| 9 | +(ns html-string |
| 10 | + (:require [nextjournal.clerk :as clerk] |
| 11 | + [nextjournal.clerk.eval :as eval] |
| 12 | + [nextjournal.clerk.view :as view] |
| 13 | + [nextjournal.clerk.viewer :as viewer] |
| 14 | + [clojure.repl.deps :as deps])) |
| 15 | + |
| 16 | +(defn render-cell [xs] |
| 17 | + (apply list xs)) |
| 18 | + |
| 19 | +(defn render-notebook [{xs :blocks :keys [package doc-css-class sidenotes? toc toc-visibility header footer]} |
| 20 | + #_{:as render-opts :keys [!expanded-at]}] |
| 21 | + [:div.flex |
| 22 | + [:div.flex-auto.w-screen.scroll-container |
| 23 | + (into |
| 24 | + [:div |
| 25 | + (merge |
| 26 | + {#_#_:key "notebook-viewer" |
| 27 | + :class (cond-> (or doc-css-class (mapv name [:flex :flex-col :items-center :notebook-viewer :flex-auto])) |
| 28 | + sidenotes? (conj :sidenotes-layout))})] |
| 29 | + (concat (when header [header]) xs (when footer [footer])))]]) |
| 30 | + |
| 31 | +(defn render-html [html] |
| 32 | + html) |
| 33 | + |
| 34 | +(defn render-code-block [code-string #_{:as opts :keys [id]}] |
| 35 | + [:div.viewer.code-viewer.w-full.max-w-wide #_{:data-block-id id} |
| 36 | + code-string |
| 37 | + #_ |
| 38 | + [code/render-code code-string (assoc opts :language "clojure")]]) |
| 39 | + |
| 40 | +(defn render-toc [_]) |
| 41 | + |
| 42 | +(def modified-viewers |
| 43 | + [(assoc viewer/cell-viewer :render-fn `render-cell) |
| 44 | + (assoc viewer/notebook-viewer :render-fn `render-notebook) |
| 45 | + (assoc viewer/code-block-viewer :render-fn `render-code-block) |
| 46 | + (assoc viewer/html-viewer :render-fn `render-html) |
| 47 | + (assoc viewer/toc-viewer :render-fn `render-toc)]) |
| 48 | + |
| 49 | +(defn doc->viewer [doc] |
| 50 | + (viewer/present |
| 51 | + (viewer/with-viewers (viewer/add-viewers modified-viewers) |
| 52 | + (viewer/notebook doc)))) |
| 53 | + |
| 54 | +(def viewer-doc |
| 55 | + (doc->viewer (eval/eval-file "notebooks/hello.clj"))) |
| 56 | + |
| 57 | +(defn extract-viewers [viewer-doc] |
| 58 | + (into [] |
| 59 | + (keep :nextjournal/viewer) |
| 60 | + (tree-seq coll? seq viewer-doc))) |
| 61 | + |
| 62 | +(sort (keys (frequencies (mapv (comp :form :render-fn) (extract-viewers viewer-doc))))) |
| 63 | + |
| 64 | +(defn ->fn [sym] |
| 65 | + (cond |
| 66 | + (simple-symbol? sym) (ns-resolve 'clojure.core sym) |
| 67 | + (qualified-symbol? sym) (requiring-resolve sym) |
| 68 | + :else (throw (ex-info "->fn expected symbol, got" {:sym sym})))) |
| 69 | + |
| 70 | +(defn eval-viewers [viewer-doc] |
| 71 | + (clojure.walk/postwalk (fn [x] |
| 72 | + (if (and (map? x) |
| 73 | + (contains? x :nextjournal/viewer) |
| 74 | + (contains? x :nextjournal/value)) |
| 75 | + ((->fn (get-in x [:nextjournal/viewer :render-fn :form])) |
| 76 | + (:nextjournal/value x)) |
| 77 | + x)) |
| 78 | + viewer-doc)) |
| 79 | +#_ |
| 80 | +(clerk/present! (viewer/html |
| 81 | + (eval-viewers viewer-doc))) |
| 82 | + |
| 83 | +(defonce html |
| 84 | + (do |
| 85 | + (deps/add-lib 'io.github.escherize/huff) |
| 86 | + @(requiring-resolve 'huff2.core/html))) |
| 87 | + |
| 88 | +(defn ->html [viewer-doc] |
| 89 | + (nextjournal.clerk.view/->html {:exclude-js? true |
| 90 | + :html (html (eval-viewers viewer-doc))})) |
| 91 | + |
| 92 | +(doto "test.html" |
| 93 | + (spit (->html viewer-doc)) |
| 94 | + (clojure.java.browse/browse-url)) |
| 95 | + |
| 96 | + |
| 97 | + |
0 commit comments