-
Notifications
You must be signed in to change notification settings - Fork 4
/
transform.cljc
207 lines (169 loc) · 7.43 KB
/
transform.cljc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
(ns nextjournal.markdown.transform
"transform markdown data as returned by `nextjournal.markdown/parse` into other formats, currently:
* hiccup")
;; helpers
(defn guard [pred val] (when (pred val) val))
(defn ->text [{:as _node :keys [type text content]}]
(or (when (= :softbreak type) " ")
text
(apply str (map ->text content))))
(defn hydrate-toc
"Scans doc contents and replaces toc node placeholder with the toc node accumulated during parse."
[{:as doc :keys [toc]}]
(update doc :content (partial into [] (map (fn [{:as node t :type}] (if (= :toc t) toc node))))))
(defn table-alignment [{:keys [style]}]
(when (string? style)
(let [[_ alignment] (re-matches #"^text-align:(.+)$" style)]
(when alignment {:text-align alignment}))))
(defn heading-markup [{l :heading-level}] [(keyword (str "h" (or l 1)))])
;; into-markup
(declare ->hiccup)
(defn into-markup
"Takes a hiccup vector, a context and a node, puts node's `:content` into markup mapping through `->hiccup`."
[mkup ctx {:as node :keys [text content]}]
(cond ;; formula nodes are leaves: have text and no contents
text (conj mkup text)
content (into mkup
(keep (partial ->hiccup (assoc ctx ::parent node)))
content)))
(defn toc->hiccup [{:as ctx ::keys [parent]} {:as node :keys [attrs content children]}]
(let [id (:id attrs)
toc-item (cond-> [:div]
(seq content)
(conj [:a {:href (str "#" id) #?@(:cljs [:on-click #(when-some [el (.getElementById js/document id)] (.preventDefault %) (.scrollIntoViewIfNeeded el))])}
(-> node heading-markup (into-markup ctx node))])
(seq children)
(conj (into [:ul] (map (partial ->hiccup (assoc ctx ::parent node))) children)))]
(cond->> toc-item
(= :toc (:type parent))
(conj [:li.toc-item])
(not= :toc (:type parent))
(conj [:div.toc]))))
(comment
;; override toc rendering
(-> "# Hello
a paragraph
[[TOC]]
## Section _nice_ One
### Section Nested
## Section **terrible** Idea
"
nextjournal.markdown/parse
;; :toc
;; ->hiccup #_
(->> (->hiccup (assoc default-hiccup-renderers
:toc (fn [ctx {:as node :keys [content children heading-level]}]
(cond-> [:div]
(seq content) (conj [:span.title {:data-level heading-level} (:id node)])
(seq children) (conj (into [:ul] (map (partial ->hiccup ctx)) children)))))))))
(def default-hiccup-renderers
{:doc (partial into-markup [:div])
:heading (fn [ctx {:as node :keys [attrs]}] (-> (heading-markup node) (conj attrs) (into-markup ctx node)))
:paragraph (partial into-markup [:p])
:plain (partial into-markup [:<>])
:text (fn [_ {:keys [text]}] text)
:hashtag (fn [_ {:keys [text]}] [:a.tag {:href (str "/tags/" text)} (str "#" text)]) ;; TODO: make it configurable
: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)]))
;; code
:code (partial into-markup [:pre.viewer-code.not-prose])
;; breaks
:softbreak (constantly " ")
:hardbreak (constantly [:br])
;; formulas
:formula (partial into-markup [:span.formula])
:block-formula (partial into-markup [:figure.formula])
;; lists
:bullet-list (partial into-markup [:ul])
:list-item (partial into-markup [:li])
:todo-list (partial into-markup [:ul.contains-task-list])
:numbered-list (fn [ctx {:as node :keys [attrs]}] (into-markup [:ol attrs] ctx node))
:todo-item (fn [ctx {:as node :keys [attrs]}]
(into-markup [:li [:input {:type "checkbox" :checked (:checked attrs)}]] ctx node))
;; tables
:table (partial into-markup [:table])
:table-head (partial into-markup [:thead])
:table-body (partial into-markup [:tbody])
:table-row (partial into-markup [:tr])
:table-header (fn [ctx {:as node :keys [attrs]}]
(into-markup (let [ta (table-alignment attrs)] (cond-> [:th] ta (conj {:style ta})))
ctx node))
:table-data (fn [ctx {:as node :keys [attrs]}]
(into-markup (let [ta (table-alignment attrs)] (cond-> [:td] ta (conj {:style ta})))
ctx node))
;; footnotes & sidenodes
:sidenote-container (partial into-markup [:div.sidenote-container])
:sidenote-column (partial into-markup [:div.sidenote-column])
:sidenote-ref (fn [_ {:keys [ref label]}] [:sup.sidenote-ref {:data-label label} (str (inc ref))])
:sidenote (fn [ctx {:as node :keys [ref]}]
(into-markup [:span.sidenote [:sup {:style {:margin-right "3px"}} (str (inc ref))]] ctx node))
:footnote-ref (fn [_ {:keys [ref label]}] [:sup.sidenote-ref {:data-label label} (str (inc ref))])
;; NOTE: there's no default footnote placement (see n.markdown.parser/insert-sidenotes)
:footnote (fn [ctx {:as node :keys [ref label]}]
(into-markup [:div.footnote [:span.footnote-label {:data-ref ref} label]] ctx node))
;; TOC
:toc toc->hiccup
;; marks
:em (partial into-markup [:em])
:strong (partial into-markup [:strong])
:monospace (partial into-markup [:code])
:strikethrough (partial into-markup [:s])
:link (fn [ctx {:as node :keys [attrs]}] (into-markup [:a {:href (:href attrs)}] ctx node))
:internal-link (fn [_ {:keys [attrs text]}] [:a.internal {:href (:href attrs text)} text])
;; default convenience fn to wrap extra markup around the default one from within the overriding function
:default (fn [ctx {:as node t :type}] (when-some [d (get default-hiccup-renderers t)] (d ctx node)))
})
(defn ->hiccup
([node] (->hiccup default-hiccup-renderers node))
([ctx {:as node t :type}]
(let [{:as node :keys [type]} (cond-> node (= :doc t) hydrate-toc)]
(if-some [f (guard fn? (get ctx type))]
(f ctx node)
[:span.message.red
[:strong (str "Unknown type: '" type "'.")]
[:code (pr-str node)]]
))))
(comment
(-> "# Hello
a nice paragraph with sidenotes[^my-note]
[[TOC]]
## Section One
A nice $\\phi$ formula [for _real_ **strong** fun](/path/to) soft
break
- [ ] one **ahoi** list
- two `nice` and ~~three~~
- [x] checked
> that said who?
---
## Section Two
### Tables
| Syntax | JVM | JavaScript |
|--------|-------------------------:|:--------------------------------|
| foo | Loca _lDate_ ahoiii | goog.date.Date |
| bar | java.time.LocalTime | some [kinky](link/to/something) |
| bag | java.time.LocalDateTime | $\\phi$ |
### Images
![Some **nice** caption](https://www.example.com/images/dinosaur.jpg)
and here as inline ![alt](foo/bar) image
```clj
(some nice clojure)
```
[^my-note]: Here can discuss at length"
nextjournal.markdown/parse
->hiccup
)
;; override defaults
(->> "## Title
par one
par two"
nextjournal.markdown/parse
(->hiccup (assoc default-hiccup-renderers
:heading (partial into-markup [:h1.at-all-levels])
;; wrap something around the default
:paragraph (fn [{:as ctx d :default} node] [:div.p-container (d ctx node)]))))
)