From 79cf209204b083d03269223cdb4c839868185eb1 Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Fri, 2 Sep 2022 11:38:04 +0200 Subject: [PATCH 01/13] Transform Markdown data back to text --- src/nextjournal/markdown/parser.cljc | 9 +- src/nextjournal/markdown/transform.cljc | 230 +++++++++++++++++++++++- 2 files changed, 232 insertions(+), 7 deletions(-) diff --git a/src/nextjournal/markdown/parser.cljc b/src/nextjournal/markdown/parser.cljc index 43285f3..292cfe0 100644 --- a/src/nextjournal/markdown/parser.cljc +++ b/src/nextjournal/markdown/parser.cljc @@ -103,7 +103,8 @@ (defn tag-node [text] {:type :hashtag :text text}) (defn formula [text] {:type :formula :text text}) (defn block-formula [text] {:type :block-formula :text text}) -(defn sidenote-ref [ref] {:type :sidenote-ref :content [(text-node (str (inc ref)))]}) +(defn sidenote-data [token] {:ref (get-in* token [:meta :id]) + :label (get-in* token [:meta :label])}) ;; node constructors (defn node @@ -279,9 +280,11 @@ end" close-node)) ;; footnotes -(defmethod apply-token "sidenote_ref" [doc token] (push-node doc (sidenote-ref (get-in* token [:meta :id])))) +(defmethod apply-token "sidenote_ref" [doc token] (push-node doc (assoc (sidenote-data token) :type :sidenote-ref) )) (defmethod apply-token "sidenote_anchor" [doc token] doc) -(defmethod apply-token "sidenote_open" [doc token] (-> doc (assoc :sidenotes? true) (open-node :sidenote {:ref (get-in* token [:meta :id])}))) +(defmethod apply-token "sidenote_open" [doc token] (-> doc + (assoc :sidenotes? true) + (open-node :sidenote nil (sidenote-data token)))) (defmethod apply-token "sidenote_close" [doc token] (close-node doc)) (defmethod apply-token "sidenote_block_open" [doc token] (-> doc (assoc :sidenotes? true) (open-node :sidenote {:ref (get-in* token [:meta :id])}))) (defmethod apply-token "sidenote_block_close" [doc token] (close-node doc)) diff --git a/src/nextjournal/markdown/transform.cljc b/src/nextjournal/markdown/transform.cljc index ee5b2cf..2f1d7f3 100644 --- a/src/nextjournal/markdown/transform.cljc +++ b/src/nextjournal/markdown/transform.cljc @@ -1,7 +1,9 @@ (ns nextjournal.markdown.transform "transform markdown data as returned by `nextjournal.markdown/parse` into other formats, currently: * hiccup" - (:require [lambdaisland.uri.normalize :as uri.normalize])) + {:nextjournal.clerk/visibility {:code :hide :result :hide}} + (:require [lambdaisland.uri.normalize :as uri.normalize] + [clojure.string :as str])) ;; helpers (defn guard [pred val] (when (pred val) val)) @@ -108,9 +110,9 @@ a paragraph :table-data (fn [ctx {:as node :keys [attrs]}] (into-markup [:td {:style (table-alignment attrs)}] ctx node)) ;; sidenodes - :sidenote-ref (partial into-markup [:sup.sidenote-ref]) - :sidenote (fn [ctx {:as node :keys [attrs]}] - (into-markup [:span.sidenote [:sup {:style {:margin-right "3px"}} (-> attrs :ref inc)]] + :sidenote-ref (fn [_ {:keys [ref]}] [:sup.sidenote-ref (str (inc ref))]) + :sidenote (fn [ctx {:as node :keys [ref]}] + (into-markup [:span.sidenote [:sup {:style {:margin-right "3px"}} (str (inc ref))]] ctx node)) ;; TOC @@ -194,3 +196,223 @@ par two" ;; wrap something around the default :paragraph (fn [{:as ctx d :default} node] [:div.p-container (d ctx node)])))) ) + +;; Text Transform +(defn write [ctx & strs] (update ctx ::buf into strs)) + +;; ctx -> node -> ctx +(defn write-node [ctx {:as node :keys [type]}] + (if-some [handler (get ctx type)] + (handler ctx node) + (throw (ex-info (str "unhandled node type: " type) {:node node})))) + +(defn write-child-nodes [ctx node] + (update (reduce write-node (update ctx ::parents conj (:type node)) (:content node)) + ::parents pop)) + +;; {node ->} str +(def new-line "\n") +(def block-end "\n\n") +(def code-fence "```") +(defn code-fence+info [_ {:keys [language]}] (str code-fence language new-line)) +(def tab "indent unit" " ") +(defn heading-marker [_ {:keys [heading-level]}] + (str (str/join (repeat heading-level "#")) " ")) + +;; handler -> handler +(defn ?->fn [m] (cond-> m (not (fn? m)) constantly)) +(defn before [bf f] (fn [ctx n] (f (write ctx ((?->fn bf) ctx n)) n))) +(defn after [af f] (fn [ctx n] + (let [ctx' (f ctx n)] + (write ctx' ((?->fn af) ctx' n))))) + +(defn block [f] (after block-end f)) + +;; nest children +(defn prepend-to-child-nodes [bf] (before bf write-child-nodes)) +(defn append-to-child-nodes [af] (after af write-child-nodes)) +(defn wrap-child-nodes [bf af] (after af (before bf write-child-nodes))) +(defn wrap-mark [m] (wrap-child-nodes m m)) + +(def top? (comp #{:doc} peek ::parents)) +(defn quote? [{::keys [parents]}] (some #{:blockquote} parents)) +(defn list-container [{::keys [parents]}] (some #{:bullet-list :numbered-list} parents)) +(defn write-list-padding [{:as ctx ::keys [parents]}] + (apply write ctx (repeat (dec (count (keep #{:bullet-list :numbered-list :todo-list} parents))) tab))) + +(defn write-list [ctx n] + (-> ctx + (write-child-nodes n) + (cond-> (top? ctx) (write new-line)))) + +(defn item-marker [{:as ctx ::keys [item-number]}] + (case (list-container ctx) + :bullet-list "* " + :numbered-list (str item-number ". "))) + +(defn write-sidenote [ctx {:as node :keys [label]}] + (-> ctx (write "[^" label "]: ") (write-child-nodes node) (write new-line))) + +(def default-md-renderers + {:doc write-child-nodes + :toc (fn [ctx n] ctx) ;; ignore toc + :text (fn [ctx {:keys [text]}] (write ctx text)) + :heading (block (prepend-to-child-nodes heading-marker)) + :ruler (block (fn [ctx _] (write ctx "---"))) + :paragraph (block + (prepend-to-child-nodes + (fn [ctx _] (when (quote? ctx) "> ")))) + :plain (append-to-child-nodes new-line) + :softbreak (fn [ctx _] + (-> ctx + (write new-line) + (cond-> (list-container ctx) (-> write-list-padding (write " "))) + (cond-> (quote? ctx) (write "> ")))) + :blockquote write-child-nodes + + :formula (fn [ctx {:keys [text]}] (write ctx (str "$" text "$"))) + :block-formula (block (fn [ctx {:keys [text]}] (write ctx (str "$$" (str/trim text) "$$")))) + + ;; marks + :em (wrap-mark "_") + :strong (wrap-mark "**") + :monospace (wrap-mark "`") + :strikethrough (wrap-mark "~~") + :hashtag (fn [ctx {:keys [text]}] (write ctx (str \# text))) + :link (fn [ctx {:as n :keys [attrs]}] + (-> ctx + (write "[") + (write-child-nodes n) + (write "](" (:href attrs) ")"))) + :internal-link (fn [ctx {:keys [text]}] (write ctx "[[" text "]]")) + + :code (block (wrap-child-nodes code-fence+info code-fence)) + + ;; lists + :bullet-list write-list + :numbered-list (fn [ctx n] + (-> ctx + (assoc ::item-number (-> n :attrs (:start 1))) + (write-list n) + (dissoc ::item-number))) + :todo-list write-list + :list-item (fn [{:as ctx ::keys [item-number]} n] + (-> ctx + (cond-> item-number (update ::item-number inc)) + write-list-padding + (write (item-marker ctx)) + (write-child-nodes n))) + :todo-item (fn [ctx {:as n :keys [attrs]}] + (-> ctx + write-list-padding + (write (str "- [" (if (:checked attrs) "x" " ") "] ")) + (write-child-nodes n))) + + :image (fn [{:as ctx ::keys [parents]} {:as n :keys [attrs]}] + (-> ctx + (write "![") + (write-child-nodes n) + (write "](" (:src attrs) ")") + (cond-> (top? ctx) (write block-end)))) + + :sidenote-ref (fn [ctx {:keys [label]}] (write ctx "[^" label "]")) + :sidenote (fn [ctx n] (update ctx ::sidenotes conj n)) + }) + +(defn ->md + ([doc] (->md default-md-renderers doc)) + ([{:as ctx ::keys [sidenotes]} doc] + (as-> ctx c + (write-node c doc) + (reduce write-sidenote c (reverse sidenotes)) + (str (str/trim (apply str (reverse (::buf c)))) "\n")))) + +(comment + (do + (def doc (nextjournal.markdown/parse "# Ahoi +this is *just* a __strong__ ~~text~~ with a $\\phi$ and a #hashtag + +this is an ![inline-image](/some/src) and a [_link_](/foo/bar) + +par with a sidenote at the end[^sidenote] and another[^sn2] somewhere + +```clojure +(+ 1 2) +``` + +$$ +\\int_a^b\\phi(t)dt +$$ + +* _this_ + + * sub1 + * sub2 some bla + bla bla +* is not + + 2. nsub1 + 3. nsub2 + 4. nsub3 +* thight + - [ ] undone + - [x] done +* > and + > a nice + > quote + +![block *image*](/some/src) + +> so what +> is this + +1. one +2. two +--- + +another + +[^sidenote]: Here a __description__ +[^sn2]: And some _other_ +")) + + (-> doc + ->md + #_ nextjournal.markdown/parse + )) + + + (nextjournal.markdown/parse " +this is ![alt](/the/hell/is/you \"hey\") inline") + (nextjournal.markdown/parse " +[_alt_](/the/hell/is/you)") + + (nextjournal.markdown/parse " +par with a sidenote at the end[^sidenote] and another[^sn2] somewhere + +[^sidenote]: some desc +[^sn2]: some other desc +") + + (nextjournal.markdown/parse " +a [_link text_](/foo/bar) and a sn [^sidenote] + +[^sidenote]: what a __description__") + + (nextjournal.markdown/tokenize " +a [_link text_](/foo/bar) and a sn") + + (nextjournal.markdown/tokenize " +a [_link_](/foo/bar) link and a sidenote at the end[^sidenote] + +[^sidenote]: what a sn") + + (nextjournal.markdown/parse " +* foo bar + bla bla +* and what +* > is this + > for a + > nice quote") + + ) From ae09abea4262346b58fb45de0f0845e996bd9633 Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Wed, 28 Sep 2022 14:23:11 +0200 Subject: [PATCH 02/13] Add notebook --- notebooks/clerk_to_markdown.clj | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 notebooks/clerk_to_markdown.clj diff --git a/notebooks/clerk_to_markdown.clj b/notebooks/clerk_to_markdown.clj new file mode 100644 index 0000000..f522fda --- /dev/null +++ b/notebooks/clerk_to_markdown.clj @@ -0,0 +1,29 @@ +;; # 🗜 Clerk to Markdown +(ns clerk_to_markdown + (:require [nextjournal.clerk :as clerk] + [nextjournal.clerk.parser :as clerk.parser] + [nextjournal.markdown.transform :as md.transform])) + +;; This notebook shows how to transform a Clojure namespace with Clerk style _literal fragments_ into a [markdown](https://daringfireball.net/projects/markdown/) file. + +(def this-notebook "notebooks/clerk_to_markdown.clj") + +;; A clerk notebook is composed of blocks, of type `:markdown` and `:code`. With recent additions to the markdown library we can turn our markdown AST data back into markdown text. + +;; This function turns a Clerk block into a string + +(defn block->md [{:as block :keys [type text doc]}] + (case type + :code (str "```clojure\n" text "\n```\n\n") + :markdown (md.transform/->md doc))) + +;; to put everything together, parse this notebook with Clerk and emit markdown as follows. + +^{::clerk/viewer '(fn [s _] (v/html [:pre s]))} +(->> this-notebook + (clerk.parser/parse-file {:doc? true}) + :blocks + (map block->md) + (apply str)) + +;; To learn more about clerk visit our [github page](https://github.com/nextjournal/clerk). From 10d01b478b7b2ee864d9ea6d49be5c0a483cafb4 Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Wed, 28 Sep 2022 14:44:48 +0200 Subject: [PATCH 03/13] Fix sidenotes / Add test --- deps.edn | 1 + src/nextjournal/markdown/transform.cljc | 21 +++++-- test/nextjournal/markdown/transform_test.cljc | 62 +++++++++++++++++++ 3 files changed, 78 insertions(+), 6 deletions(-) create mode 100644 test/nextjournal/markdown/transform_test.cljc diff --git a/deps.edn b/deps.edn index a8e3d1d..326bab0 100644 --- a/deps.edn +++ b/deps.edn @@ -17,6 +17,7 @@ "notebooks/pandoc.clj" "notebooks/parsing_extensibility.clj" "notebooks/benchmarks.clj" + "notebooks/clerk_to_markdown.clj" "notebooks/tight_lists.clj"]}} :quiet diff --git a/src/nextjournal/markdown/transform.cljc b/src/nextjournal/markdown/transform.cljc index 2f1d7f3..44ead0c 100644 --- a/src/nextjournal/markdown/transform.cljc +++ b/src/nextjournal/markdown/transform.cljc @@ -321,10 +321,10 @@ par two" (defn ->md ([doc] (->md default-md-renderers doc)) - ([{:as ctx ::keys [sidenotes]} doc] + ([ctx doc] (as-> ctx c (write-node c doc) - (reduce write-sidenote c (reverse sidenotes)) + (reduce write-sidenote c (reverse (::sidenotes c))) (str (str/trim (apply str (reverse (::buf c)))) "\n")))) (comment @@ -349,14 +349,17 @@ $$ * sub1 * sub2 some bla bla bla + * is not 2. nsub1 3. nsub2 4. nsub3 + * thight - [ ] undone - [x] done + * > and > a nice > quote @@ -376,10 +379,16 @@ another [^sn2]: And some _other_ ")) - (-> doc - ->md - #_ nextjournal.markdown/parse - )) + + (->md doc ) + + (::buf (write-node default-md-renderers doc)) + + + (def doc2 (-> doc ->md nextjournal.markdown/parse)) + (= (:content doc) (= (:content doc2))) + doc + doc2) (nextjournal.markdown/parse " diff --git a/test/nextjournal/markdown/transform_test.cljc b/test/nextjournal/markdown/transform_test.cljc new file mode 100644 index 0000000..a4b05ff --- /dev/null +++ b/test/nextjournal/markdown/transform_test.cljc @@ -0,0 +1,62 @@ +(ns nextjournal.markdown.transform-test + (:require #?(:clj [clojure.test :refer :all] + :cljs [cljs.test :refer (deftest testing is)]) + [matcher-combinators.test] + [nextjournal.markdown :as md] + [nextjournal.markdown.transform :as md.transform])) + +(def test-text "# Ahoi +this is *just* a __strong__ ~~text~~ with a $\\phi$ and a #hashtag + +this is an ![inline-image](/some/src) and a [_link_](/foo/bar) + +par with a sidenote at the end[^sidenote] and another[^sn2] somewhere + +```clojure +(+ 1 2) +``` + +$$\\int_a^b\\phi(t)dt$$ + +* _this_ + + * sub1 + * sub2 some bla + bla bla + +* is not + + 2. nsub1 + 3. nsub2 + 4. nsub3 + +* thight + - [ ] undone + - [x] done + +* > and + > a nice + > quote + +![block *image*](/some/src) + +> so what +> is this + +1. one +2. two +--- + +another + +[^sidenote]: Here a __description__ +[^sn2]: And some _other_ +") + +(deftest ->md + (let [doc (md/parse test-text)] + + (is (= doc + (-> doc + md.transform/->md + md/parse))))) From 4f97dd7a23ae81b4dc6642e0eb480ae62ccca563 Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Wed, 28 Sep 2022 17:16:07 +0200 Subject: [PATCH 04/13] Transform tables --- src/nextjournal/markdown/transform.cljc | 106 +++++++++--------- test/nextjournal/markdown/transform_test.cljc | 8 ++ 2 files changed, 63 insertions(+), 51 deletions(-) diff --git a/src/nextjournal/markdown/transform.cljc b/src/nextjournal/markdown/transform.cljc index 44ead0c..68af974 100644 --- a/src/nextjournal/markdown/transform.cljc +++ b/src/nextjournal/markdown/transform.cljc @@ -253,6 +253,43 @@ par two" (defn write-sidenote [ctx {:as node :keys [label]}] (-> ctx (write "[^" label "]: ") (write-child-nodes node) (write new-line))) +(declare ->md) +(defn process-table-cell [ctx node] + (-> node (select-keys [:attrs]) (assoc :text (str/trim (->md (dissoc ctx ::buf ::sidenotes) node))))) + +(defn write-row [col-widths ctx row] + (as-> ctx c + (write c "|") + (reduce-kv (fn [ctx i {:as cell :keys [text]}] + (as-> ctx c + (write c " ") + (write c text) + (apply write c (repeat (- (col-widths i) (count text)) " ")) + (write c " |"))) c (vec row)) + (write c new-line))) + +(defn write-head-line [col-widths ctx row] + (as-> ctx c + (write c "|") + (reduce-kv (fn [ctx i {{:keys [style]} :attrs}] + (as-> ctx c + (write c (if (#{"text-align:center" "text-align:left"} style) ":" "-")) + (apply write c (repeat (col-widths i) "-")) + (write c (if (#{"text-align:center" "text-align:right"} style) ":" "-") "|"))) c (vec row)) + (write c new-line))) + +(defn write-table [{:as ctx ::keys [table]}] + (def table table) + (let [[head & body :as rows] (:rows table) + column-widths (mapv (fn [i] (apply max (map (comp count :text #(nth % i)) rows))) + (range (count (first rows))))] + (as-> ctx c + (write-row column-widths c head) + (write-head-line column-widths c head) + (reduce (partial write-row column-widths) c body) + (write c new-line)))) + +;; md text renderers (def default-md-renderers {:doc write-child-nodes :toc (fn [ctx n] ctx) ;; ignore toc @@ -317,7 +354,15 @@ par two" :sidenote-ref (fn [ctx {:keys [label]}] (write ctx "[^" label "]")) :sidenote (fn [ctx n] (update ctx ::sidenotes conj n)) - }) + + ;; tables + :table (fn [ctx n] (-> ctx (assoc ::table {:rows []}) (write-child-nodes n) write-table (dissoc ::table))) + :table-head write-child-nodes + :table-body write-child-nodes + :table-header write-child-nodes + :table-data write-child-nodes + :table-row (fn [ctx {:keys [content]}] + (update-in ctx [::table :rows] conj (map (partial process-table-cell ctx) content)))}) (defn ->md ([doc] (->md default-md-renderers doc)) @@ -327,9 +372,7 @@ par two" (reduce write-sidenote c (reverse (::sidenotes c))) (str (str/trim (apply str (reverse (::buf c)))) "\n")))) -(comment - (do - (def doc (nextjournal.markdown/parse "# Ahoi +#_ (->md (nextjournal.markdown/parse "# Ahoi this is *just* a __strong__ ~~text~~ with a $\\phi$ and a #hashtag this is an ![inline-image](/some/src) and a [_link_](/foo/bar) @@ -371,57 +414,18 @@ $$ 1. one 2. two + --- another +| _col1_ | col2 | +|:-------------:|:------------------------| +| whatthasdasfd | hell | +| this is | insane as as as as as f | + +end + [^sidenote]: Here a __description__ [^sn2]: And some _other_ ")) - - - (->md doc ) - - (::buf (write-node default-md-renderers doc)) - - - (def doc2 (-> doc ->md nextjournal.markdown/parse)) - (= (:content doc) (= (:content doc2))) - doc - doc2) - - - (nextjournal.markdown/parse " -this is ![alt](/the/hell/is/you \"hey\") inline") - (nextjournal.markdown/parse " -[_alt_](/the/hell/is/you)") - - (nextjournal.markdown/parse " -par with a sidenote at the end[^sidenote] and another[^sn2] somewhere - -[^sidenote]: some desc -[^sn2]: some other desc -") - - (nextjournal.markdown/parse " -a [_link text_](/foo/bar) and a sn [^sidenote] - -[^sidenote]: what a __description__") - - (nextjournal.markdown/tokenize " -a [_link text_](/foo/bar) and a sn") - - (nextjournal.markdown/tokenize " -a [_link_](/foo/bar) link and a sidenote at the end[^sidenote] - -[^sidenote]: what a sn") - - (nextjournal.markdown/parse " -* foo bar - bla bla -* and what -* > is this - > for a - > nice quote") - - ) diff --git a/test/nextjournal/markdown/transform_test.cljc b/test/nextjournal/markdown/transform_test.cljc index a4b05ff..90f8a80 100644 --- a/test/nextjournal/markdown/transform_test.cljc +++ b/test/nextjournal/markdown/transform_test.cljc @@ -45,10 +45,18 @@ $$\\int_a^b\\phi(t)dt$$ 1. one 2. two + --- another +| _col1_ | col2 | +|:-------------:|:------------------------| +| whatthasdasfd | hell | +| this is | insane as as as as as f | + +end + [^sidenote]: Here a __description__ [^sn2]: And some _other_ ") From c7ce7f692e4f45b23ca1391fed0a489e366f2915 Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Thu, 29 Sep 2022 18:16:30 +0200 Subject: [PATCH 05/13] Add transformation of a Markdown notebook to Clerk clojure namespace --- notebooks/clerk_to_markdown.clj | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/notebooks/clerk_to_markdown.clj b/notebooks/clerk_to_markdown.clj index f522fda..079b07d 100644 --- a/notebooks/clerk_to_markdown.clj +++ b/notebooks/clerk_to_markdown.clj @@ -1,6 +1,8 @@ ;; # 🗜 Clerk to Markdown (ns clerk_to_markdown - (:require [nextjournal.clerk :as clerk] + {:nextjournal.clerk/no-cache true} + (:require [clojure.string :as str] + [nextjournal.clerk :as clerk] [nextjournal.clerk.parser :as clerk.parser] [nextjournal.markdown.transform :as md.transform])) @@ -10,7 +12,7 @@ ;; A clerk notebook is composed of blocks, of type `:markdown` and `:code`. With recent additions to the markdown library we can turn our markdown AST data back into markdown text. -;; This function turns a Clerk block into a string +;; This function turns a Clerk block into a markdown string (defn block->md [{:as block :keys [type text doc]}] (case type @@ -20,10 +22,26 @@ ;; to put everything together, parse this notebook with Clerk and emit markdown as follows. ^{::clerk/viewer '(fn [s _] (v/html [:pre s]))} -(->> this-notebook - (clerk.parser/parse-file {:doc? true}) +(def as-markdown + (->> this-notebook + (clerk.parser/parse-file {:doc? true}) + :blocks + (map block->md) + (apply str))) + +;; We can go back to a clojure namespace as follows +(defn block->clj [{:as block :keys [type text doc]}] + (case type + :code (str "\n" text "\n") + :markdown (apply str + (interleave (repeat ";; ") + (str/split-lines (md.transform/->md doc)) + (repeat "\n"))))) + +^{::clerk/viewer '(fn [s _] (v/html [:pre s]))} +(->> (clerk.parser/parse-markdown-string {:doc? true} as-markdown) :blocks - (map block->md) + (map block->clj) (apply str)) ;; To learn more about clerk visit our [github page](https://github.com/nextjournal/clerk). From 617716cb9fd26cf7fe31d5ea1e0036c17326fb50 Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Thu, 29 Sep 2022 18:25:49 +0200 Subject: [PATCH 06/13] Better title --- notebooks/clerk_to_markdown.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notebooks/clerk_to_markdown.clj b/notebooks/clerk_to_markdown.clj index 079b07d..3dc6f30 100644 --- a/notebooks/clerk_to_markdown.clj +++ b/notebooks/clerk_to_markdown.clj @@ -1,4 +1,4 @@ -;; # 🗜 Clerk to Markdown +;; # 🔄 Clerk to Markdown and Back (ns clerk_to_markdown {:nextjournal.clerk/no-cache true} (:require [clojure.string :as str] From 22758bad01d33bbea6d232ed44219d4d0a2fe2e1 Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Fri, 10 Mar 2023 18:15:07 +0100 Subject: [PATCH 07/13] Bump Clerk --- README.md | 4 ++-- deps.edn | 4 ++-- notebooks/clerk_to_markdown.clj | 4 ++-- notebooks/try.clj | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 1d52afb..d325c00 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/deps.edn b/deps.edn index 1384b1e..0fc4898 100644 --- a/deps.edn +++ b/deps.edn @@ -6,7 +6,7 @@ :aliases {:nextjournal/clerk {:extra-paths ["notebooks"] - :extra-deps {io.github.nextjournal/clerk {:git/sha "60f2399dcdaf39f5d7bc2c213a8dde9a6e0081a9" + :extra-deps {io.github.nextjournal/clerk {:git/sha "711a1b2fae3c212d2c8c2323cf4d53c178766114" :exclusions [io.github.nextjournal/markdown]}} :jvm-opts ["-Dclojure.main.report=stderr" "-Dclerk.resource_manifest={\"/js/viewer.js\" \"js/viewer.js\"}"] ;; @@ -40,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 "711a1b2fae3c212d2c8c2323cf4d53c178766114" :deps/root "render"}}} :build diff --git a/notebooks/clerk_to_markdown.clj b/notebooks/clerk_to_markdown.clj index 241708d..5ab2c64 100644 --- a/notebooks/clerk_to_markdown.clj +++ b/notebooks/clerk_to_markdown.clj @@ -23,7 +23,7 @@ ;; now, to put everything together, parse this notebook with Clerk and emit markdown as follows. -^{::clerk/viewer '(fn [s _] (v/html [:pre s]))} +^{::clerk/viewer '(fn [s _] (nextjournal.clerk.viewer/html [:pre s]))} (def as-markdown (->> this-notebook (clerk.parser/parse-file {:doc? true}) @@ -40,7 +40,7 @@ (str/split-lines (md.transform/->md doc)) (repeat "\n"))))) -^{::clerk/viewer '(fn [s _] (v/html [:pre s]))} +^{::clerk/viewer '(fn [s _] (nextjournal.clerk.viewer/html [:pre s]))} (->> (clerk.parser/parse-markdown-string {:doc? true} as-markdown) :blocks (map block->clj) diff --git a/notebooks/try.clj b/notebooks/try.clj index 1c2d3ba..6ee3255 100644 --- a/notebooks/try.clj +++ b/notebooks/try.clj @@ -6,8 +6,8 @@ ^{::clerk/width :full ::clerk/visibility {:code :fold} ::clerk/viewer {:render-fn '(fn [_] - (v/html - (reagent/with-let + (nextjournal.clerk.viewer/html + (reagent.core/with-let [init-text "# 👋 Hello Markdown ```clojure id=xxyyzzww @@ -19,13 +19,13 @@ - [ ] _stuff_ here" text->state (fn [text] (as-> (md/parse text) parsed {:parsed parsed :hiccup (md.transform/->hiccup md.demo/renderers parsed)})) - !state (reagent/atom (text->state init-text)) + !state (reagent.core/atom (text->state init-text)) text-update! (fn [text] (reset! !state (text->state text)))] [:div.grid.grid-cols-2.m-10 [:div.m-2.p-2.text-xl.border-2.overflow-y-scroll.bg-slate-100 {:style {:height "20rem"}} [md.demo/editor {:doc-update text-update! :doc init-text}]] [:div.m-2.p-2.font-medium.overflow-y-scroll {:style {:height "20rem"}} [md.demo/inspect-expanded (:parsed @!state)]] [:div.m-2.p-2.overflow-x-scroll [md.demo/inspect-expanded (:hiccup @!state)]] - [:div.m-2.p-2.bg-slate-50.viewer-markdown [v/html (:hiccup @!state)]]])))}} + [:div.m-2.p-2.bg-slate-50.viewer-markdown [nextjournal.clerk.viewer/html (:hiccup @!state)]]])))}} (Object.) From deaa4b047d8c115b947b5018f68af22ce2774ce6 Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Fri, 10 Mar 2023 18:20:33 +0100 Subject: [PATCH 08/13] Reorg / improve tests --- src/nextjournal/markdown/transform.cljc | 38 ++++++++++--------- test/nextjournal/markdown/transform_test.cljc | 4 +- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/nextjournal/markdown/transform.cljc b/src/nextjournal/markdown/transform.cljc index 646693d..10c7765 100644 --- a/src/nextjournal/markdown/transform.cljc +++ b/src/nextjournal/markdown/transform.cljc @@ -11,6 +11,7 @@ text (apply str (map ->text content)))) +;; ## Hiccup Transform (defn hydrate-toc "Scans doc contents and replaces toc node placeholder with the toc node accumulated during parse." [{:as doc :keys [toc]}] @@ -208,7 +209,7 @@ par two" :paragraph (fn [{:as ctx d :default} node] [:div.p-container (d ctx node)])))) ) -;; Text Transform +;; ## Text Transform (defn write [ctx & strs] (update ctx ::buf into strs)) ;; ctx -> node -> ctx @@ -256,10 +257,20 @@ par two" (write-child-nodes n) (cond-> (top? ctx) (write new-line)))) -(defn item-marker [{:as ctx ::keys [item-number]}] - (case (list-container ctx) - :bullet-list "* " - :numbered-list (str item-number ". "))) +(defn item-marker [{:as ctx ::keys [item-number]} {:keys [type attrs]}] + (if (= :todo-item type) + (str "- [" (if (:checked attrs) "x" " ") "] ") + (case (list-container ctx) + :bullet-list "* " + :numbered-list (str item-number ". ")))) + +(defn write-list-item [{:as ctx ::keys [item-number]} n] + (-> ctx + (cond-> item-number (update ::item-number inc)) + (cond-> (quote? ctx) (write "> ")) + write-list-padding + (write (item-marker ctx n)) + (write-child-nodes n))) (defn write-footnote [ctx {:as node :keys [label ref]}] (-> ctx (write "[^" (or label ref) "]: ") (write-child-nodes node) (write new-line))) @@ -302,7 +313,7 @@ par two" ;; md text renderers (def default-md-renderers - {:doc write-child-nodes + {:doc (block write-child-nodes) :toc (fn [ctx n] ctx) ;; ignore toc :text (fn [ctx {:keys [text]}] (write ctx text)) :heading (block (prepend-to-child-nodes heading-marker)) @@ -316,7 +327,7 @@ par two" (write new-line) (cond-> (list-container ctx) (-> write-list-padding (write " "))) (cond-> (quote? ctx) (write "> ")))) - :blockquote write-child-nodes + :blockquote (block write-child-nodes) :formula (fn [ctx {:keys [text]}] (write ctx (str "$" text "$"))) :block-formula (block (fn [ctx {:keys [text]}] (write ctx (str "$$" (str/trim text) "$$")))) @@ -344,17 +355,8 @@ par two" (write-list n) (dissoc ::item-number))) :todo-list write-list - :list-item (fn [{:as ctx ::keys [item-number]} n] - (-> ctx - (cond-> item-number (update ::item-number inc)) - write-list-padding - (write (item-marker ctx)) - (write-child-nodes n))) - :todo-item (fn [ctx {:as n :keys [attrs]}] - (-> ctx - write-list-padding - (write (str "- [" (if (:checked attrs) "x" " ") "] ")) - (write-child-nodes n))) + :list-item write-list-item + :todo-item write-list-item :image (fn [{:as ctx ::keys [parents]} {:as n :keys [attrs]}] (-> ctx diff --git a/test/nextjournal/markdown/transform_test.cljc b/test/nextjournal/markdown/transform_test.cljc index 90f8a80..bf5ada7 100644 --- a/test/nextjournal/markdown/transform_test.cljc +++ b/test/nextjournal/markdown/transform_test.cljc @@ -55,6 +55,9 @@ another | whatthasdasfd | hell | | this is | insane as as as as as f | +> * one +> * two + end [^sidenote]: Here a __description__ @@ -63,7 +66,6 @@ end (deftest ->md (let [doc (md/parse test-text)] - (is (= doc (-> doc md.transform/->md From ca3d8a023c6c1789ad556888eed1603ccf0f9227 Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Fri, 14 Apr 2023 15:47:12 +0200 Subject: [PATCH 09/13] Bump graal JS --- deps.edn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps.edn b/deps.edn index 0fc4898..522dfb1 100644 --- a/deps.edn +++ b/deps.edn @@ -1,7 +1,7 @@ {:paths ["src" "resources"] :deps {applied-science/js-interop {:mvn/version "0.3.3"} org.clojure/data.json {:mvn/version "2.4.0"} - org.graalvm.js/js {:mvn/version "21.3.2.1"}} + org.graalvm.js/js {:mvn/version "22.3.1"}} :aliases {:nextjournal/clerk From 3b152ae6da6bd520ec4dcd9dbe56b9cab252958f Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Fri, 14 Apr 2023 17:26:02 +0200 Subject: [PATCH 10/13] Keep markup from markdown tokens --- src/nextjournal/markdown.cljs | 4 +- src/nextjournal/markdown/parser.cljc | 62 +++++++++++-------- src/nextjournal/markdown/transform.cljc | 28 +++++++++ test/nextjournal/markdown/transform_test.cljc | 6 +- test/test_runner.clj | 3 +- 5 files changed, 71 insertions(+), 32 deletions(-) diff --git a/src/nextjournal/markdown.cljs b/src/nextjournal/markdown.cljs index 53b851d..77f8636 100644 --- a/src/nextjournal/markdown.cljs +++ b/src/nextjournal/markdown.cljs @@ -7,7 +7,9 @@ (extend-type Token ILookup - (-lookup [this key] (j/get this key))) + (-lookup + ([this key] (j/get this key)) + ([this key not-found] (j/get this key not-found)))) (def tokenize md/tokenize) diff --git a/src/nextjournal/markdown/parser.cljc b/src/nextjournal/markdown/parser.cljc index 1946520..74b5713 100644 --- a/src/nextjournal/markdown/parser.cljc +++ b/src/nextjournal/markdown/parser.cljc @@ -135,10 +135,9 @@ ;; node constructors (defn node - [type content attrs top-level] + [type content keyvals] (cond-> {:type type :content content} - (seq attrs) (assoc :attrs attrs) - (seq top-level) (merge top-level))) + (seq keyvals) (merge keyvals))) (defn empty-text-node? [{text :text t :type}] (and (= :text t) (empty? text))) @@ -158,10 +157,9 @@ (defn open-node ([doc type] (open-node doc type {})) - ([doc type attrs] (open-node doc type attrs {})) - ([doc type attrs top-level] + ([doc type keyvals] (-> doc - (push-node (node type [] attrs top-level)) + (push-node (node type [] keyvals)) (update ::path into [:content -1])))) ;; after closing a node, document ::path will point at it @@ -305,7 +303,7 @@ end" doc) ;; blocks -(defmethod apply-token "heading_open" [doc token] (open-node doc :heading {} {:heading-level (hlevel token)})) +(defmethod apply-token "heading_open" [doc token] (open-node doc :heading (assoc (select-keys token [:markup]) :heading-level (hlevel token)))) (defmethod apply-token "heading_close" [doc {doc-level :level}] (let [{:as doc ::keys [path]} (close-node doc) doc' (assign-node-id+emoji doc) @@ -320,13 +318,13 @@ end" (defmethod apply-token "paragraph_open" [doc {:as _token :keys [hidden]}] (open-node doc (if hidden :plain :paragraph))) (defmethod apply-token "paragraph_close" [doc _token] (close-node doc)) -(defmethod apply-token "bullet_list_open" [doc {{:as attrs :keys [has-todos]} :attrs}] (open-node doc (if has-todos :todo-list :bullet-list) attrs)) +(defmethod apply-token "bullet_list_open" [doc {:as token :keys [attrs]}] (open-node doc (if (:has-todos attrs) :todo-list :bullet-list) (select-keys token [:attrs]))) (defmethod apply-token "bullet_list_close" [doc _token] (close-node doc)) -(defmethod apply-token "ordered_list_open" [doc {:keys [attrs]}] (open-node doc :numbered-list attrs)) +(defmethod apply-token "ordered_list_open" [doc token] (open-node doc :numbered-list (select-keys token [:attrs]))) (defmethod apply-token "ordered_list_close" [doc _token] (close-node doc)) -(defmethod apply-token "list_item_open" [doc {{:as attrs :keys [todo]} :attrs}] (open-node doc (if todo :todo-item :list-item) attrs)) +(defmethod apply-token "list_item_open" [doc {:as token :keys [attrs]}] (open-node doc (if (:todo attrs) :todo-item :list-item) (select-keys token [:attrs :markup]))) (defmethod apply-token "list_item_close" [doc _token] (close-node doc)) (defmethod apply-token "math_block" [doc {text :content}] (push-node doc (block-formula text))) @@ -341,15 +339,17 @@ end" (defmethod apply-token "tocBody" [doc _token] doc) ;; ignore body (defmethod apply-token "tocClose" [doc _token] (-> doc close-node (update-current dissoc :content))) -(defmethod apply-token "code_block" [doc {:as _token c :content}] +(defmethod apply-token "code_block" [doc {:as token :keys [content]}] (-> doc - (open-node :code) - (push-node (text-node c)) + (open-node :code (select-keys token [:markup])) + (push-node (text-node content)) close-node)) -(defmethod apply-token "fence" [doc {:as _token i :info c :content}] +(defmethod apply-token "fence" [doc {:as token :keys [info content]}] (-> doc - (open-node :code {} (assoc (parse-fence-info i) :info i)) - (push-node (text-node c)) + (open-node :code (-> (select-keys token [:markup]) + (assoc :info info) + (merge (parse-fence-info info)))) + (push-node (text-node content)) close-node)) ;; footnotes @@ -363,7 +363,7 @@ end" ;; consider an offset in case we're parsing multiple inputs into the same context (let [ref (+ (get-in* token [:meta :id]) footnote-offset) label (get-in* token [:meta :label])] - (open-node doc :footnote nil (cond-> {:ref ref} label (assoc :label label))))) + (open-node doc :footnote (cond-> {:ref ref} label (assoc :label label))))) (defmethod apply-token "footnote_close" [doc token] (close-node doc)) @@ -384,7 +384,7 @@ end" (defn footnote->sidenote [{:keys [ref label content]}] ;; this assumes the footnote container is a paragraph, won't work for lists - (node :sidenote (-> content first :content) nil (cond-> {:ref ref} label (assoc :label label)))) + (node :sidenote (-> content first :content) (cond-> {:ref ref} label (assoc :label label)))) (defn node-with-sidenote-refs [p-node] (loop [l (->zip p-node) refs []] @@ -457,11 +457,11 @@ And what. (defmethod apply-token "thead_close" [doc _token] (close-node doc)) (defmethod apply-token "tr_open" [doc _token] (open-node doc :table-row)) (defmethod apply-token "tr_close" [doc _token] (close-node doc)) -(defmethod apply-token "th_open" [doc token] (open-node doc :table-header (:attrs token))) +(defmethod apply-token "th_open" [doc token] (open-node doc :table-header (select-keys token [:attrs]))) (defmethod apply-token "th_close" [doc _token] (close-node doc)) (defmethod apply-token "tbody_open" [doc _token] (open-node doc :table-body)) (defmethod apply-token "tbody_close" [doc _token] (close-node doc)) -(defmethod apply-token "td_open" [doc token] (open-node doc :table-data (:attrs token))) +(defmethod apply-token "td_open" [doc token] (open-node doc :table-data (select-keys token [:attrs]))) (defmethod apply-token "td_close" [doc _token] (close-node doc)) (comment @@ -577,18 +577,25 @@ _this #should be a tag_, but this [_actually #foo shouldnt_](/bar/) is not." (defmethod apply-token "hardbreak" [doc _token] (push-node doc {:type :hardbreak})) ;; images -(defmethod apply-token "image" [doc {:keys [attrs children]}] (-> doc (open-node :image attrs) (apply-tokens children) close-node)) +(defmethod apply-token "image" [doc {:as token :keys [children]}] + (-> doc + (open-node :image (select-keys token [:attrs])) + (apply-tokens children) + close-node)) ;; marks -(defmethod apply-token "em_open" [doc _token] (open-node doc :em)) +(defmethod apply-token "em_open" [doc token] (open-node doc :em (select-keys token [:markup]))) (defmethod apply-token "em_close" [doc _token] (close-node doc)) -(defmethod apply-token "strong_open" [doc _token] (open-node doc :strong)) +(defmethod apply-token "strong_open" [doc token] (open-node doc :strong (select-keys token [:markup]))) (defmethod apply-token "strong_close" [doc _token] (close-node doc)) -(defmethod apply-token "s_open" [doc _token] (open-node doc :strikethrough)) +(defmethod apply-token "s_open" [doc token] (open-node doc :strikethrough (select-keys token [:markup]))) (defmethod apply-token "s_close" [doc _token] (close-node doc)) -(defmethod apply-token "link_open" [doc token] (open-node doc :link (:attrs token))) +(defmethod apply-token "link_open" [doc token] (open-node doc :link (select-keys token [:attrs]))) (defmethod apply-token "link_close" [doc _token] (close-node doc)) -(defmethod apply-token "code_inline" [doc {text :content}] (-> doc (open-node :monospace) (push-node (text-node text)) close-node)) +(defmethod apply-token "code_inline" [doc {:as token :keys [content]}] + (-> doc (open-node :monospace (select-keys token [:markup])) + (push-node (text-node content)) + close-node)) ;; html (ignored) (defmethod apply-token "html_inline" [doc _] doc) @@ -737,5 +744,6 @@ some final par" (into [] (comp (mapcat (partial tree-seq (comp seq :children) :children)) - (map #(select-keys % [:type :content :hidden :level :info :meta]))) + (map (comp #(into {} (filter (comp some? val)) %) + #(select-keys % [:type :content :hidden :level :info :attrs :markup :meta])))) tokens)) diff --git a/src/nextjournal/markdown/transform.cljc b/src/nextjournal/markdown/transform.cljc index 10c7765..eccd45b 100644 --- a/src/nextjournal/markdown/transform.cljc +++ b/src/nextjournal/markdown/transform.cljc @@ -442,3 +442,31 @@ end [^sidenote]: Here a __description__ [^sn2]: And some _other_ ")) + + +(comment + ;; nested marks + (->md (nextjournal.markdown/parse " +some *emph around a __strong__* bit")) + + + ;; preserving markup + (->md (nextjournal.markdown/parse " +Preserve Markup +--------------- + +- this _should_ and *could* +- look **the** __very same__ +")) + + (nextjournal.markdown.parser/flatten-tokens (nextjournal.markdown/tokenize " +``` +fence +``` + +1) one +2) two + +- this _should_ and *could* +- look **the** __very same__ and ~~why~~ +"))) diff --git a/test/nextjournal/markdown/transform_test.cljc b/test/nextjournal/markdown/transform_test.cljc index bf5ada7..5d18bc1 100644 --- a/test/nextjournal/markdown/transform_test.cljc +++ b/test/nextjournal/markdown/transform_test.cljc @@ -6,7 +6,7 @@ [nextjournal.markdown.transform :as md.transform])) (def test-text "# Ahoi -this is *just* a __strong__ ~~text~~ with a $\\phi$ and a #hashtag +this is _just_ a **strong** ~~text~~ with a $\\phi$ and a #hashtag this is an ![inline-image](/some/src) and a [_link_](/foo/bar) @@ -38,7 +38,7 @@ $$\\int_a^b\\phi(t)dt$$ > a nice > quote -![block *image*](/some/src) +![block _image_](/some/src) > so what > is this @@ -60,7 +60,7 @@ another end -[^sidenote]: Here a __description__ +[^sidenote]: Here a _description_ [^sn2]: And some _other_ ") diff --git a/test/test_runner.clj b/test/test_runner.clj index 22e6521..d5b5c48 100644 --- a/test/test_runner.clj +++ b/test/test_runner.clj @@ -1,6 +1,7 @@ (ns test-runner (:require [clojure.test] - [nextjournal.markdown-test])) + [nextjournal.markdown-test] + [nextjournal.markdown.transform-test])) (defn run [_] (let [{:keys [fail error]} (clojure.test/run-all-tests #"nextjournal\.markdown.*-test")] From eb7f8a23f7421104f4142156abd14084d1b8c6bc Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Fri, 14 Apr 2023 17:44:41 +0200 Subject: [PATCH 11/13] A first step toward preserving markup --- src/nextjournal/markdown/transform.cljc | 14 ++++++++++---- test/nextjournal/markdown/transform_test.cljc | 12 ++++++------ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/nextjournal/markdown/transform.cljc b/src/nextjournal/markdown/transform.cljc index eccd45b..2e19b36 100644 --- a/src/nextjournal/markdown/transform.cljc +++ b/src/nextjournal/markdown/transform.cljc @@ -230,6 +230,7 @@ par two" (def tab "indent unit" " ") (defn heading-marker [_ {:keys [heading-level]}] (str (str/join (repeat heading-level "#")) " ")) +(defn node-markup [default] (fn [_ {:keys [markup]}] (or markup default))) ;; handler -> handler (defn ?->fn [m] (cond-> m (not (fn? m)) constantly)) @@ -244,7 +245,9 @@ par two" (defn prepend-to-child-nodes [bf] (before bf write-child-nodes)) (defn append-to-child-nodes [af] (after af write-child-nodes)) (defn wrap-child-nodes [bf af] (after af (before bf write-child-nodes))) -(defn wrap-mark [m] (wrap-child-nodes m m)) +(defn wrap-mark [default-markup] (wrap-child-nodes + (node-markup default-markup) + (node-markup default-markup))) (def top? (comp #{:doc} peek ::parents)) (defn quote? [{::keys [parents]}] (some #{:blockquote} parents)) @@ -257,12 +260,12 @@ par two" (write-child-nodes n) (cond-> (top? ctx) (write new-line)))) -(defn item-marker [{:as ctx ::keys [item-number]} {:keys [type attrs]}] +(defn item-marker [{:as ctx ::keys [item-number]} {:keys [type markup attrs]}] (if (= :todo-item type) (str "- [" (if (:checked attrs) "x" " ") "] ") (case (list-container ctx) - :bullet-list "* " - :numbered-list (str item-number ". ")))) + :bullet-list (str (or markup "*") " ") + :numbered-list (str item-number (or markup ".") " ")))) (defn write-list-item [{:as ctx ::keys [item-number]} n] (-> ctx @@ -457,6 +460,9 @@ Preserve Markup - this _should_ and *could* - look **the** __very same__ + +1) one +2) two ")) (nextjournal.markdown.parser/flatten-tokens (nextjournal.markdown/tokenize " diff --git a/test/nextjournal/markdown/transform_test.cljc b/test/nextjournal/markdown/transform_test.cljc index 5d18bc1..aa8f95e 100644 --- a/test/nextjournal/markdown/transform_test.cljc +++ b/test/nextjournal/markdown/transform_test.cljc @@ -6,7 +6,7 @@ [nextjournal.markdown.transform :as md.transform])) (def test-text "# Ahoi -this is _just_ a **strong** ~~text~~ with a $\\phi$ and a #hashtag +this is *just* a **strong** ~~text~~ with a $\\phi$ and a #hashtag this is an ![inline-image](/some/src) and a [_link_](/foo/bar) @@ -20,8 +20,8 @@ $$\\int_a^b\\phi(t)dt$$ * _this_ - * sub1 - * sub2 some bla + - sub1 + - sub2 some bla bla bla * is not @@ -43,8 +43,8 @@ $$\\int_a^b\\phi(t)dt$$ > so what > is this -1. one -2. two +1) one +2) two --- @@ -60,7 +60,7 @@ another end -[^sidenote]: Here a _description_ +[^sidenote]: Here a __description__ [^sn2]: And some _other_ ") From bc356f503d6c30925c14aff6f8f32618c3e416ec Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Fri, 14 Apr 2023 17:55:19 +0200 Subject: [PATCH 12/13] Support hard line breaks --- src/nextjournal/markdown/transform.cljc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/nextjournal/markdown/transform.cljc b/src/nextjournal/markdown/transform.cljc index 2e19b36..07486ae 100644 --- a/src/nextjournal/markdown/transform.cljc +++ b/src/nextjournal/markdown/transform.cljc @@ -328,7 +328,13 @@ par two" :softbreak (fn [ctx _] (-> ctx (write new-line) - (cond-> (list-container ctx) (-> write-list-padding (write " "))) + (cond-> (list-container ctx) (-> write-list-padding (write tab))) + (cond-> (quote? ctx) (write "> ")))) + :hardbreak (fn [ctx _] + (-> ctx + (write "\\") + (write new-line) + (cond-> (list-container ctx) (-> write-list-padding (write tab))) (cond-> (quote? ctx) (write "> ")))) :blockquote (block write-child-nodes) From c1b60ca3a2ecd52a6a26272baea4deb1851c06c2 Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Wed, 1 Feb 2023 11:52:06 +0100 Subject: [PATCH 13/13] Typing --- src/nextjournal/markdown/transform.cljc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/nextjournal/markdown/transform.cljc b/src/nextjournal/markdown/transform.cljc index 07486ae..cf9b8d8 100644 --- a/src/nextjournal/markdown/transform.cljc +++ b/src/nextjournal/markdown/transform.cljc @@ -314,7 +314,11 @@ par two" (reduce (partial write-row column-widths) c body) (write c new-line)))) -;; md text renderers +;; ## Markdown Text Renderers +;; Dispatch on types, holds renderers +;; +;; { Type : Ctx -> Node -> Ctx } +;; (def default-md-renderers {:doc (block write-child-nodes) :toc (fn [ctx n] ctx) ;; ignore toc