Skip to content
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

Use viewer api to to enable customization of markdown nodes #122

Merged
merged 44 commits into from
Apr 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
6a435a9
Preparatory work
zampino Mar 23, 2022
70c6305
clearer separation of concerns between fetch-fn and transform-fn
zampino Mar 23, 2022
2e34b9b
Make `into-markup` a fn of node
zampino Mar 23, 2022
ba985e4
Sketch of inline eval
zampino Mar 23, 2022
55ac94f
Wire things up: markdown notebook case
zampino Mar 23, 2022
4fa8d39
Wire things up for clojure notebooks as well
zampino Mar 23, 2022
73bea15
Implement all markdown nodes
zampino Mar 23, 2022
53bd5ea
Show inline eval
zampino Mar 24, 2022
1d593c2
Test simplification of viewer spec
zampino Mar 24, 2022
44699a9
Compact markdown viewer spec
zampino Mar 24, 2022
cdc5022
Fix hashing tests
zampino Mar 24, 2022
e5de829
Fix code viewer
zampino Mar 24, 2022
1eb958c
Fix rendering inline katex formulas
zampino Mar 24, 2022
9b3816d
Make custom eval a bit more interesting
zampino Mar 24, 2022
d0f4a23
Inline eval in bold
zampino Mar 24, 2022
987005b
Merge markdown viewers in existing var
zampino Mar 28, 2022
33e67f8
Fix markdown image viewer
zampino Mar 28, 2022
8d22dc1
Remove debug class
zampino Mar 28, 2022
8ffd8ba
Adjust scope of notebook to in-text eval
zampino Mar 28, 2022
afaa3e1
Make custom markdown viewers survive page reload
zampino Mar 28, 2022
b8b56d4
Fix demo notebooks paths and nss
zampino Mar 28, 2022
fe1e9a6
Fix reagent structure for in-text evals not to trigger re-rendering
zampino Mar 29, 2022
2bade89
Tweak in text eval
mk Mar 30, 2022
cc24fa2
Remove useless class from text spans
zampino Mar 30, 2022
e613885
Make clerk/md function consistent with markdown viewers
zampino Mar 30, 2022
bd6a1a6
Fix describe tests
zampino Mar 30, 2022
109862f
Merge remote-tracking branch 'origin/main' into described-markdown-II
zampino Mar 30, 2022
f1ad495
Merge remote-tracking branch 'origin/main' into described-markdown-II
zampino Mar 30, 2022
8f8dabd
Fix hashing tests
zampino Mar 30, 2022
1ee6b2c
Bump markdown lib with internal links
zampino Mar 30, 2022
4173f2c
Fix showing initial help doc
zampino Mar 30, 2022
528c190
Fix TOC text for heading nodes with marks
zampino Mar 31, 2022
f911e66
How Clerk Works ToC
zampino Mar 31, 2022
7ad9564
Ignore [[TOC]] placeholder instead of throwing
zampino Mar 31, 2022
359761c
Fix rendering softbreaks
zampino Apr 4, 2022
f4cff55
Merge branch 'main' into described-markdown-II
mk Apr 4, 2022
a359fe0
remove viewers exclusions
zampino Apr 4, 2022
284c36e
Remove redundant notebook
zampino Apr 4, 2022
5bcec41
Move markdown viewers to a separate var
zampino Apr 4, 2022
4abc4ad
Merge remote-tracking branch 'origin/main' into described-markdown-II
zampino Apr 4, 2022
50962d1
Fix scrolling to section when clicking on ToC items
zampino Apr 4, 2022
e31fe9e
Make assertions more precise in describe test
zampino Apr 4, 2022
b256c23
Improve notebook wording
zampino Apr 4, 2022
55d9290
Merge branch 'main' into described-markdown-II
mk Apr 6, 2022
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
2 changes: 1 addition & 1 deletion deps.edn
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

com.nextjournal/beholder {:mvn/version "1.0.0"}

io.github.nextjournal/markdown {:mvn/version "0.2.44"}
io.github.nextjournal/markdown {:mvn/version "0.3.69"}

com.taoensso/nippy {:mvn/version "3.1.1"}
mvxcvi/multihash {:mvn/version "2.0.3"}
Expand Down
2 changes: 1 addition & 1 deletion notebooks/how_clerk_works.clj
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
;; # How Clerk Works 🕵🏻‍♀️
^{:nextjournal.clerk/toc? true}
^{:nextjournal.clerk/toc true}
(ns how-clerk-works
(:require [next.jdbc :as jdbc]
[nextjournal.clerk :as clerk]
Expand Down
55 changes: 55 additions & 0 deletions notebooks/viewers/custom_markdown.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Customizable Markdown

Playground for overriding markdown nodes

```clojure
^{:nextjournal.clerk/visibility #{:hide-ns}}
(ns ^:nextjournal.clerk/no-cache viewers.custom-markdown
(:require [nextjournal.clerk :as clerk]
[nextjournal.clerk.viewer :as v]))
```

```clojure
(clerk/set-viewers! [{:name :nextjournal.markdown/text
:transform-fn (v/into-markup [:span {:style {:color "#64748b"}}])}
{:name :nextjournal.markdown/ruler
:transform-fn (constantly
(v/html [:div {:style {:width "100%" :height "80px" :background-position "center" :background-size "cover"
:background-image "url(https://www.maxpixel.net/static/photo/1x/Ornamental-Separator-Decorative-Line-Art-Divider-4715969.png)"}}]))}])
```

## Sections

with some _more_ text and a ruler.

---

Clerk parses all _consecutive_ `;;`-led _clojure comment lines_ as [Markdown](https://daringfireball.net/projects/markdown). All of markdown syntax should be supported:

### Lists

- one **strong** hashtag like #nomarkdownforoldcountry
- two ~~strikethrough~~

and bullet lists

- [x] what
- [ ] the

### Code

(assoc {:this "should get"} :clojure "syntax highlighting")

---

### Tables

Tables with specific alignment.

| feature | |
|:-------:|:---|
| One |✅ |
| Two |✅ |
| Three |❌ |

---
35 changes: 35 additions & 0 deletions notebooks/viewers/in_text_eval.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
;; # 📝 _In-Text_ Evaluation
^{:nextjournal.clerk/visibility #{:hide-ns :hide} :nextjournal.clerk/toc true}
(ns ^{:nextjournal.clerk/no-cache true} viewers.in-text-eval
(:require [nextjournal.clerk :as clerk]
[nextjournal.clerk.viewer :as v]
[nextjournal.markdown.transform :as markdown.transform]))

;; Being able to override markdown viewers allows us to get in-text evaluation for free:

^{::clerk/viewer clerk/hide-result}
(defonce num★ (atom 3))
#_(reset! num★ 3)

^{::clerk/visibility :show}
(clerk/set-viewers! [{:name :nextjournal.markdown/monospace
:transform-fn (comp eval read-string markdown.transform/->text)}
{:name :nextjournal.markdown/ruler
:transform-fn (constantly
(v/with-viewer :html [:div.text-center (repeat @num★ "★")]))}])
;; ---
^{::clerk/viewer clerk/hide-result}
(defn slider [var {:keys [min max]}]
(clerk/with-viewer
{:fetch-fn (fn [_ x] x)
:transform-fn (fn [var] {:var-name (symbol var) :value @@var})
:render-fn `(fn [{:as x :keys [var-name value]}]
(v/html [:input {:type :range
:min ~min :max ~max
:value value
:on-change #(v/clerk-eval `(reset! ~var-name (Integer/parseInt ~(.. % -target -value))))}]))}
var))

;; Drag the following slider `(slider #'num★ {:min 1 :max 40})` to control the number of stars (currently **`(deref num★)`**) in our custom horizontal rules.

;; ---
2 changes: 1 addition & 1 deletion notebooks/viewers/markdown.clj
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
;; # Markdown ✍️
(ns markdown (:require [nextjournal.clerk :as clerk]))

(clerk/md "### Text can be\n * **bold**\n * *italic\n * ~~Strikethrough~~\n
(clerk/md "### Text can be\n * **bold**\n * *italic*\n * ~~Strikethrough~~\n
It's [Markdown](https://daringfireball.net/projects/markdown/), like you know it.")
2 changes: 1 addition & 1 deletion resources/viewer-js-hash
Original file line number Diff line number Diff line change
@@ -1 +1 @@
qwFLBeeD9jQbcqC1zFhsiitXFpP
3psx9ELzW9wRaYsqhrcBg9eFzXDj
4 changes: 3 additions & 1 deletion src/nextjournal/clerk.clj
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,8 @@

(def clerk-docs
(into ["notebooks/markdown.md"
"notebooks/onwards.md"]
"notebooks/onwards.md"
"notebooks/viewers/custom_markdown.md"]
(map #(str "notebooks/" % ".clj"))
["hello"
"how_clerk_works"
Expand All @@ -378,6 +379,7 @@
"viewers/html"
"viewers/image"
"viewers/image_layouts"
"viewers/in_text_eval"
"viewers/markdown"
"viewers/plotly"
"viewers/table"
Expand Down
10 changes: 6 additions & 4 deletions src/nextjournal/clerk/hashing.clj
Original file line number Diff line number Diff line change
Expand Up @@ -198,16 +198,18 @@
(and doc? (n/comment? node))
(-> state
(assoc :nodes (drop-while n/comment? nodes))
(update :blocks conj {:type :markdown :text (apply str (map (comp remove-leading-semicolons n/string)
(take-while n/comment? nodes)))}))
(update :blocks conj {:type :markdown
:doc (-> (apply str (map (comp remove-leading-semicolons n/string)
(take-while n/comment? nodes)))
markdown/parse
(select-keys [:type :content]))}))
:else
(update state :nodes rest)))
(merge (select-keys state [:blocks :visibility])
(when doc?
(-> {:content (into []
(comp (filter (comp #{:markdown} :type))
(map (comp markdown/parse :text))
(mapcat :content))
(mapcat (comp :content :doc)))
blocks)}
markdown.parser/add-title+toc
(select-keys #{:title :toc})
Expand Down
19 changes: 5 additions & 14 deletions src/nextjournal/clerk/sci_viewer.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
(reduce
(fn [acc {:as item :keys [content children]}]
(if content
(let [title (-> content first :text)]
(let [title (md.transform/->text item)]
(->> {:title title
:path (str "#" (uri.normalize/normalize-fragment title))
:items (toc-items children)}
Expand Down Expand Up @@ -1027,9 +1027,10 @@ black")}]))}
(html (katex/to-html-string tex-string)))

(defn html-viewer [markup]
(if (string? markup)
(html [:div {:dangerouslySetInnerHTML {:__html markup}}])
(r/as-element markup)))
(r/as-element
(if (string? markup)
[:span {:dangerouslySetInnerHTML {:__html markup}}]
markup)))

(defn reagent-viewer [x]
(r/as-element (cond-> x (fn? x) vector)))
Expand All @@ -1039,15 +1040,6 @@ black")}]))}
(def plotly-viewer (comp normalize-viewer plotly/viewer))
(def vega-lite-viewer (comp normalize-viewer vega-lite/viewer))

(defn markdown-viewer
"Accept a markdown string or a structure from parsed markdown."
[data]
(cond
(string? data)
(markdown/viewer data)
(and (map? data) (contains? data :content) (contains? data :type))
(with-viewer :hiccup (md.transform/->hiccup markdown/default-renderers data))))

(def expand-icon
[:svg {:xmlns "http://www.w3.org/2000/svg" :viewBox "0 0 20 20" :fill "currentColor" :width 12 :height 12}
[:path {:fill-rule "evenodd" :d "M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" :clip-rule "evenodd"}]])
Expand Down Expand Up @@ -1101,7 +1093,6 @@ black")}]))}
'notebook-viewer notebook
'katex-viewer katex-viewer
'mathjax-viewer mathjax-viewer
'markdown-viewer markdown-viewer
'code-viewer code-viewer
'foldable-code-viewer foldable-code-viewer
'plotly-viewer plotly-viewer
Expand Down
5 changes: 4 additions & 1 deletion src/nextjournal/clerk/view.clj
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,10 @@

(defn describe-block [{:keys [inline-results?] :or {inline-results? false}} {:keys [ns]} {:as cell :keys [type text doc]}]
(case type
:markdown [(v/md (or doc text))]
:markdown [(binding [*ns* ns]
(v/describe (cond
text (v/md text)
doc (v/with-md-viewer doc))))]
:code (let [{:as cell :keys [result]} (update cell :result apply-viewer-unwrapping-var-from-def)
{:keys [code? fold? result?]} (->display cell)]
(cond-> []
Expand Down
86 changes: 81 additions & 5 deletions src/nextjournal/clerk/viewer.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
[clojure.walk :as w]
#?@(:clj [[clojure.repl :refer [demunge]]
[nextjournal.clerk.config :as config]]
:cljs [[reagent.ratom :as ratom]]))
:cljs [[reagent.ratom :as ratom]])
[nextjournal.markdown :as md]
[nextjournal.markdown.transform :as md.transform]
[lambdaisland.uri.normalize :as uri.normalize])
#?(:clj (:import (java.lang Throwable)
(java.awt.image BufferedImage)
(javax.imageio ImageIO))))
Expand Down Expand Up @@ -153,7 +156,7 @@

(defn inspect-leafs [opts x]
(if (wrapped-value? x)
[(->viewer-fn 'v/inspect) (describe x opts)]
[(->viewer-eval 'v/inspect) (describe x opts)]
x))

(defn fetch-all [opts xs]
Expand All @@ -166,6 +169,17 @@
(defn var-from-def? [x]
(and (map? x) (get-safe x :nextjournal.clerk/var-from-def)))

(declare with-viewer)

(defn with-md-viewer [{:as node :keys [type]}]
(with-viewer (keyword "nextjournal.markdown" (name type)) node))

(defn into-markup [mkup]
(let [mkup-fn (if (fn? mkup) mkup (constantly mkup))]
(fn [{:as node :keys [text content]}]
(with-viewer :html
(into (mkup-fn node) (cond text [text] content (map with-md-viewer content)))))))

(declare !viewers)

;; keep viewer selection stricly in Clojure
Expand Down Expand Up @@ -206,10 +220,9 @@
{:name :latex :render-fn (quote v/katex-viewer) :fetch-fn fetch-all}
{:name :mathjax :render-fn (quote v/mathjax-viewer) :fetch-fn fetch-all}
{:name :html :render-fn (quote v/html) :fetch-fn fetch-all}
{:name :hiccup :render-fn (quote v/html)} ;; TODO: drop once markdown doesn't use it anymore
{:name :plotly :render-fn (quote v/plotly-viewer) :fetch-fn fetch-all}
{:name :vega-lite :render-fn (quote v/vega-lite-viewer) :fetch-fn fetch-all}
{:name :markdown :render-fn (quote v/markdown-viewer) :fetch-fn fetch-all}
{:name :markdown :transform-fn (comp with-md-viewer md/parse)}
{:name :code :render-fn (quote v/code-viewer) :fetch-fn fetch-all :transform-fn #(let [v (value %)] (if (string? v) v (with-out-str (pprint/pprint v))))}
{:name :code-folded :render-fn (quote v/foldable-code-viewer) :fetch-fn fetch-all :transform-fn #(let [v (value %)] (if (string? v) v (with-out-str (pprint/pprint v))))}
{:name :reagent :render-fn (quote v/reagent-viewer) :fetch-fn fetch-all}
Expand Down Expand Up @@ -243,8 +256,71 @@
{:pred string? :render-fn (quote v/string-viewer) :fetch-opts {:n 100}}
{:pred number? :render-fn '(fn [x] (v/html [:span.tabular-nums (if (js/Number.isNaN x) "NaN" (str x))]))}])

(def markdown-viewers
[{:name :nextjournal.markdown/doc :transform-fn (into-markup [:div.viewer-markdown])}

;; blocks
{:name :nextjournal.markdown/heading
:transform-fn (into-markup
(fn [{:as node :keys [heading-level]}]
[(str "h" heading-level) {:id (uri.normalize/normalize-fragment (md.transform/->text node))}]))}
{:name :nextjournal.markdown/image :transform-fn #(with-viewer :html [:img (:attrs %)])}
{:name :nextjournal.markdown/blockquote :transform-fn (into-markup [:blockquote])}
{:name :nextjournal.markdown/paragraph :transform-fn (into-markup [:p])}
{:name :nextjournal.markdown/ruler :transform-fn (into-markup [:hr])}
{:name :nextjournal.markdown/code
:transform-fn #(with-viewer :html
[:div.viewer-code
(with-viewer :code
(md.transform/->text %))])}

;; marks
{:name :nextjournal.markdown/em :transform-fn (into-markup [:em])}
{:name :nextjournal.markdown/strong :transform-fn (into-markup [:strong])}
{:name :nextjournal.markdown/monospace :transform-fn (into-markup [:code])}
{:name :nextjournal.markdown/strikethrough :transform-fn (into-markup [:s])}
{:name :nextjournal.markdown/link :transform-fn (into-markup #(vector :a (:attrs %)))}
{:name :nextjournal.markdown/internal-link :transform-fn (into-markup #(vector :a {:href (str "#" (:text %))}))}
{:name :nextjournal.markdown/hashtag :transform-fn (into-markup #(vector :a {:href (str "#" (:text %))}))}

;; inlines
{:name :nextjournal.markdown/text :transform-fn (into-markup [:span])}
{:name :nextjournal.markdown/softbreak :transform-fn (fn [_] (with-viewer :html [:span " "]))}
#?(:clj {:name :nextjournal.markdown/inline :transform-fn (comp eval read-string md.transform/->text)})

;; formulas
{:name :nextjournal.markdown/formula :transform-fn :text :render-fn 'v/katex-viewer}
{:name :nextjournal.markdown/block-formula :transform-fn :text :render-fn 'v/katex-viewer}

;; lists
{:name :nextjournal.markdown/bullet-list :transform-fn (into-markup [:ul])}
{:name :nextjournal.markdown/numbered-list :transform-fn (into-markup [:ol])}
{:name :nextjournal.markdown/todo-list :transform-fn (into-markup [:ul.contains-task-list])}
{:name :nextjournal.markdown/list-item :transform-fn (into-markup [:li])}
{:name :nextjournal.markdown/todo-item
:transform-fn (into-markup (fn [{:keys [attrs]}] [:li [:input {:type "checkbox" :default-checked (:checked attrs)}]]))}

;; tables
{:name :nextjournal.markdown/table :transform-fn (into-markup [:table])}
{:name :nextjournal.markdown/table-head :transform-fn (into-markup [:thead])}
{:name :nextjournal.markdown/table-body :transform-fn (into-markup [:tbody])}
{:name :nextjournal.markdown/table-row :transform-fn (into-markup [:tr])}
{:name :nextjournal.markdown/table-header
:transform-fn (into-markup #(vector :th {:style (md.transform/table-alignment (:attrs %))}))}
{:name :nextjournal.markdown/table-data
:transform-fn (into-markup #(vector :td {:style (md.transform/table-alignment (:attrs %))}))}

;; ToC via [[TOC]] placeholder ignored
{:name :nextjournal.markdown/toc :transform-fn (into-markup [:div.toc])}

;; sidenotes
{:name :nextjournal.markdown/sidenote
:transform-fn (into-markup (fn [{:keys [attrs]}] [:span.sidenote [:sup {:style {:margin-right "3px"}} (-> attrs :ref inc)]]))}
{:name :nextjournal.markdown/sidenote-ref
:transform-fn (into-markup [:sup.sidenote-ref])}])

(defn get-all-viewers []
{:root default-viewers
{:root (concat default-viewers markdown-viewers)
:table default-table-cell-viewers})

(defonce
Expand Down
14 changes: 6 additions & 8 deletions test/nextjournal/clerk/hashing_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,18 @@
(deftest parse-clojure-string
(testing "is returning blocks with types and markdown structure attached"
(is (match? (m/equals {:blocks [{:type :code, :text "^:nextjournal.clerk/no-cache ^:nextjournal.clerk/toc (ns example-notebook)", :ns? true}
{:type :markdown, :text " # 📶 Sorting\n"}
{:type :markdown, :text " ## Sorting Sets\n The following set should be sorted upon description\n"}
{:type :markdown, :doc {:type :doc :content [{:type :heading}]}}
{:type :markdown, :doc {:type :doc :content [{:type :heading}
{:type :paragraph}]}}
{:type :code, :text "#{3 1 2}"}
{:type :markdown, :text " ## Sorting Maps\n"}
{:type :markdown, :doc {:type :doc :content [{:type :heading}]}}
{:type :code, :text "{2 \"bar\" 1 \"foo\"}"}],
:visibility #{:show},
:title "📶 Sorting",
:toc {:type :toc,
:mode true,
:children [{:type :toc,
:content [{:type :text, :text "📶 Sorting"}],
:heading-level 1,
:children [{:type :toc, :content [{:type :text, :text "Sorting Sets"}], :heading-level 2}
{:type :toc, :content [{:type :text, :text "Sorting Maps"}], :heading-level 2}]}]}})
:children [{:type :toc :children [{:type :toc}
{:type :toc}]}]}})
(h/parse-clojure-string {:doc? true} notebook)))))

(deftest no-cache?
Expand Down
Loading