mldoc.cljs 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. (ns frontend.format.mldoc
  2. (:require [cljs-bean.core :as bean]
  3. [cljs.core.match :refer [match]]
  4. [clojure.string :as string]
  5. [frontend.format.protocol :as protocol]
  6. [frontend.text :as text]
  7. [frontend.utf8 :as utf8]
  8. [frontend.util :as util]
  9. [goog.object :as gobj]
  10. [lambdaisland.glogi :as log]
  11. [medley.core :as medley]
  12. ["mldoc" :as mldoc :refer [Mldoc]]
  13. [linked.core :as linked]))
  14. (defonce parseJson (gobj/get Mldoc "parseJson"))
  15. (defonce parseInlineJson (gobj/get Mldoc "parseInlineJson"))
  16. (defonce parseOPML (gobj/get Mldoc "parseOPML"))
  17. (defonce exportToHtml (gobj/get Mldoc "exportToHtml"))
  18. (defonce anchorLink (gobj/get Mldoc "anchorLink"))
  19. (defonce parseAndExportMarkdown (gobj/get Mldoc "parseAndExportMarkdown"))
  20. (defonce parseAndExportOPML (gobj/get Mldoc "parseAndExportOPML"))
  21. (defonce astExportMarkdown (gobj/get Mldoc "astExportMarkdown"))
  22. (defn default-config
  23. ([format]
  24. (default-config format {:export-heading-to-list? false}))
  25. ([format {:keys [export-heading-to-list? export-keep-properties? export-md-indent-style]}]
  26. (let [format (string/capitalize (name (or format :markdown)))]
  27. (->> {:toc false
  28. :heading_number false
  29. :keep_line_break true
  30. :format format
  31. :heading_to_list (or export-heading-to-list? false)
  32. :exporting_keep_properties export-keep-properties?
  33. :export_md_indent_style export-md-indent-style}
  34. (filter #(not(nil? (second %))))
  35. (into {})
  36. (bean/->js)
  37. (js/JSON.stringify)))))
  38. (def default-references
  39. (js/JSON.stringify
  40. (clj->js {:embed_blocks []
  41. :embed_pages []})))
  42. (defn parse-json
  43. [content config]
  44. (parseJson content config))
  45. (defn inline-parse-json
  46. [text config]
  47. (parseInlineJson text config))
  48. (defn parse-opml
  49. [content]
  50. (parseOPML content))
  51. (defn parse-export-markdown
  52. [content config references]
  53. (parseAndExportMarkdown content
  54. config
  55. (or references default-references)))
  56. (defn parse-export-opml
  57. [content config title references]
  58. (parseAndExportOPML content
  59. config
  60. title
  61. (or references default-references)))
  62. (defn ast-export-markdown
  63. [ast config references]
  64. (astExportMarkdown ast
  65. config
  66. (or references default-references)))
  67. ;; Org-roam
  68. (defn get-tags-from-definition
  69. [ast]
  70. (loop [ast ast]
  71. (if (seq ast)
  72. (match (first ast)
  73. ["List" l]
  74. (when-let [name (:name (first l))]
  75. (let [name (and (vector? name)
  76. (last (first name)))]
  77. (when (and (string? name)
  78. (= (string/lower-case name) "tags"))
  79. (->>
  80. (last (first (:content (first l))))
  81. (map second)
  82. (filter (and map? :url))
  83. (map (fn [x]
  84. (let [label (last (first (:label x)))
  85. search (and (= (first (:url x)) "Search")
  86. (last (:url x)))
  87. tag (if-not (string/blank? label)
  88. label
  89. search)]
  90. (when tag (string/lower-case tag)))))
  91. (remove nil?)))))
  92. ["Heading" _h]
  93. nil
  94. :else
  95. (recur (rest ast)))
  96. nil)))
  97. (defn- ->vec
  98. [s]
  99. (if (string? s) [s] s))
  100. (defn- ->vec-concat
  101. [& coll]
  102. (->> (map ->vec coll)
  103. (remove nil?)
  104. (apply concat)
  105. (distinct)))
  106. (defn collect-page-properties
  107. [ast]
  108. (if (seq ast)
  109. (let [original-ast ast
  110. ast (map first ast) ; without position meta
  111. directive?
  112. (fn [[item _]] (= "directive" (string/lower-case (first item))))
  113. grouped-ast (group-by directive? original-ast)
  114. directive-ast (get grouped-ast true)
  115. [properties-ast other-ast] (if (= "Property_Drawer" (ffirst ast))
  116. [(last (first ast))
  117. (rest original-ast)]
  118. [(->> (map first directive-ast)
  119. (map rest))
  120. (get grouped-ast false)])
  121. properties (->>
  122. properties-ast
  123. (map (fn [[k v]]
  124. (let [k (keyword (string/lower-case k))
  125. v (if (contains? #{:title :description :filters :roam_tags} k)
  126. v
  127. (text/split-page-refs-without-brackets v))]
  128. [k v]))))
  129. properties (into (linked/map) properties)
  130. macro-properties (filter (fn [x] (= :macro (first x))) properties)
  131. macros (if (seq macro-properties)
  132. (->>
  133. (map
  134. (fn [[_ v]]
  135. (let [[k v] (util/split-first " " v)]
  136. (mapv
  137. string/trim
  138. [k v])))
  139. macro-properties)
  140. (into {}))
  141. {})
  142. properties (->> (remove (fn [x] (= :macro (first x))) properties)
  143. (into (linked/map)))
  144. properties (if (seq properties)
  145. (cond-> properties
  146. (:roam_key properties)
  147. (assoc :key (:roam_key properties)))
  148. properties)
  149. definition-tags (get-tags-from-definition ast)
  150. properties (cond-> properties
  151. (seq macros)
  152. (assoc :macros macros))
  153. alias (->> (->vec-concat (:roam_alias properties) (:alias properties))
  154. (remove string/blank?))
  155. filetags (if-let [org-file-tags (:filetags properties)]
  156. (->> (string/split org-file-tags ":")
  157. (remove string/blank?)))
  158. roam-tags (if-let [org-roam-tags (:roam_tags properties)]
  159. (let [pat #"\"(.*?)\"" ;; note: lazy, capturing group
  160. quoted (map second (re-seq pat org-roam-tags))
  161. rest (string/replace org-roam-tags pat "")
  162. rest (->> (string/split rest " ")
  163. (remove string/blank?))]
  164. (concat quoted rest)))
  165. tags (->> (->vec-concat roam-tags (:tags properties) definition-tags filetags)
  166. (remove string/blank?))
  167. properties (assoc properties :tags tags :alias alias)
  168. properties (-> properties
  169. (update :roam_alias ->vec)
  170. (update :roam_tags (constantly roam-tags))
  171. (update :filetags (constantly filetags)))
  172. properties (medley/filter-kv (fn [k v] (not (empty? v))) properties)]
  173. (if (seq properties)
  174. (cons [["Properties" properties] nil] other-ast)
  175. original-ast))
  176. ast))
  177. (defn update-src-full-content
  178. [ast content]
  179. (let [content (utf8/encode content)]
  180. (map (fn [[block pos-meta]]
  181. (if (and (vector? block)
  182. (= "Src" (first block)))
  183. (let [{:keys [start_pos end_pos]} pos-meta
  184. content (utf8/substring content start_pos end_pos)
  185. spaces (re-find #"^[\t ]+" (first (string/split-lines content)))
  186. content (if spaces (text/remove-indentation-spaces content (count spaces) true)
  187. content)
  188. block ["Src" (assoc (second block) :full_content content)]]
  189. [block pos-meta])
  190. [block pos-meta])) ast)))
  191. (defn block-with-title?
  192. [type]
  193. (contains? #{"Paragraph"
  194. "Raw_Html"
  195. "Hiccup"
  196. "Heading"} type))
  197. (defn ->edn
  198. [content config]
  199. (try
  200. (if (string/blank? content)
  201. {}
  202. (-> content
  203. (parse-json config)
  204. (util/json->clj)
  205. (update-src-full-content content)
  206. (collect-page-properties)))
  207. (catch js/Error e
  208. (log/error :edn/convert-failed e)
  209. [])))
  210. (defn opml->edn
  211. [content]
  212. (try
  213. (if (string/blank? content)
  214. {}
  215. (let [[headers blocks] (-> content (parse-opml) (util/json->clj))]
  216. [headers (collect-page-properties blocks)]))
  217. (catch js/Error e
  218. (log/error :edn/convert-failed e)
  219. [])))
  220. (defn inline->edn
  221. [text config]
  222. (try
  223. (if (string/blank? text)
  224. {}
  225. (-> text
  226. (inline-parse-json config)
  227. (util/json->clj)))
  228. (catch js/Error _e
  229. [])))
  230. (defrecord MldocMode []
  231. protocol/Format
  232. (toEdn [this content config]
  233. (->edn content config))
  234. (toHtml [this content config references]
  235. (exportToHtml content config references))
  236. (loaded? [this]
  237. true)
  238. (lazyLoad [this ok-handler]
  239. true)
  240. (exportMarkdown [this content config references]
  241. (parse-export-markdown content config references))
  242. (exportOPML [this content config title references]
  243. (parse-export-opml content config title references)))
  244. (defn plain->text
  245. [plains]
  246. (string/join (map last plains)))