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

Overridable Markdown node Viewers #62

Closed
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
52d7518
Add notebook to test mathjax
zampino Jan 24, 2022
51fa0ed
Incorporate viewer css upstream changes
zampino Jan 24, 2022
63324fd
Restore text parts as parsed markdown for clojure files
zampino Jan 25, 2022
448f5b2
Describe markdown blocks in the same fashion of usual clerk values
zampino Jan 25, 2022
231e98f
Adjust css classes in notebook viewer
zampino Jan 25, 2022
e13adf6
Add markdown doc viewer paired with sci context function
zampino Jan 25, 2022
7777bcd
Improve test Notebook
zampino Jan 25, 2022
d2cade5
Fix top margins for consecutive markdown blocks
zampino Jan 25, 2022
b5d7d4e
Fix devcard styles
zampino Jan 25, 2022
2a5e5b1
Fix client-side viewer registration
zampino Jan 25, 2022
122d76f
Allow html viewer to display inline content
zampino Jan 25, 2022
16dc507
Unify rendering of formulas within clerk markdown with clerk results
zampino Jan 25, 2022
1343daf
Center mathjax formulas which are not inlined
zampino Jan 25, 2022
d55a3b0
Move mathjax notebook under viewers folder
zampino Jan 25, 2022
04f7f74
Bump viewers
zampino Jan 25, 2022
0e00939
Add notebook to static snapshots
zampino Jan 25, 2022
cafa735
Implement markdown links
zampino Jan 25, 2022
02ea2b4
Implement todo and numbered lists
zampino Jan 25, 2022
fd5dd5e
Handle softbreaks
zampino Jan 25, 2022
3d53220
Fix parse test
zampino Jan 25, 2022
2def051
Ruler code image strikethrough blockquote
zampino Jan 25, 2022
379e371
Fix margin bottom of last child paragraphs
zampino Jan 26, 2022
2ad2995
Cleaner and safer update helper
zampino Jan 26, 2022
6d3e722
Fix markdown fenced code blocks background
zampino Jan 26, 2022
5edb697
Remove unused dynamic var *viewers*
zampino Jan 26, 2022
f7606d8
Make markdown viewers overridable via set-viewers!
zampino Jan 26, 2022
50e704d
Better doc to viewer function
zampino Jan 26, 2022
a4f8d51
Release js and improve examples of markdown overrides
zampino Jan 27, 2022
0bb975c
Use `inspect-children` in example notebook
zampino Jan 27, 2022
eda9beb
Fix initial help doc
zampino Jan 27, 2022
ac11cbb
Move markdown.doc viewer to a more sensible place
zampino Jan 27, 2022
62a37ef
Reimplement Markdown Tables
zampino Jan 27, 2022
0d2bcb9
Implement remaining markdown nodes
zampino Jan 27, 2022
9fda473
Release js
zampino Jan 31, 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 @@ -25,7 +25,7 @@
:sci {:extra-deps {applied-science/js-interop {:mvn/version "0.3.0"}
borkdude/sci {:mvn/version "0.2.6"}
reagent/reagent {:mvn/version "1.1.0"}
io.github.nextjournal/viewers {:git/sha "3e1569a67dbec87bcbd7d1343aa754222aeede77"}
io.github.nextjournal/viewers {:git/sha "fd4a1b9ec220e17d2893988c57be2a239d165ef1"}
metosin/reitit-frontend {:mvn/version "0.5.15"}}}

:dev {:extra-deps {arrowic/arrowic {:mvn/version "0.1.1"}
Expand Down
59 changes: 56 additions & 3 deletions notebooks/viewers/markdown.clj
Original file line number Diff line number Diff line change
@@ -1,5 +1,58 @@
;; # Markdown ✍️
(ns markdown (:require [nextjournal.clerk :as clerk]))
;; # Markdown Viewer ✍️

(clerk/md "### Text can be\n * **bold**\n * *italic\n * ~~Strikethrough~~\n
^{:nextjournal.clerk/visibility #{:hide-ns}}
(ns ^:nextjournal.clerk/no-cache viewers.markdown
(:require [nextjournal.clerk :as clerk]))

;; ## Markdown Comments
;; 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~~
;; ### Code
;; ```css
;; .viewer-markdown > :where(:last-child):not(:where([class~="not-prose"] *)) {
;; margin-bottom: 0;
;; }
;; ```
;; ### Images
;; ![JCM](https://nextjournal.com/data/QmUyFWw9L8nZ6wvFTfJvtyqxtDyJiAr7EDZQxLVn64HASX?filename=Requiem-Ornaments-Byline.png&content-type=image/png)
;;
;; ---
;; ## Markdown Results
;; Clerk can in addition produce markdown result blocks

(clerk/md "#### Text can be\n * **bold**\n * *italic*\n * ~~Strikethrough~~\n
It's [Markdown](https://daringfireball.net/projects/markdown/), like you know it.")

;; ---
;; ## Overriding Markdown Viewers
;; What's wrong with paragraph colors in this notebook? It's possible to override each markdown node viewer via `set-viewers!`[^setv], and this will instantly have effect on the whole document.
;;
;; [^setv]: `nextjournal.clerk/set-viewers!` takes a vector of viewer specs...

(clerk/set-viewers! [{:name :nextjournal.markdown/paragraph
:render-fn '(fn [{:keys [content]} opts]
(v/html
(into [:p {:style {:color "#0c4a6e"
:font-size "110%"}}]
(v/inspect-children opts)
content)))}

{:name :nextjournal.markdown/ruler
:render-fn '(constantly
(v/html
[:hr {:style {:border "3px solid #f472b6"}}]))}
{:name :latex
:render-fn 'v/mathjax-viewer}])

;; ### Current State
;; As an excuse to test _tables_ in markdown:
;;
;; | feature | |
;; |:-------------------------------------------------------------------------------------------------------------------|:---|
;; | Reactively update document when markdown viewers change |✅ |
;; | Chaning viewers markdown depends on reactively update the document (see `:latex` viewer for $\phi$-or-$\mu$-ulas) |✅ |
;; | Expose convenient helpers to compose markup |❌ |
;;
;; ---
38 changes: 38 additions & 0 deletions notebooks/viewers/mathjax.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
;; # ${\LaTeX}$ with MathJax
;; This is how to set Clerk's latex rendering to use ~~KaTeX~~ [MathJax](https://mathjax.org). _Right-click on formulas in this notebook to actually check it's rendered with Mathjax_.

(ns ^:nextjournal.clerk/no-cache viewers.mathjax
(:require [nextjournal.clerk :as clerk]))

(clerk/set-viewers! [{:name :latex
:render-fn (quote v/mathjax-viewer)
:fetch-fn (fn [_ x] x)}])

;; Check **inline formulas** are rendered correctly in _deeper fragments_
;;
;; - this is an inline formula $\phi$ in
;; - a list
;;
;; this is a block formula in a markdown comment fragment
;;
;; $$
;; \bigoplus_{\alpha<\omega}\iota_\alpha
;; $$
;; while the following one is a block formula rendererd as clerk result

(clerk/tex "\\int_a^b\\varphi(t)dt")

;; equation references is only supported in MathJax

(clerk/tex "\\begin{equation}
\\cos \\theta_1 = \\cos \\theta_2 \\implies \\theta_1 = \\theta_2
\\label{eq:cosinjective}
\\tag{COS-INJ}
\\end{equation}")
;; this is an example of reference $\eqref{eq:cosinjective}$.
;;
;; # Todos
;; ----------
;; - [x] render inline/display mode of formulas (both KaTeX and MathJax)
;; - [ ] handle clicks on equation references **(?)**

18 changes: 18 additions & 0 deletions resources/stylesheets/viewer.css
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@
@apply mt-1;
}

/* compact TOC */
.viewer-markdown .toc ul {
list-style: none;
@apply my-0;
}

/* Code Viewer */
/* --------------------------------------------------------------- */

Expand Down Expand Up @@ -202,6 +208,18 @@
@apply inline;
}

/* MathJax */
/* --------------------------------------------------------------- */

mjx-container[display] {
display: flex;
align-items: center;
justify-content: center;
}
mjx-container:not([display]) {
display: inline-block;
}

/* Todo Lists */
/* --------------------------------------------------------------- */

Expand Down
1 change: 1 addition & 0 deletions src/nextjournal/clerk.clj
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@
"viewers/image"
"viewers/image_layouts"
"viewers/markdown"
"viewers/mathjax"
"viewers/plotly"
"viewers/table"
"viewers/tex"
Expand Down
2 changes: 1 addition & 1 deletion src/nextjournal/clerk/config.clj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
(not= "false" prop)))

(def default-resource-manifest
{"/js/viewer.js" "https://storage.googleapis.com/nextjournal-cas-eu/data/8VtrAyT1tkNwAPZyFu4amwY7s9rMteumpHLJUHWj4hhaAn7NxQnM1u9C5r9p37ZaEJ656h6Dh7NxGgHU6ztkNqEsnQ"})
{"/js/viewer.js" "https://storage.googleapis.com/nextjournal-cas-eu/data/8VvtpFHnfhgifbafZ1cEWyafMPzDLYn3MGyD5KAEa3WebTGGggFqiuGjn6EgYBFqxESBKxHXpWJeGJcANVJTs73Quq"})

(def resource-manifest-from-props
(when-let [prop (System/getProperty "clerk.resource_manifest")]
Expand Down
12 changes: 8 additions & 4 deletions src/nextjournal/clerk/hashing.clj
Original file line number Diff line number Diff line change
Expand Up @@ -178,20 +178,24 @@
(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
(dissoc :title :toc))}))
: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]))))))))

#_(parse-clojure-string {:doc? true} (slurp "notebooks/mathjax.clj"))

(defn code-cell? [{:as node :keys [type]}]
(and (= :code type) (contains? node :info)))

Expand Down
136 changes: 123 additions & 13 deletions src/nextjournal/clerk/sci_viewer.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,14 @@
(map (fn [x]
(let [viewer (viewer/viewer x)
blob-id (:blob-id (viewer/value x))
prose? (= viewer :markdown)
md? (contains? #{:markdown :nextjournal.markdown/doc} viewer)
inner-viewer-name (some-> x viewer/value viewer/viewer :name)]
;; TODO: cleanup this mess
[:div {:class ["viewer" "overflow-x-auto"
(when (keyword? viewer) (str "viewer-" (name viewer)))
(when-not prose? "not-prose")
(when (keyword? viewer) (str "viewer-" (if md? "markdown" (name viewer))))
(when-not md? "not-prose")
(when inner-viewer-name (str "viewer-" (name inner-viewer-name)))
(when (and prose? inner-viewer-name (not= inner-viewer-name :markdown)) "not-prose")
(when (and md? inner-viewer-name (not= inner-viewer-name :markdown)) "not-prose")
(case (or (viewer/width x) (case viewer (:code :code-folded) :wide :prose))
:wide "w-full max-w-wide"
:full "w-full"
Expand Down Expand Up @@ -676,9 +677,6 @@
{:y (shuffle (range 10)) :name "The Empire"}]})])]
{::dc/class "p-0"})


(def ^:dynamic *viewers* nil)

(dc/defcard inspect-rule-30-sci
[]
[inspect {:path []
Expand Down Expand Up @@ -892,18 +890,20 @@ black")}]))}
(defn clerk-eval [form]
(.ws_send ^js goog/global (pr-str form)))

(defn katex-viewer [tex-string]
(html (katex/to-html-string tex-string)))
(defn katex-viewer [tex-string {:keys [inline?]}]
(html (katex/to-html-string tex-string #js {:displayMode (not inline?)})))

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

(defn reagent-viewer [x]
(r/as-element (cond-> x (fn? x) vector)))

(def mathjax-viewer (comp normalize-viewer mathjax/viewer))
(defn mathjax-viewer [tex-string opts]
(normalize-viewer (mathjax/viewer tex-string opts)))

(def code-viewer (comp normalize-viewer code/viewer))
(def plotly-viewer (comp normalize-viewer plotly/viewer))
(def vega-lite-viewer (comp normalize-viewer vega-lite/viewer))
Expand Down Expand Up @@ -947,6 +947,113 @@ black")}]))}
doc-url
(sci/new-var 'doc-url (fn [x] (str "#" x))))

;;;;;;;;;;;;;; Markdown Viewers ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn into-markup [m]
(fn [{:keys [content]} opts] (html (into m (inspect-children opts) content))))

(def default-markdown-viewers
[{:name :nextjournal.markdown/text :render-fn (fn [{:keys [text]} _] (html [:span text]))}
{:name :nextjournal.markdown/paragraph :render-fn (into-markup [:p])}
{:name :nextjournal.markdown/softbreak :render-fn (constantly (html [:span " "]))}
{:name :nextjournal.markdown/ruler :render-fn (constantly (html [:hr]))}
{:name :nextjournal.markdown/code
:render-fn (fn [{:keys [content language info]} opts]
(html [:div.viewer-code.w-full {:class (when (seq language) (str "language-" language))}
[inspect opts (with-viewer :code (apply str (map (comp :text viewer/value) content)))]]))}
{:name :nextjournal.markdown/image :render-fn (fn [{:keys [attrs]} _] (html [:img attrs]))}
{:name :nextjournal.markdown/heading
:render-fn (fn [{:keys [content heading-level]} opts]
(html (into [(keyword (str "h" heading-level))] (inspect-children opts) content)))}

;; marks
{:name :nextjournal.markdown/em :render-fn (into-markup [:em])}
{:name :nextjournal.markdown/strong :render-fn (into-markup [:strong])}
{:name :nextjournal.markdown/monospace :render-fn (into-markup [:code.monospace])}
{:name :nextjournal.markdown/strikethrough :render-fn (into-markup [:s])}
{:name :nextjournal.markdown/blockquote :render-fn (into-markup [:blockquote])}
{:name :nextjournal.markdown/link
:render-fn (fn [{:keys [content attrs]} opts]
(html (into [:a attrs] (inspect-children opts) content)))}

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

;; formulas
{:name :nextjournal.markdown/block-formula
:render-fn (fn [{:keys [text]} _opts] (with-viewer :latex text))}
{:name :nextjournal.markdown/formula
:render-fn (fn [{:keys [text]} opts] (inspect (assoc opts :inline? true) (with-viewer :latex text)))}

;; tables
{:name :nextjournal.markdown/table :render-fn (into-markup [:table])}
{:name :nextjournal.markdown/table-head :render-fn (into-markup [:thead])}
{:name :nextjournal.markdown/table-body :render-fn (into-markup [:tbody])}
{:name :nextjournal.markdown/table-row :render-fn (into-markup [:tr])}
{:name :nextjournal.markdown/table-header
:render-fn (fn [{:keys [content attrs]} opts]
(html (into [:th {:style (md.transform/table-alignment attrs)}] (inspect-children opts) content)))}
{:name :nextjournal.markdown/table-data
:render-fn (fn [{:keys [content attrs]} opts]
(html (into [:td {:style (md.transform/table-alignment attrs)}] (inspect-children opts) content)))}

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

;; hashtags
{:name :nextjournal.markdown/hashtag
:render-fn (fn [{:keys [text]} _] (html [:a.tag {:href (str "/tags/" text) :style {:color "#fb923c"}} (str "#" text)]))}])

(defn markdown-doc-viewer [{:as doc :keys [content sidenotes?]} opts]
(html (into [:div {:class (when sidenotes? "contains-sidenotes")}]
(inspect-children (update opts :viewers #(concat % default-markdown-viewers)))
content)))

(dc/defcard markdown-doc-viewer
[:div.viewer-markdown
[inspect #:nextjournal{:viewer :nextjournal.markdown/doc,
:value [{:heading-level 1,
:nextjournal/viewer :nextjournal.markdown/heading,
:nextjournal/value [#:nextjournal{:value "MathJax",
:viewer :nextjournal.markdown/text}]}]}]

[inspect #:nextjournal{:viewer :nextjournal.markdown/doc,
:value [#:nextjournal{:viewer :nextjournal.markdown/paragraph,
:value [#:nextjournal{:value "this is ", :viewer :nextjournal.markdown/text}
#:nextjournal{:viewer :nextjournal.markdown/strong,
:value [#:nextjournal{:value "strong",
:viewer :nextjournal.markdown/text}]}
#:nextjournal{:value " ", :viewer :nextjournal.markdown/text}
#:nextjournal{:viewer :nextjournal.markdown/em,
:value [#:nextjournal{:value "cursive",
:viewer :nextjournal.markdown/text}]}
#:nextjournal{:value " text", :viewer :nextjournal.markdown/text}]}]}]

[inspect #:nextjournal{:viewer :nextjournal.markdown/doc,
:value [#:nextjournal{:viewer :nextjournal.markdown/bullet-list,
:value [#:nextjournal{:viewer :nextjournal.markdown/list-item,
:value [#:nextjournal{:viewer :nextjournal.markdown/paragraph,
:value [#:nextjournal{:value "this",
:viewer :nextjournal.markdown/text}]}]}
#:nextjournal{:viewer :nextjournal.markdown/list-item,
:value [#:nextjournal{:viewer :nextjournal.markdown/paragraph,
:value [#:nextjournal{:value "is",
:viewer :nextjournal.markdown/text}]}]}
#:nextjournal{:viewer :nextjournal.markdown/list-item,
:value [#:nextjournal{:viewer :nextjournal.markdown/paragraph,
:value [#:nextjournal{:value "a list",
:viewer :nextjournal.markdown/text}]}]}]}]}]])

(def sci-viewer-namespace
{'html html-viewer
'inspect inspect
Expand Down Expand Up @@ -977,7 +1084,10 @@ black")}]))}
'reagent-viewer reagent-viewer

'doc-url doc-url
'url-for url-for})
'url-for url-for

'markdown-doc-viewer markdown-doc-viewer
})

(defonce !sci-ctx
(atom (sci/init {:async? true
Expand Down
10 changes: 9 additions & 1 deletion src/nextjournal/clerk/view.clj
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,17 @@
#_(->display {:result {:nextjournal.clerk/visibility #{:hide} :nextjournal/value {:nextjournal/viewer :hide-result}} :ns? false})
#_(->display {:result {:nextjournal.clerk/visibility #{:hide}} :ns? true})

(defn md->viewer [{:as node :keys [type content]}]
(v/with-viewer (keyword "nextjournal.markdown" (name type))
(cond-> node
(seq content)
(update :content (partial mapv md->viewer)))))

#_ (-> (nextjournal.markdown/parse "# Hello") md->viewer)

(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 [(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
Loading