Skip to content

Commit

Permalink
Commonmark Compliance re Images (#21)
Browse files Browse the repository at this point in the history
Comply with commonmark's rendering of images by default. See #18.
  • Loading branch information
zampino authored Nov 29, 2023
1 parent 0307e97 commit 2d255ac
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 14 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## Unreleased

* Comply with commonmark's suggested rendering of images by default ([#18](https://github.com/nextjournal/markdown/issues/18)). This is a breaking change.

## 0.5.148

* Fixes a bug in the construction of the table of contents ([#19](https://github.com/nextjournal/markdown/issues/19)).
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ A cross-platform clojure library for [Markdown](https://en.wikipedia.org/wiki/Ma

## Flavor

By building on top of [markdown-it](https://github.com/markdown-it/markdown-it), we adhere to [CommonMark Spec](https://spec.commonmark.org/0.30/) and also comply with extensions from [Github flavoured Markdown](https://github.github.com/gfm). Additionally, we parse $\LaTeX$ formulas (delimited by a $ for inline rendering or $$ for display mode).
By building on top of [markdown-it](https://github.com/markdown-it/markdown-it), we adhere to [CommonMark Spec](https://spec.commonmark.org/0.30/) (with some exceptions[^images]) and also comply with extensions from [Github flavoured Markdown](https://github.github.com/gfm). Additionally, we parse $\LaTeX$ formulas (delimited by a $ for inline rendering or $$ for display mode).

For more details you might have a look at [the set of plugins](https://github.com/nextjournal/markdown/blob/main/src/js/markdown.js) we're using.

Expand Down Expand Up @@ -93,14 +93,14 @@ We've built hiccup transformation in for convenience, but the same approach can
This library is one of the building blocks of [Clerk](https://github.com/nextjournal/clerk) where it is used for rendering _literate fragments_.

```clojure
^{:nextjournal.clerk/viewer :markdown}
^{:nextjournal.clerk/viewer 'nextjournal.clerk.viewer/markdown-viewer}
data
```

The transformation of markdown node types can be customised like this:

```clojure
^{:nextjournal.clerk/viewer :html}
^{:nextjournal.clerk/viewer 'nextjournal.clerk.viewer/html-viewer}
(md.transform/->hiccup
(assoc md.transform/default-hiccup-renderers
;; :doc specify a custom container for the whole doc
Expand All @@ -117,3 +117,6 @@ The transformation of markdown node types can be customised like this:
## Extensibility

We added minimal tooling for [extending markdown expressions](https://nextjournal.github.io/markdown/notebooks/parsing_extensibility).

[^images]: isolated images are not wrapped in a paragraph
node, unless they're part of inline content. See more examples in this [notebook](https://nextjournal.github.io/markdown/notebooks/images).
2 changes: 1 addition & 1 deletion bb.edn
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

:init (do
(def major 0)
(def minor 5)
(def minor 6)
(def rev-count-offset 69) ;; previous repo offset
(def meta-inf-file "resources/META-INF/nextjournal/markdown/meta.edn")

Expand Down
7 changes: 4 additions & 3 deletions deps.edn
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@
:aliases
{:nextjournal/clerk
{:extra-paths ["notebooks"]
:extra-deps {io.github.nextjournal/clerk {:git/sha "60f2399dcdaf39f5d7bc2c213a8dde9a6e0081a9"
:extra-deps {io.github.nextjournal/clerk {:git/sha "e8f275b5cf077ec9441e404c1885ff0b6ee0aef9"
:exclusions [io.github.nextjournal/markdown]}}
:jvm-opts ["-Dclojure.main.report=stderr"
"-Dclerk.resource_manifest={\"/js/viewer.js\" \"js/viewer.js\"}"] ;;
:exec-fn nextjournal.clerk/build-static-app!
:exec-fn nextjournal.clerk/build!
:exec-args {:git/url "https://github.com/nextjournal/markdown"
:paths ["README.md"
"CHANGELOG.md"
"notebooks/try.clj"
"notebooks/images.clj"
"notebooks/pandoc.clj"
"notebooks/parsing_extensibility.clj"
"notebooks/benchmarks.clj"
Expand All @@ -39,7 +40,7 @@
:main-opts ["-m" "shadow.cljs.devtools.cli"]
:jvm-opts ["-Dclerk.resource_manifest={\"/js/viewer.js\" \"http://localhost:8021/viewer.js\"}"]
:extra-deps {io.github.nextjournal/clerk.render {:git/url "https://github.com/nextjournal/clerk"
:git/sha "bc137cb51ce98236f48d8dfcb420f6e9df3342c1"
:git/sha "e8f275b5cf077ec9441e404c1885ff0b6ee0aef9"
:deps/root "render"}}}

:build
Expand Down
39 changes: 39 additions & 0 deletions notebooks/images.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
;; # 🖼️ Block Level Images
(ns images
{:nextjournal.clerk/visibility {:code :hide :result :show}}
(:require [nextjournal.clerk :as clerk]
[nextjournal.markdown :as md]
[nextjournal.markdown.transform :as md.transform]))

;; Unlike [commonmark](https://spec.commonmark.org/0.30/#example-571),
;; nextjournal.markdown distinguishes between inline images and _block images_: image syntax which span a whole
;; line of text produces a direct child of the document and is not wrapped in a paragraph note. Take the following text

^{::clerk/viewer {:var-from-def? true
:transform-fn #(clerk/html [:pre @(::clerk/var-from-def (:nextjournal/value %))])}}
(def text-with-images
"This example shows how we're parsing images, the following is a _block image_
![block level image](https://images.freeimages.com/images/large-previews/773/koldalen-4-1384902.jpg)
while this is an inline ![inline](https://github.com/nextjournal/clerk/actions/workflows/main.yml/badge.svg) image.
")

;; This is parsed as

(clerk/code
(dissoc (md/parse text-with-images)
:toc :footnotes))

;; This allows for a different rendering of images, for instance we might want to render block images with a caption:

^{::clerk/visibility {:code :show} :nextjournal.clerk/viewer 'nextjournal.clerk.viewer/html-viewer}
(md.transform/->hiccup
(assoc md.transform/default-hiccup-renderers
:image (fn [{:as _ctx ::md.transform/keys [parent]} {:as node :keys [attrs]}]
(if (= :doc (:type parent))
[:figure.image
[:img (assoc attrs :alt (md.transform/->text node))]
[:figcaption.text-center.mt-1 (md.transform/->text node)]]
[:img.inline (assoc attrs :alt (md.transform/->text node))])))
(md/parse text-with-images))
3 changes: 1 addition & 2 deletions shadow-cljs.edn
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,4 @@
:output-to "out/sci/viewer.js"
:release {:output-dir "public/build/js"}
:js-options {:output-feature-set :es8}
:modules {:viewer {:entries [nextjournal.clerk.sci-ext
nextjournal.clerk.static-app]}}}}}
:modules {:viewer {:entries [nextjournal.clerk.sci-ext]}}}}}
11 changes: 6 additions & 5 deletions src/nextjournal/markdown/transform.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,12 @@ a paragraph
:blockquote (partial into-markup [:blockquote])
:ruler (constantly [:hr])

;; images
:image (fn [{:as ctx ::keys [parent]} {:as node :keys [attrs]}]
(if (= :paragraph (:type parent))
[:img.inline attrs]
[:figure.image [:img attrs] (into-markup [:figcaption] ctx node)]))
;; by default we always wrap images in paragraph to restore compliance with commonmark
:image (fn [{:as _ctx ::keys [parent]} {:as node :keys [attrs]}]
(let [img-markup [:img (assoc attrs :alt (->text node))]]
(if (= :doc (:type parent))
[:p img-markup]
img-markup)))

;; code
:code (partial into-markup [:pre.viewer-code.not-prose])
Expand Down
25 changes: 25 additions & 0 deletions test/nextjournal/markdown_test.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,31 @@ Par.
"Explain 2"]]]]
(md.transform/->hiccup parsed+sidenotes))))))

(deftest commonmark-compliance
;; we need an extra [:div] for embedding purposes, which might be dropped e.g. by configuring the `:doc` type renderer to use a react fragment `[:<>]`

(testing "images"
;; https://spec.commonmark.org/0.30/#example-571
(is (= [:div [:p [:img {:src "/url" :alt "foo" :title "title"}]]]
(md/->hiccup "![foo](/url \"title\")")))

;; https://spec.commonmark.org/0.30/#example-578
(is (= [:div [:p "My " [:img {:alt "foo bar" :src "/path/to/train.jpg" :title "title"}]]]
(md/->hiccup "My ![foo bar](/path/to/train.jpg \"title\" )"))))

(testing "loose vs. tight lists"
;; https://spec.commonmark.org/0.30/#example-314 (loose list)
(is (= [:div [:ul [:li [:p "a"]] [:li [:p "b"]] [:li [:p "c"]]]]
(md/->hiccup "- a\n- b\n\n- c")))

;; https://spec.commonmark.org/0.30/#example-319 (tight with loose sublist inside)
(is (= [:div [:ul [:li [:<> "a"] [:ul [:li [:p "b"] [:p "c"]]]] [:li [:<> "d"]]]]
(md/->hiccup "- a\n - b\n\n c\n- d\n")))

;; https://spec.commonmark.org/0.30/#example-320 (tight with blockquote inside)
(is (= [:div [:ul [:li [:<> "a"] [:blockquote [:p "b"]]] [:li [:<> "c"]]]]
(md/->hiccup "* a\n > b\n >\n* c")))))

(deftest repro-19-test
(is (match? {:type :toc
:children [{:type :toc
Expand Down

0 comments on commit 2d255ac

Please sign in to comment.